JavaScript 的繼承

繼承是OO語言中的一個最爲人津津樂道的概念。ECMAScript實現繼承主要是經過原型鏈來實現的。函數

1,原型鏈

咱們知道,每個構造函數都有一個原型對象,這個函數包含一個指向構造函數的指針,同時每一個實例都有一個指向原型對象的內部指針。也就是說,當咱們訪問一個實例的屬性時,先在實例中查找,若是沒找到,就經過內部指針去原型中查找,若仍是沒找到,再經過原型的內部指針查找原型的原型對象。this

 

//父類

    function Parent(){

        this.name = "父類";

    }

    //父類原型

    Parent.prototype.getName = function(){

        console.log(this.name );

    };

    //子類

    function Son(){

        this.age = 23;

    }

    //子類繼承父類

    Son.prototype = new Parent();//注意這個地方父類的實例方法也在子類的原型裏面了,例如name

    //給子類原型添加方法

    Son.prototype.getAge = function(){

        console.log(this.age)

    };

    //新建對象,繼承子類的實例和原型

    var test = new Son();

    console.log(test.name);

    test.getName();

    console.log(test.age);

    test.getAge();

    console.log(test);

    console.log(Son.prototype);

    console.log(Parent.prototype);

    //肯定實例與原型的關係 兩種方法

    // instanceof

    alert(test instanceof Object);

    alert(test instanceof Son);

    alert(test instanceof Parent);

    // isPrototypeOf

    alert(Object.prototype.isPrototypeOf(test));

    alert(Son.prototype.isPrototypeOf(test));

    alert(Parent.prototype.isPrototypeOf(test));

 

2a4151e3-7bdd-4997-9656-9e5dfb08c480

 

把test和Son的原型都打印出來,就是在控制檯能夠看看原型鏈的一個繼承,test的原型如上圖,指向Son,Son有個age實例屬性,有getAge原型屬性,有name原型屬性,Son 的原型指向Parent函數。整個的原型鏈就是這樣繼承下來的。spa

2,借用構造函數

原型鏈的的弊端就是,在建立子類的實例的時候,不能向父類的構造函數傳參數。借用構造函數能夠解決這個問題,組合繼承就是結合構造函數和原型鏈來的,如今先單獨的看下構造函數是怎麼實現傳參的功能的。prototype

//父類

    function Parent(name){

        this.name = name;

    }

    //子類

    function Son(name){

        Parent.call(this, name)

    }

    var p1 = new Parent('p1');

    console.log(p1.name);// p1

    var o1 = new Son('o1');

    console.log(o1.name);// o1

    var o2 = new Son('o2');

    console.log(o2.name);// o2

 

以上能夠看到,主要是借用call函數來實現傳參的功能,call實現的話,只會調用父函數的示例屬性和方法。指針

3,組合繼承

上邊已經提到組合繼承,將兩種優勢結合起來,先看下示例,code

//父類

    function Parent(name){

        this.name = name;

    }

    Parent.prototype.getName = function() {

        console.log(this.name);

    }

    //子類

    function Son(name){

        Parent.call(this, name);

    }

    Son.prototype = new Parent('');

    Son.prototype.constructor = Son;

    var o1 = new Son('o1');

    console.log(o1.name);

    o1.getName();// o1

    var o2 = new Son('o2');

    console.log(o2.name);

    o2.getName();// o2

 

以上兩種已經寫的比較詳細了,Son.prototype.constructor = Son;這句須要注意一下,Son的原型已經被重寫過,constructor這個屬性須要從新指向自己,打印出o1,在控制檯便可看出。對象

4,原型式繼承

借用已有的對象去建立新對象,和前面的原型鏈類似,blog

二者區別是這裏是根據對象建立新對象,原型鏈則是根據new出來的函數實現繼承。

