JavaScript六种继承方法

224 阅读3分钟

原型链继承

function Father() {
    this.name = 'jiangyao';
    this.food = ['rice', 'sandwich']
}
function Son() {}
Son.prototype = new Father()  //子类原型作为父类的实例
var son1 = new Son();  //子类实例son1
var son2 = new Son();  //子类实例son2
son1.name = 'zs';
son1.food.push('apple');
console.log(son2.name)  //jiangyao
console.log(son2.food)  //[ 'rice', 'sandwich', 'apple' ]

优点:

子类可以继承父类构造函数的方法及原型上的方法。

缺点:

1.某一子类更改父类的引用属性(上文food),其他子类也会受到影响;
2.子类在实例化时不能给父类的构造函数传参。

盗用构造函数

在子类构造函数中使用call()apply()方法继承父类

function Father(name) {
    this.name = name;
    this.food = ['rice', 'sandwich']
}
Father.prototype.say = function() {
    console.log('hello')
}
function Son() {
    Father.call(this'jiangyao')
    // Father.apply(this, ['jiangyao'])
    //call()与apply的区别是apply传参需要是数组形式
}
var son1 = new Son()
var son2 = new Son()
console.log(son1.name) //jiangyao
son1.food.push('single')
console.log(son1.food)  //[ 'rice', 'sandwich', 'single' ]
console.log(son2.food)  //[ 'rice', 'sandwich' ]
son1.say(); //son1.say is not a function 

优点:

1.某一子类更改父类的引用属性,不会影响其他子类;
2.在子类构造函数种可以向父类传参。

缺点:

子类不能继承父类原型上的属性。

组合继承

组合继承综合了原型链继承盗用构造函数继承,将两者的优点结合了。

function Father(name) {
    this.name = name;
    this.food = ['rice', 'sandwich']
}
Father.prototype.say = function() {
    console.log('hello')
}
function Son() {
    Father.call(this, 'jiangyao')  //盗用构造函数继承 第二次调用Father()
}
Son.prototype = new Father()  //原型链继承  第一次调用Father()
let son1 = new Son()
son1.say();   //hello

原型式继承

本质上,是对传入的对象执行了一次浅复制,此继承方式与原型链继承效果一样,属性中包含的引用值始终会在相关对象间共享,见下面例子
function object(obj) {
    function F() {}
    F.prototype = obj;
    return new F()
}
let person = {
    name: 'jiangyao',
    friends: ['Jy', 'Mary']
}
let anotherPerson = object(person)
anotherPerson.name = 'zs';
anotherPerson.friends.push('Jack')
console.log(anotherPerson.name)    //zs
let yetAnotherPerson = object(person)
yetAnotherPerson.name = 'lisi';
yetAnotherPerson.friends.push('Susan')
console.log(yetAnotherPerson.name)     //lisi
console.log(person.friends)    //[ 'Jy', 'Mary', 'Jack', 'Susan' ]

原型式继承适用于你有一个对象,你想在它的基础上再创建一个新对象。此方法不需要单独创建构造函数便可以实现对象间共享信息。ES5的Object.create()方法在只有第一个参数时,与这里的object()方法效果相同。

寄生式继承

与原型式继承比较接近的一种继承方式,以某种方式增强对象。
function object(obj) {
    function F() {}
    F.prototype = obj;
    return new F()
}
function createAnother(original) {
    let clone = object(original);    //通过调用函数创建一个新对象
    clone.sayHi = function() {      //以某种方式增强这个对象
        console.log('Hi');
    };
    return clone;    
}
let person = {
    name: 'jiangyao',
    friends: ['Jy', 'Mary']
}
let anotherPerson = createAnother(person);  //基于person对象返回的新对象
anotherPerson.sayHi();     //Hi 具有person所有的属性和方法,还有自己的新方法sayHi()

上文object()函数不是寄生式继承必需的,任何返回的新对象都可以在这里使用。

寄生式组合继承

组合继承其实也存在效率问题,最主要的效率问题就是父类构造函数始终会被调用两次:一次是在创建子类原型是调用,另一次是在子类构造函数中调用。本质上,子类原型最终是要包含超类对象所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。

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

function inheritPrototype(son, father) {
    let prototype = object(father.prototype); // 创建对象
    prototype.constructor = son;             // 增强对象
    son.prototype = prototype;               // 赋值对象
}

function Father(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'pink']
}
Father.prototype.sayName = function() {
    console.log(this.name);
}

function Son(name, age) {
    Father.call(this, name);
    this.age = age;
}

inheritPrototype(Son, Father);
Son.prototype.sayAge = function() {
    console.log(this.age);
}

let son1 = new Son('jy', 21)
son1.colors.push('green')
console.log(son1.name)    //jy
console.log(son1.age)     //21
console.log(son1.colors)    //[ 'red', 'blue', 'pink', 'green' ]

let son2 = new Son('jack', 22)
son2.sayAge();   //jack
son2.sayName();   //22
console.log(son2.colors)  //[ 'red', 'blue', 'pink' ]

在这里只调用了一次Father()构造函数,避免了Father.prototype上不必要也用不到的属性,因此可以说这个继承方法的效率更高。寄生式组合继承算是引用类型继承的最佳模式