1. 程式人生 > >JavaScript中的繼承模式

JavaScript中的繼承模式

本文是前端學習筆記的第六篇,對應web前端開發JavaScript精英課js的第21課時,本篇主要寫關於JS中的四種繼承方式,這四種也可以說是整個JS繼承的發展史了

目錄

JavaScript中的繼承發展史

1. 原型鏈

2. 借用建構函式(通過call/apply)

3. 共享原型

4. 聖盃模式


JavaScript中的繼承發展史

JS繼承一共可分為四種,根據不斷的發展進化由第一種進化到如今普遍使用的第四種

  • 原型鏈
  • 借用建構函式(通過call/apply)
  • 共享原型
  • 聖盃模式

 

1. 原型鏈

通過原型鏈的方式實現繼承,是最初級版的繼承,這種繼承方式存在一定的弊端,譬如我只想繼承某個建構函式的原型物件的一些屬性,但卻不得不一併繼承了屬於建構函式自己的一些屬性

 <script>

        Father.prototype.Firstname = "周";

        function Father() {
            this.englishName = "I am Father";
        }

        var father = new Father();
        Son.prototype = father;

        function Son() {

        }

        var son = new Son();

</script>

son此時只想繼承Father的FirstName,但卻因為原型鏈的緣故,不得不繼承了原本不需要的屬性EnglishName

,這顯然不是我們所期望的,因此便有了後來的一系列繼承方式

 

2. 借用建構函式(通過call/apply)

通過call/apply函式,我們可以借用別的建構函式來為物件增添屬性。先複習一下call和apply的用法,二者都是用於重定義this的指向,區別是前者是通過 函式.call(物件引用,args0,args1...)的方式可以把物件引用取代前面函式中的this的位置進行操作,後者是通過函式.apply(物件引用,args[n])的方式改變函式中的this引用,也即是引數用陣列來表示

接下來看下面一個例子

<script>

        function Car(weight,height,speed) {
            CarFactory.call(this,weight,height,speed);
        }
        
        function CarFactory(weight,height,speed) {
            this.weight = weight;
            this.height = height;
            this.speed = speed;
        }

        var car = new Car('4900','160','1km/s');

</script>

此時在建立物件car時通過借用建構函式CarFactory,為自己添加了屬性weight,height,speed,當然準確來說這不能算繼承,只是借用別的建構函式為自己增加屬性,且這種方式也有缺點,若過多使用會造成編碼效率降低,程式碼冗餘

 

3. 共享原型

在第一第二種繼承方式都不理想的情況下,又發展出了第三種繼承模式:共享原型

看下面一個例子

<script>

        Father.prototype.firstName = "周";

        function Father() {
            this.englishName = "I am Father";
        }


        Son.prototype = Father.prototype;

        function Son() {

        }

        var son = new Son();

</script>

共享了Father.prototype與Son.prototype後,此時物件son相比第一種原型鏈的情況有了很大的改善,不再會繼承無關的屬性englishName,因為這個屬性是屬於用建構函式Father建立的物件,而此時並沒有用建構函式Father建立物件,只是共享了彼此的原型,也就是隻是獲得了屬性firstName

但是這種做法仍有缺陷,再看下面一個例子

<script>

        Father.prototype.firstName = "周";

        function Father() {
            this.englishName = "I am Father";
        }


        Son.prototype = Father.prototype;

        function Son() {

        }

        var son = new Son();

        Son.prototype.firstName = '陳';

        console.log(Father.prototype.firstName);   // 陳

</script>

此時試圖修改Son的原型物件上的firstName屬性,但是因為共享原型的緣故,Father.prototype和Son.prototype的引用是相同的,那麼修改其中一個的屬性就會修改另外一個物件的屬性,這顯然不是我們希望看到的,因此,便提出了新的解決方案

 

4. 聖盃模式

既然修改原型物件會導致共享的原型物件的屬性改變,那麼是否可以在二者之間建一箇中間層隔開一下呢,基於這樣的考慮,聖盃模式便誕生了,看下面一個例子

<script>

        function inherit(Orign,Target) {
            function F () {}
            F.prototype = Orign.prototype;
            Target.prototype = new F();
            Target.prototype.constructor = Target;
            Target.prototype.uber = Orign.prototype;
        }

        function Son() {
            
        }

        Father.prototype.firstName = '周';

        function Father() {
            
        }

        inherit(Father,Son);

        var son = new Son();

</script>

原本的共享原型是直接Target.prototype = Orign.prototype,而這裡通過一個建構函式F,作為一箇中間層,使得Target.prototype修改的用建構函式 f 造出來的物件的屬性,而不會干預到Father.prototype的屬性值,這裡再提提另外兩行特別的程式碼

  • Target.prototype.constructor = Target
  • Target.prototype.uber = Orign

第一行是修改原型物件的構造器,我們知道,物件本身是沒有constructor屬性的,這個屬性在物件的原型物件身上的,而這裡son的原型物件是用建構函式F造出來的物件,本身系統預設給的prototype,其身上同樣沒有constructor,繼續在其原型物件身上找,也就是Father.prototype身上找,終於找到了constructor屬性,而constructor屬性表示的是物件的建構函式,因此如果沒有這句,那麼son的constructor便是Father.prototype身上的constructor,修改後,便變為了自己,更符合實際情況

第二行是為Son的原型物件新增一個uber屬性,值為Orign.prototype,因為此時已改變了構造器,此時不再知道其共享的到底是哪個原型物件,因此便新增一個屬性用於找回這個原型物件

 

當然,還可以更進一步,把聖盃模式寫的更加簡潔、結構清晰

<script>
        var inherit = (function () {
            var f = function () {}
            return function (Orign,Target) {
                f.prototype = Orign.prototype;
                Target.prototype = new f();
                Target.prototype.constructor = Target;
                Target.prototype.uber = Orign.prototype;
            }
        }());

        function Son() {
            
        }

        Father.prototype.firstName = '周';

        function Father() {
            
        }

        inherit(Father,Son);

        var son = new Son();

</script>

把整個共享原型的過程以立即執行函式的方式表現,程式碼可讀型大大提高,程式碼複用率也高,最重要的是,避免了對全域性變數的汙染,推薦平時如果需要用繼承就用這種做法