再讀《悟透javascript》之二、JavaScript原型

前言

      JavaScript原型是JavaScript的精髓所在,理解了JavaScript原型對於我們理解JavaScript有着重大的意義。

 

 

一、構造對象引出的問題

1.       構造對象

在JavaScript中創建一個新的對象,可以如下:

    function test(){}

    var t1 = new test();

           而上面的方法等價於如下:

    function test(){}

var t1 = {};  //創建一個空的對象(此時t1指向這個新創建的對象)
t1.__proto__ = test.prototype;   //將t1的內置對象指針指向test的prototype對象,t1的內置對象指針是無法直接訪問到的,但是內置對象指針所指向的對象的方法和屬性是可以被直接使用的
test.call(t1);

先創建一個空的對象(t1),然後將t1的內置對象指針指向test的prototype對象,最後調用test函數,並將該對象(t1)作爲test函數的this。

理解上面的三個步驟對於理解後面的代碼有着非常關鍵的意義,我相信會有很多人覺得讀到這理解了,但是當往後讀就會發現,他們的理解是不夠深入的,但沒有問題,到時記得再回來讀讀就好。(注:JavaScript中所有的obj對象都有__proto__屬性,但是該屬性無法被訪問,只有在new xxx的時候,會將其指向xxx的prototype對象)

注意:三個步驟的第一步,創建一個空的對象,假如t1是原本指向另外的對象的話,之後t1就會改爲指向這個新創建的對象了。例:

    <script type="text/javascript">

    var luo = new Object();

    luo.name = "soldierluo";

    luo.age = 23;

    alert(luo.name+" is "+luo.age+" years old");     //此時的luo所指向的對象擁有兩個屬性
   

    function person(){

        this.salary = "33333";

    }

    luo = new person();     //這之後,luo所指向的對象替代了此前的luo所指向的對象(就是所luo的指向改變了)
    alert(luo.name+" is "+luo.age+" years old");     //所有這時訪問不到原來的對象的屬性了
    alert(luo.salary);

</script>

 

 

二、模擬繼承

我們可以通過call來模擬繼承,例:

    <script type="text/javascript">

    function person(name){

        this.name = name;

        this.say = function(){alert("i'm "+this.name)};

    }

    function worker(name, salary){

        person.call(this,name);

        this.salary = salary;

        this.show = function(){alert(this.name+"'s salary is "+this.salary)};

    }

   

    var w1 = new worker("luo",10000);

    var w2 = new worker("soldierluo",333333);

    w1.say(); w1.show();

w2.say(); w2.show();

alert(w1.say==w2.say);

</script>

上面我們將person和worker看成兩個類,而worker類繼承了person類,這樣worker類就獲得了person類的屬性和方法。

而繼承的實現是通過在worker中調用person方法,並將worker的this傳遞給person,而不爲person的this,這樣,person和worker就擁有了同樣的this,從而實現了繼承。

 

 

但是。。。。。。有一個不小的問題——函數沒能複用

上面代碼的最後一句,返回的是false,這說明w1和w2對象使用的不是同一個say函數體。這是因爲say方法在w1和w2兩個對象中都有一份say函數體的拷貝。同一個類的對象各自擁有一套函數體顯然是一種浪費。

 

 

三、使用原型(prototype)杜絕浪費

1.       JavaScript中所有function類型的對象都有一個prototype屬性,這個prototype屬性又指向一個object對象,所以我們可以任意給它添加屬性和方法。通過同一函數聲明的對象,在該函數的prototype屬性上的方法和屬性都是公用的,並且可以直接使用。例:

    <script type="text/javascript">

    function person(name){this.name = name;}

    person.prototype.say = function(){alert("i'm "+this.name);}

    var p1 = new person("luo");

    var p2 = new person("soldier");

alert(p1.say==p2.say);

p1.say();p2.say();

</script>

返回結果爲true,說明p1和p2對象使用的是同一個say方法體,而prototype上的say方法也可以直接調用。

 

 

2.       將prototype應用於繼承,這樣繼承的類就可以使用同一個方法體了,例:

    <script type="text/javascript">

    function person(name){

        this.name = name;

    }

    person.prototype.say = function(){alert("i'm "+this.name)};

    function worker(name, salary){

        person.call(this,name);

        this.salary = salary;

        this.show = function(){alert(this.name+"'s salary is "+this.salary);};

    }

   

    var w1 = new worker("luo",10000);

    var w2 = new worker("soldierluo",333333);

    w1.say();

    w2.say();

    alert(w1.say);

    alert(w2.say);

</script>

