对象 | 继承(含图解、手写)

本文详细探讨了JavaScript中的各种继承模式,包括原型链继承、盗用构造函数继承、组合式继承、原型式继承、寄生式继承和寄生式组合继承。分析了每种模式的优缺点,如原型链继承能复用父类方法但可能导致共享属性的修改影响所有子类,而寄生式组合继承解决了效率问题,确保子类既能复用父类方法又不会共享引用属性。文章最后进行了总结并提供了相关参考资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原型链继承

基本思想:通过原型链继承多个引用类型的属性和方法

我们知道,实例和原型是有直接联系的,实例可以通过__proto__访问原型,所以原型链继承即将父类的实例作为子类的原型,这样子类的原型就可以通过__proto__访问父类的原型了

function Father() {};

function Son() {};

// 继承Father
Son.prototype = new Father();

console.log(Son.prototype.__proto__ === Father.prototype);   // true

原型链继承
优点

  • 父类的方法子类能够被子类复用,因为子类可以访问父类原型,原型存在着父类实例可以共享的方法和属性

缺点

  • 更改一个子类的引用,其他子类也会受到影响
function Father() {};
Father.prototype.colors = ['red', 'black', 'yellow'];

function Son() {};
Son.prototype = new Father();

let p1 = new Son();
let p2 = new Son();

p1.colors[1] = 'pink';
console.log(p1.colors);    // ['red', 'pink', 'yellow']
console.log(p2.colors);    // ['red', 'pink', 'yellow']
  • 子类在实例化的时候不能向父类传参

盗用构造函数继承

基本思想:在子类构造函数中调用父类构造函数,可以使用call或者apply的方法

function Father() {
    this.colors = ['red', 'black', 'yellow'];
};

function Son() {
    // 继承Father
    Father.call(this);   // 通过此方法让子类继承父类中的方法和属性
};

let p1 = new Son();
let p2 = new Son();

console.log(p1.colors);    // ['red', 'black', 'yellow']
console.log(p2.colors);    // ['red', 'black', 'yellow']

盗用构造函数继承
优点

  • 可以在子类构造函数中向父类构造函数传参
function Father(name) {
    this.name = name;
};

function Son() {
    // 继承Father
    Father.call(this, 'Katrina');   // 通过此方法让子类继承父类中的方法和属性
};

let p1 = new Son();
console.log(p1.name);   // Katrina
  • 父类的引用属性不会被共享
function Father() {
    this.colors = ['red', 'black', 'yellow'];
};

function Son() {
    // 继承Father
    Father.call(this);   // 通过此方法让子类继承父类中的方法和属性
};

let p1 = new Son();
let p2 = new Son();

p1.colors[1] = 'pink';
console.log(p1.colors);    // ['red', 'pink', 'yellow']
console.log(p2.colors);    // ['red', 'black', 'yellow']

缺点

  • 必须在构造函数中定义方法,函数不能复用,每次创建都会初始化
function Father(name) {
 	this.colors = ['red', 'black', 'yellow'];
    this.name = name;
    this.sayHello = function() {
        console.log(`${this.name} say hello!`)
    };
}

每一次调用new Father,就会在实例内部定义一次这个sayHello方法,也就是new Function(console.log(${this.name} say hello!)),其实是更推荐把方法定义在Father.prototype上的,这样每个实例构造出来就自动继承这个方法,不需要在构造函数里面一次次地写

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

// 等价于
function Son(name) {
    this.colors = ['red', 'black', 'yellow'];
    this.name = name;
    this.sayHello = function() {
        console.log(`${this.name} say hello!`)
    };
}

也就是说在构造函数继承的时候也在一次次调用sayHello这个方法

  • (上个问题的延申)子类不能访问父类原型上的方法【instanceOf会失效】,所有方法都只能定义在父类构造函数中

组合式继承

基本思想:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性

function Father(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['Jack', 'Jenny', 'Lucy'];
}

