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