天天看點

JavaScript中實作繼承的八種方式1.原型鍊繼承2.借用構造函數(經典繼承)3.組合繼承(僞經典繼承)4.原型式繼承5.寄生式繼承6.寄生組合式繼承(僞類繼承)7.混入式繼承8.ES6的extends

1.原型鍊繼承

核心思想:子類的原型等于父類的一個執行個體。

function Parent() {
    this.name = 'afeng';
}
Parent.prototype.getName = function() {
    console.log(this.name);
}

function Child() {

}
// 這裡是最關鍵的一步
Child.prototype = new Parent();

var child1 = new Child();
console.log(child1.getName()); // afeng
           

缺點:

1.引用類型的屬性被所有的執行個體共享。這是因為子類的原型現在是父類的一個執行個體,如果父類的執行個體上存在引用類型的值,就會被共享。

2.在建立Child的執行個體時,不能向Parent傳參。 解決辦法:使用借用構造函數

2.借用構造函數(經典繼承)

基本思想:在子類構造函數中,調用執行父類構造函數,竊取父類構造函數中的屬性。

核心實作代碼:Parent.call(this);

function Parent(name) {
   this.name = name;
   this.likes = ['coding', 'sport']
}

function Child(name) {
   // 核心實作,通過構造函數竊取,現在每個子類執行個體上都有likes這個屬性。
   Parent.call(this, name);
}

var child1 = new Child('afeng');
child1.likes.push('game');
console.log(child1.likes);

var child2 = new Child('fang');
console.log(child2.likes);
           

優點:

1.避免了引用類型的屬性被所有執行個體共享

2.可以在Child中向Parent傳參

缺點:

1.方法都定義在構造函數中,每次建立執行個體都會建立一遍方法。

2.隻能繼承父類的執行個體屬性和方法,不能繼承原型屬性和方法。 因為并沒有改變子類的原型

3.組合繼承(僞經典繼承)

基本思想:原型鍊繼承+借用構造函數

用原型鍊實作對原型屬性和方法的繼承,用借用構造函數實作執行個體屬性的繼承。

function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
    console.log(this.name);
}

function Child(name, age) {
    // 借用構造函數
    Parent.call(this, name);
    this.age = age;
}

// 原型鍊繼承
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('Afeng', '18');
child1.colors.push('black');
console.log(child1.colors);

var child2 = new Child('yaFeng', '18');
console.log(child2.colors);
           

缺點:

1.兩次調用父類構造函數,

2.并且子類執行個體和子類原型中都有一個colors屬性

4.原型式繼承

基本思想:

function createObj(obj) {
    function F() {};
    F.prototype = obj;
    return new F();
}
           

其實就是ES5 Object.create()的模拟實作,将傳入對象作為被建立對象的原型對象。

var person = {
    name: 'afeng',
    like: ['coding', 'game']
}

var person1 = createObj(person); // [ 'coding', 'game', 'sport' ]
var person2 = createObj(person); // [ 'coding', 'game', 'sport' ]

person1.like.push('sport');
console.log(person1.like);

console.log(person2.like);

// 隻是在person1上添加了name屬性。
person1.name = 'yafeng';
console.log(person1.name); // yafeng
console.log(person2.name); // afeng
           

缺點:引用類型值會共享。

5.寄生式繼承

基本思想:基本思想:建立一個僅用于封裝繼承過程的函數,該函數在内部以某種形式增強對象,最後傳回對象。

function createObj(obj) {
    var clone = Object.create(obj);
    clone.getName = function() {
        console.log(this.name);
    }
    return clone;
}

var person = {
    name: 'afeng',
    like: ['coding', 'game']
};
// 傳一個對象,作為傳回對象的原型。
var afeng = createObj(person);
afeng.getName()
afeng.like.push('sport');
console.log(afeng.like); // [ 'coding', 'game', 'sport' ]

var mengfan = createObj(person);
console.log(fang.like); // [ 'coding', 'game', 'sport' ]

console.log(afeng.getName === fang.getName); // false. 方法被重複建立了
           

缺點:引用類型屬性會被共享。

6.寄生組合式繼承(僞類繼承)

基本思想:主要是為了解決組合繼承中兩次調用父類構造函數。我們并不是非要父類的執行個體等于子類的原型,才能實作原型鍊的繼承。 我們需要的是子類的原型和父類原型建立起關系。

function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
    console.log(this.name);
}

function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}
Child.prototype = Object.create(Parent.prototype, {
    constructor: {
        configurable: true,
        enumerable: false,
        value: Child,
        writable: true
    }
});
// 原型一定要先改變,然後再添加。
Child.prototype.getAge = function() {
    console.log(this.age);
}

var child1 = new Child('afeng', 18);
var child2 = new Child('yafeng', 18);

child1.colors.push('pink');
console.log(child1.colors);

console.log(child2.colors);
           

這種方式的高效率展現在隻調用一次Parent構造函數,并且是以避免了在Child.prototype上面建立不必要的,多餘的屬性。同時,原型鍊還能保持不變。

7.混入式繼承

定義:一個對象在不改變原型對象的情況下得到另一個對象的屬性被稱為混入。

基本思想:

function minix(receiver, supplier) {
    // 檢視浏覽器是否支援ES5
    if(Object.getOwnPropertyDescriptor) {
        // Object.keys擷取到的是自有的可枚舉屬性
        Object.keys(supplier).forEach(function(property) {
            // 得到屬性描述對象
            var desc = Object.getOwnPropertyDescriptor(supplier, property);
            // 設定給同名的接收者
            Object.defineProperty(receiver, property, desc);
        });
    } else {
        for(var property in supplier) {
            if(supplier.hasOwnProperty(property)) {
                receiver[property] = supplier[property];
            }
        }
    }
    return receiver;
}
           

這其實也是一種增強對象的手段。

8.ES6的extends

繼續閱讀