你不知道的JS系列——深刻繼承


不要由於沒有掌聲,就放棄了最初的理想
javascript

繼承本質

軟件中的繼承是對真實世界繼承的一種抽象。在面嚮對象語言中,提到繼承,咱們先引出 這個概念。java

是什麼?

是一種自定義的數據類型,每一個能夠包含一組數據類型和操做數據的方法。字符串、數字就是數據類型,固然數據類型有不少,咱們能夠有不一樣的組合方式,這樣組合出來的複雜數據類型,咱們就稱之爲,一般咱們還會向裏面添加操做數據的方法。安全

面嚮對象語言中的 繼承

面嚮對象語言中的 繼承 ,本質上就是 複製 。子類將父類複製一份,那麼父類上有的屬性、方法,在子類上也就有了,咱們就說子類 繼承 了父類。閉包

Javascript 中的 繼承

很惋惜,在Javascript中並無 這個概念,在這裏萬物皆對象,因此天然不存在 類複製 這一說法。那Javascript中的 繼承 是怎樣作到的呢?答案是 複製對象委託關聯 。固然最好的實現方式是 委託關聯,就是利用原型鏈機制來達到所謂的 繼承 。簡單理解就是,有一個公有的對象,它上面存有屬性和操做方法,別的對象均可以去它上面拿到想拿的東西,它在這裏共享,這種模式就是Javascript中的 繼承 本質。ide


固然上面只是簡單分析,下面深刻原理。不過在此以前,由於本篇是講 繼承,咱們順便也講講對應面嚮對象語言另外兩大特徵:封裝多態 在Javascript中的實現方式。仍是按 封裝、繼承、多態 的順序講吧,下面就一塊兒進入叢林探險。 函數

封裝

顧名思義,封裝 就是將數據密封起來,讓外界訪問不到,而後咱們對外只提供操做數據的方法。封裝 的目的在於隱藏內部實現,保護數據安全,避免錯誤的修改數據。
在Javascript中,咱們利用 閉包 來實現數據的 封裝 。舉例以下:測試

function person(){
    let obj = {
        name: 'Tom',
        age: 18
    };
    return {
        get(){
            return obj.name + '====' + obj.age
        },
        set(name, age){
            obj.name = name;
            obj.age = age;
        }
    }
}
var p = person();
p.get();    // "Tom====18"
p.set('Bob', 20);
p.get();    // "Bob====20"
複製代碼

上面咱們沒法直接訪問obj中的數據,對數據的訪問只能經過返回的方法來進行讀取和操做。固然閉包有閉包的好處,不過也不要濫用,要及時消除閉包。ui

繼承

這裏不會講 class、extends 這種語法糖,由於 class 的本質仍是對象(萬物皆對象),咱們只講模擬實現 繼承 的五種方式。this

1. 顯示混入

function mixin( sourceObj, targetObj ) {    // 混入的方法
    for (var key in sourceObj) { // 只會在不存在的狀況下複製
        if (!(key in targetObj)) { 
            targetObj[key] = sourceObj[key]; 
        } 
    }
    return targetObj; 
}
var Animal = { 
    eyes: 2, 
    eat: function() { 
        console.log( "Animal love eat." ); 
    }
};
var Cat = mixin( Animal, { 
    tail: 1,    // 貓有1條尾巴
    eat: function() { 
        Animal.eat.call(this); 
        console.log( "Cat also love eat." ); 
    } 
});
複製代碼

須要注意的就是,這裏的 複製淺複製(淺拷貝),固然安全起見仍是進行 深拷貝spa

2.寄生繼承

顯式混入模式的一種變體,它既是顯式的又是隱式的。

function Animal() {
    this.eyes = 2; 
}
Animal.prototype.eat = function() { 
    console.log( "Animal love eat." ); 
};
//「寄生類」Cat
function Cat() {    // cat 也是一種 Animal
    var cat = new Animal();
    cat.tail = 1;
    var animalEat = cat.eat;
    cat.eat = function() { 
        animalEat.call( this ); 
        console.log( "Cat also love eat." );
    }
    return cat; 
}
var myCat = new Cat(); 
myCat.eat();    // Animal love eat. Cat also love eat.
複製代碼

如你所見,首先咱們 複製 了一份 Animal 父類(對象)的定義,而後 混入 Cat 子類(對象)的定義,而後再用這個複合對象構建實例。

3.隱式混入

var Animal = { 
    eat: function() {
        this.kind = 'Animal'
    } 
};
Animal.eat(); 
Animal.kind;    // "Animal"
var Cat = { 
    eat: function() { // 隱式把 Animal 混入 Cat 
        Animal.eat.call(this); 
    }
};
Cat.eat(); 
Cat.kind;   // "Animal"
複製代碼

經過在方法調用中使用 Animal.eat.call(this),咱們把 Animal 的行爲 混入 到了 Cat 中。

4.原型繼承

function Foo(name) {
    this.name = name; 
}
Foo.prototype.myName = function() {
    return this.name; 
};
function Bar(name,label) { 
    Foo.call( this, name );
    this.label = label; 
}
Bar.prototype = Object.create( Foo.prototype ); 
Bar.prototype.myLabel = function() {
    return this.label; 
};
var a = new Bar( "a", "obj a" ); 
a.myName();      // "a" 
a.myLabel();     // "obj a"
複製代碼

典型的「原型風格」,核心部分 Bar.prototype = Object.create( Foo.prototype ), 即建立一個新的 Bar.prototype 對象並把它關聯到 Foo. prototype

5.對象關聯

var Foo = { 
    init: function(who) {
        this.me = who; 
    },
    identify: function() {
        return "I am " + this.me; 
    } 
};
Bar = Object.create( Foo ); 
Bar.speak = function() { 
    console.log( "Hello, " + this.identify() + "." ); 
};
var b1 = Object.create( Bar ); 
b1.init( "b1" ); 
var b2 = Object.create( Bar );
b2.init( "b2" ); 
b1.speak(); 
b2.speak();     // Hello, I am b1. Hello, I am b2.
複製代碼

咱們利用 [[Prototype]] 委託,實現了三個對象之間的關聯,代碼簡潔易懂。

多態

多種狀態,指同一個實體同時具備多種形式。好比水有三種形態,液態、氣態、固態。咱們簡單理解就是子類都由父類派生,但它們的表現形式不一樣。在代碼中的具體體現就是子類方法的覆寫,你們都懂的,這裏就不作贅述了。

最後

這裏注意兩個概念:

instanceof 運算符用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。
isPrototypeOf() 方法用於測試一個對象是否存在於另外一個對象的原型鏈上。

Tips: 千萬不要按字面意思理解,將instanceof理解爲誰是誰的實例,將isPrototypeOf理解爲誰是誰的原型。

每個有夢想的人都值得被尊敬!
相關文章
相關標籤/搜索