為什么會用這樣一個題目呢,這是要說封裝的什么問題,本文並不講高深的封裝理論,只是解決一個小問題。
問題來源
今天在百度知道上閑逛,遇到一個網友的問題,問題如下,問題的地址見這里:
下面先不看看其他網友給的答案:
網友大部分回答不能一起定義,那么我們來分析下為什么這樣做是錯的,然后給出相應的解決辦法。
重現問題
先來說說為什么調用出錯,我在自己的瀏覽器里重現了問題,處於實驗並未全部復原代碼,並且用到了全局變量哦:
function Dialog(){ Dialog.prototype = { init:function(){ console.log("ok"); } } } var a = new Dialog(); a.init();
下面是火狐提示的錯誤:
分析問題
init不是一個方法,這是為什么呢,我們將調用代碼修改下,出於演示,並未遵循JsLint代碼規范:
var a = new Dialog(); typeof a.init;
結果顯示為undefined,也就是init沒有被定義,或者說在求值過程中未找到init標識符的值,這是因為在調用函數時函數里面的內容才會被解析執行,所以在調用new Dialog(),時其內部的代碼尚未執行,所以設置Dialog的原型的語句尚未執行,通過這個例子可以看出綁定this的prototype的過程是在執行構造函數內部代碼之前,可以用下面的代碼來解釋下:
function Dialog(){ var that = Object.create(Object.getPrototypeOf(this)); Dialog.prototype = {} }
“巧妙解決”
很明顯獲取原型時尚未設置原型,但當我們再次調用new 時情況將發生改變:
var a = new Dialog(); var b = new Dialog(); typeof b.init;
在此調用時奇跡發生了,我們看到第二次調用時成功獲取了值,只有第一次時值是未獲去,那我們是不是可以改造下我們的dialog函數呢:
function Dialog(){ Dialog.prototype = { init:function(){ console.log("ok"); } } } new Dialog(); var a = new Dialog(); typeof a.init;
我們只需先調用下構造函數便解決了問題,如果你覺得上面的代碼還是有悖於封裝我們可以再做改變:
var Dialog = (function(){ function Dialog(){ Dialog.prototype = { init:function(){ console.log("ok"); } } } new Dialog(); return Dialog; }());
問題中的問題
上面真的解決問題了嗎,你難道沒有疑問呢,如果你已經看出問題所在在了,那你也一定能想出解決辦法,而且你應該是一個高手,那么讓我們看看這樣巧妙的解決辦法有什么問題,為此我們來構造下面的代碼:
var a = new Dialog(); typeof a instanceof Dialog;
也許你會問為什么會這樣寫,也許下面的結果更讓你吃驚:
結果為false,為什么我的a不是Dialog的實例呢,我的a明明是Dialog創建的,要想搞清這個問題我們先得說清楚 instanceof關鍵字的工作原理,當我們調用類似a instanceof Dialog 這樣的語句時,解釋器是怎么判斷a是Dialog創建的對象的呢,原來解釋器是判斷a的原型是否為Dialog的prototype屬性所指向的對象也就是說如果a的原型和Dialog的prorotype屬性指向同一個對象就認為a是Dialog的對象,當然在判斷時並不是至判斷a的的原型,而是判斷原型鏈中的每個對象,例如:
var a = []; a instanceof Array; a instanceof Object;
上面的兩條語句都會返回true,因為a的原型鏈中包含這兩個對象。
而上面我們的代碼為什么結果為false呢,那是因為當我們每次調用Dialog構造函數時都會在內部重寫Dialog的原型,而已經創建的對象的原型會指向原來的原型對象,解釋器在判斷兩個對象是否相等時,要判斷兩個對象是否引用同一塊地址,而不是兩個對象是否有相同的屬性和方法,所以上面出現false的原因就很清楚了,所以上面的解決辦法就出現問題了,而且是很大的問題。顯然這種方法行不通。
看清本質
那我們有沒有辦法解決問題呢,讓我們先來看看作者想要實現什么,作者想要實現的封裝,也就是構造函數和構造函數的原型分開寫的問題,作者想把他們寫到一起,作者認為這才是封裝,那么我們先來看下封裝是什么,作者對封裝的理解是否有誤:
封裝,1、在程序上,隱藏對象的屬性和實現細節,僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別;將抽象得到的數據和行為(或功能)相結合,形成一個有機的整體,也就是將數據與操作數據的源代碼進行有機的結合,形成“類”,其中數據和函數都是類的成員。
上面是對封裝的解釋,可以看出這跟作者描述的封裝並不是一個意思,作者此處所想表達的實際上是更好的代碼結構。
建議
既然清楚了作者的意思,來看下解決辦法,如何將構造函數的定義和原型的定義寫到一起呢,看下我給出的解決辦法:
var Dialog = (function(){ function Dialog(){ } Dialog.prototype = { init:function(){ console.log("ok"); } } return Dialog; }()); var a = new Dialog(); a instanceof Dialog;
好了問題解決了,結果正確了,而且我們也得到了比較清晰的代碼結構。
如果你覺得我寫的不錯,可以在這里關注我的微博。