function creat(obj){

        function F(){};

        F.prototype = obj;

        return new F;

    }

    var obj = {

        name: 'abc',

        color: ['red', 'blue']

    }

    var fn1 = creat(obj);

    fn1.color.push('black')

    console.log(fn1)

    var fn2 = creat(obj);

    fn2.color.push('white')

    console.log(fn2)

    console.log(obj)

 

1a738ec9-34cd-4db4-b504-f3031d49c0d3

打印出fn1可以看出,fn1指向的是一個臨時函數F,F的原型指向obj,這個函數實現了原型繼承,可是類和實例的關係倒是無法清晰的判斷的。另,上圖第一行是三個,下邊是4個,正好驗證了原型的動態性。繼承

既然跟原型鏈的那麼像,就寫了個若是obj是函數的,做爲對比來看下二者在代碼上有何區別,原型的繼承,以下ip

function obj() {

    }

    obj.prototype.name = 'abc';

    obj.prototype.color = ['red', 'blue'];

    var fn1 = new obj;

    fn1.color.push('black')

    console.log(fn1)

    var fn2 = new obj;

    fn2.color.push('white')

    console.log(fn2)

 

打印結果跟上邊是同樣的,區別就是下邊的能清晰的看到原型鏈的繼承關係。

5,寄生式繼承

寄生式跟原型式是緊密聯繫的,能夠說是寄生式在原型式的基礎上的,即建立一個僅用於封裝繼承過程的函數,該函數在內部已某種方式來加強對象,最後返回對象。

function creat(obj){

        function F(){};

        F.prototype = obj;

        return new F;

    }

    var obj = {

        name: 'abc',

        color: ['red', 'blue']

    }

    function creatAnother(obj) {

        var fn1 = creat(obj);

        fn1.getName = function() {

            console.log(this.name);

        }

        return fn1;

    }

    var anotherObj = creatAnother(obj);

    anotherObj.getName();

使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用率而下降效率。 

6,寄生組合式繼承

上面提到的組合繼承是js中經常使用的繼承模式,可是是有不足的地方,就是對父類構造函數調用了兩次。一次是建立子類型原型的時候,一次是在子類型構造函數內部。

//父類

    function Parent(name){

        this.name = name;

    }

    Parent.prototype.getName = function() {

        console.log(this.name);

    }

    //子類

    function Son(name){

        Parent.call(this, name);//第二次調用

    }

    Son.prototype = new Parent(''); //第一次調用

    Son.prototype.constructor = Son; // 將子類的constructor屬性指回本身

    var o1 = new Son('o1');

    console.log(o1.name);

    o1.getName();

    var o2 = new Son('o2');

    console.log(o2.name);

    o2.getName();

 

第一次調用,是給子類的原型上邊加上父類的實例屬性name,第二次是給子類實例添加實例屬性name,子類的實例屬性就屏蔽掉原型裏面name屬性。

那能不能再建立子類型的原型的時候,只將父類的原型給子類,沒有必要在子類的原型上建立多餘的屬性。按照這個想法,用兩種方法實現了下,

function creat(obj){

        function F(){};

        F.prototype = obj;

        return new F;

    }

    function inherit(son, parent) {

        var proto = creat(parent.prototype);

        proto.constructor = son;

        son.prototype = proto;

} //父類 function Parent(name){ this.name = name; } Parent.prototype.getName = function() { console.log(this.name); } //子類 function Son(name){ Parent.call(this, name) } Son.prototype = new Parent(''); //第一種寫法 // Son.prototype = Parent.prototype; //第二種寫法 // inherit(Son, Parent) // 第三種寫法 Son.prototype.constructor = Son; var o1 = new Son('o1'); console.log(o1); console.log(Son); console.log(Parent);

第二種是本身寫着簡單實現的一個寫法,後來發現這樣會把原型指向搞亂掉,Son.prototype.constructor = Son;這句會將父類的constructor 也指向到Son。

第三種就是寄生組合繼承了,只調用了一次構造函數,又避免在子類建立多餘屬性。

相關文章
相關標籤/搜索