執行上面的代碼,發現報錯,如果註釋掉w1.say();w2.say();,我們會驚奇地發現,w1.say和w2.say方法居然爲undefined,這真是奇怪了。

來看上面的代碼,worker類通過person.call(this,name);來繼承了person類,這樣它們就擁有了同樣的this,但person的say方法並不在this上,而在prototype上。那如何才能使用到person的prototype上的方法呢?最重要的一步:worker.prototype = new person();,通過這句,我們可以將worker的prototype與person的prototype關聯起來。當worker的prototype中找不到對應的屬性或方法時,它會根據上面的關聯找到person的prototype中去,這就是所謂的“原型鏈繼承”。

 

 

最終代碼如下:

    <script type="text/javascript">

    function person(name){

        this.name = name;

    }

    person.prototype.say = function(){alert("i'm "+this.name)};

    function worker(name, salary){

        person.call(this,name);

        this.salary = salary;

        this.show = function(){alert(this.name+"'s salary is "+this.salary);};

    }

    worker.prototype = new person();

   

    var w1 = new worker("luo",10000);

    var w2 = new worker("soldierluo",333333);

    w1.say();

w2.say();

</script>

 

分析上面的繼承,可以發現兩個最重要的點
1)       通過person.call(this);來實現this的繼承(或者叫擴展,我覺得更合適)
2)       通過worker.prototype=new person();(內部實現爲:worker.prototype.__proto__=person.prototype)來實現prototype的繼承(或者叫擴展,我覺得更適合)
 

 

四、閉包——原型擴展

微軟曾經使用了一種稱爲“閉包”的技術來模擬類,其大致模型如下:

    <script type="text/javascript">

    function person(name, age){

        var name = name;    //私有變量
        var getName = function(){alert("my name is "+name);}   //私有方法
       

        this.age = age;     //公共變量  

        this.say = function(){alert(name+" is "+this.age+" years old");}  //公共方法
    }

   

    var p1 = new person("luo",23);

    p1.say();     alert(p1.age);    //調用公共方法、獲取公共變量
    p1.age=100;   alert(p1.age);    //修改公共變量值
    p1.say();     alert(p1.age);    //調用公共方法、獲取公共變量
   

    p1.getName(); alert(p1.name);   //調用私有方法、獲取私有變量
</script>

上面可以看到,凡是通過var聲明的,不論是函數還是屬性,外部是無法訪問或更改的,而掛靠在this對象後的屬性和方法則可以在外部訪問和更改。通過這樣的屬性,也就模擬了面向對象中的私有和公有屬性,這就稱之爲“閉包”。

 

 

官方解釋(看起來比較頭痛):所謂的“閉包”,就是在構造函數體內定義另外的函數作爲目標對象的方法函數,而這個對象的方法函數反過來引用外層外層函數體中的臨時變量。這使得只要目標對象在生存期內始終能保持其方法,就能間接保持原構造函數體當時用到的臨時變量值。儘管最開始的構造函數調用已經結束,臨時變量的名稱也都消失了,但在目標對象的方法內卻始終能引用到該變量的值,而且該值只能通這種方法來訪問。即使再次調用相同的構造函數,但只會生成新對象和方法,新的臨時變量只是對應新的值,和上次那次調用的是各自獨立的。

 

 

五、給每一個對象設置一份方法是一種很大的浪費。還有,“閉包”這種間接保持變量值的機制,往往會給JavaSript的垃圾回收器製造難題。特別是遇到對象間複雜的循環引用時,垃圾回收的判斷邏輯非常複雜。無獨有偶,IE瀏覽器早期版本確實存在JavaSript垃圾回收方面的內存泄漏問題。再加上“閉包”模型在性能測試方面的表現不佳,微軟最終放棄了“閉包”模型,而改用“原型”模型。正所謂“有得必有失”嘛。“原型模型”例:

    <script type="text/javascript">

    function person(name, age){

        this.name = name;

        this.age = age;

    }

    person.prototype.say = function(){alert(this.name+" is "+this.age+" years old");}

   

    function worker(name, age, salary){

        person.call(this,name,age);

        this.salary = salary;

    }

    worker.prototype = new person();

    worker.prototype.show = function(){alert(this.name+"'s salary is "+this.salary);}

   

    var w1 = new worker("luo",23,5000);

    var w2 = new worker("soldier",32,50000);

    w1.show();  w1.say();

    w2.show();  w2.say();

</script>

這裏的原型模型其實就是上面說的模擬繼承,沒有私有變量,並且要分兩部分來定義類,顯得不夠簡介,不過對象的方法是共享的,並且不論是在垃圾回收還是性能方面都要優於閉包模型。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章