JavaScript 封裝問題

2018-06-16 18:35 更新

為什么會用這樣一個題目呢,這是要說封裝的什么問題,本文并不講高深的封裝理論,只是解決一個小問題。

問題來源

今天在百度知道上閑逛,遇到一個網(wǎng)友的問題,問題如下,問題的地址見這里

下面先不看看其他網(wǎng)友給的答案:

網(wǎng)友大部分回答不能一起定義,那么我們來分析下為什么這樣做是錯的,然后給出相應的解決辦法。

重現(xiàn)問題

先來說說為什么調(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)問題了,而且是很大的問題。顯然這種方法行不通。

看清本質(zhì)

那我們有沒有辦法解決問題呢,讓我們先來看看作者想要實現(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)。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號