Father.prototype.sayName = function() {
    console.log(this.name);
}

function Son(name, age) {
    // 构造函数继承
    Father.call(this, name, age);                 // 调用两次
};

// 原型链继承
Son.prototype = new Father();                     // 调用一次

let p1 = new Son('Katrina', 18);
let p2 = new Son('Kate', 20);

p1.sayName();   // 'Katrina'
p2.sayName();   // 'Kate'

p1.friends.push('Bob');
console.log(p1.friends);    // ['Jack', 'Jenny', 'Lucy', 'Bob']
console.log(p2.friends);    // ['Jack', 'Jenny', 'Lucy']

组合式继承
优点

  • 父类的方法子类能够被子类复用
  • 父类的引用属性不会被共享
  • 子类可以访问父类原型上的方法

缺点

  • 效率问题:父类构造函数始终会被调用两次
    • 创建子类原型时调用
    • 在子类构造函数中调用

原型式继承

基本思想:把现有的对象指定为构造函数的原型对象,并返回以这个对象为原型的构造函数的实例

适用于:你有一个对象,你想在这个对象的基础上再创建一个对象 以及 不需要单独创建构造函数,但仍需要在对象间共享信息的场合

function object(o) {
    function F(){};     // 临时构建构造函数
    F.prototype = o;    // 把传入的对象指定为
    return new F();     // 返回临时类型的实例
}


// 本质上,object是吧传入的o进行了一次浅复制
let person = {
    name: 'Katrina',
    age: 18,
    gender: 'female',
    friends: ['Jack', 'Jenny', 'Lucy'],
};

let anotherPerson = object(person);
anotherPerson.name;   // 'Katrina';
anotherPerson.friends.push('Kate');

let yetAnotherPerson = object(person);
yetAnotherPerson.name;   // 'Katrina';
yetAnotherPerson.friends.push('Bob');

console.log(person.friends);   // ['Jack', 'Jenny', 'Lucy', 'Kate', 'Bob']

ES5中新增的Object.create()只传一个参数时就是运用的这种思想:手写原理 | Object.create
原型式继承

优点

  • 父类方法可以复用

缺点

  • 更改一个子类的引用,其他子类也会受到影响
  • 子类在实例化的时候不能向父类传参

寄生式继承

基本思想:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象

适用于:主要关注对象,而不在乎类型和构造函数的场景

function object(o) {
    function F(){};     // 临时构建构造函数
    F.prototype = o;    // 把传入的对象指定为
    return new F();     // 返回临时类型的实例
} 


function createAnotherPerson(original) {
    let clone = object(original);    // 创建一个新对象,它的构造函数的原型是original
    clone.sayHi = function() {      // 给对象添加一个sayHi的方法
        console.log('Hi');
    };
    return clone;       // 返回这个对象
}

let person = {
    name: 'Katrina',
    age: 18,
    gender: 'female',
    friends: ['Jack', 'Jenny', 'Lucy'],
};

let anotherPerson = createAnotherPerson(person);
anotherPerson.sayHi();        // Hi

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

寄生式组合继承

寄生式组合继承主要是为了解决组合继承的效率问题

基本思想:不通过调用父类构造函数给子类原型赋值,而是取得父类函数的一个副本,即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型

function object(o) {
    function F(){};     // 临时构建构造函数
    F.prototype = o;    // 把传入的对象指定为
    return new F();     // 返回临时类型的实例
}

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




function Father(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['Jack', 'Jenny', 'Lucy'];
}

Father.prototype.sayName = function() {
    console.log(this.name);
}

function Son(name, age) {
    // 构造函数继承
    Father.call(this, name, age);                  // 调用一次   
};

inheritPrototype(Father, Son);

instanceof isPrototypeof方法正常有效
寄生式组合继承

总结

继承分类

参考

关于构造函数继承的缺点的一个疑问

一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值