《JS核心原理精讲》笔记(三)探究 JS 常见的6种继承方式

继承概念的探究

继承可以使得子类别具有父类的各种方法和属性。
在这里插入图片描述

继承的方法

第一种:原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例三者之间的关系:

  • 每个构造函数都有一个原型对象
  • 原型对象又包含一个指向构造函数的指针
  • 实例则包含一个原型对象的指针
function P1() {
    this.name = "p1";
    this.play = [1, 2, 3];
}
function C1() {
    this.type = 'c1';
}
C1.prototype = new P1();
console.log(new C1());

在这里插入图片描述
虽然父类的属性和方法都能够访问,但其实有一个潜在的问题,看下面例子:

var s1 = new C1();
var s2 = new C1();
s1.play.push(4);
console.log(s1.play, s2.play);

在这里插入图片描述
明明只改变了 s1 的 play 属性,但 s2 的也跟着变了?
原因很简单,因为两个实例使用的是同一个原型对象,他们的内存空间是共享的,但一个发生变化的时候,另外一个也随之进行了变化,这就是使用原型链继承的缺点,那么看看下面解决原型链继承内存共享的方法。

第二种:构造函数继承(借助 call )

function P2() {
    this.name = 'p2';
}

P2.prototype.getName = function () {
    return this.name;
}

function C2() {
    P2.call(this);
    this.type = 'c2';
}

let c = new C2();
console.log(c);
console.log(c.getName());

在这里插入图片描述
可以看到,除了C1的type属性以外,也继承了P1的属性name,这样写的时候,子类虽然能拿到父类的属性值,解决了第一种继承方式的弊端,但问题是父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法,这种情况下,就会出现控制台报错的结果。

构造函数实现继承的优缺点,他使父类的引用属性不会被共享,优化了第一种继承方式的弊端,但随之而来的缺点也比较明显,只能继承父类的实例、属性和方法, 不能继承原型属性或方法。

上面的两种继承方式各有优缺点,那么结合二者的有点,就产生了下面这种组合方式。

第三种:组合继承(前两种组合)

function P3() {
    this.name = "p3";
    this.play = [1, 2, 3];
}

P3.prototype.getName = function () {
    return this.name;
}

function C3() {
    //第二次调用P3()
    P3.call(this);
    this.type = 'c3';
}

//第一次调用P3()
C3.prototype = new P3();
C3.prototype.constructor = C3;
var s3 = new C3();
var s4 = new C3();
s3.play.push(4);
console.log(s3.play, s4.play);
console.log(s3.getName());
console.log(s4.getName());

在这里插入图片描述
可以从结果看出之前方法一和方法二的问题得以解决,但是这里又增加了一个新问题,通过注释,我们可以看到 P3 执行了两次,第一次是改变 C3 的prototype 时,第二次通过 call 调用 P3 时,那么 P3 多构造一次,就多进行了一次性能开销,这是我们不愿看到的。

下面的第六种方法继承能更好的的解决这个问题。

上面介绍更多的是围绕构造函数的方式,那么对于JS普通对象,怎么实现继承呢?

第四种:原型式继承 Object.create()

该方法接受两个参数,一是用作新对象原型的对象,二是新对象定义额外属性的对象(可选参数)

let p4 = {
    name: "p4",
    friends: ["p1", "p2", "p3"],
    getName:function () {
        return this.name;
    }
}

let c4 = Object.create(p4);
c4.name = "c4";
c4.friends.push("jerry");

let c5 = Object.create(p4);
c5.friends.push("lucy");

console.log(c4.name);
console.log(c4.name === c4.getName());
console.log(c5.name);
console.log(c4.friends);
console.log(c5.friends);

在这里插入图片描述
缺点:多个实例的引用类型属性指向相同内存,存在篡改的可能。

第五种:寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法。
虽然优缺点和原型式继承一样,但对于普通对象,寄生式继承相比于原型式继承还是在父类基础上添加了更多的方法。

let p5 = {
    name: "p5",
    friends: ["p1", "p2", "p3"],
    getName: function () {
        return this.name;
    }
};

function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function () {
        return this.friends;
    };
    return clone;
}

let c5 = clone(p5);
console.log(c5.getName());
console.log(c5.getFriends());

在这里插入图片描述
可以看到,c5 通过 clone() 方法,添加了 getFriends() 方法,从而使 c5 这个普通对象在继承过程中又增加了一个方法,这样的继承方式就是寄生式继承。

第六种:寄生组合式继承

在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式。

function clone(parent, child) {
    //这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}

function P6() {
    this.name = 'p6';
    this.play = [1, 2, 3];
}
P6.prototype.getName = function () {
    return this.name;
}

function C6() {
    P6.call(this);
    this.friends = 'c6';
}

clone(P6, C6);

C6.prototype.getFriends = function () {
    return this.friends;
}

let c6 = new C6();
console.log(c6);
console.log(c6.getName());
console.log(c6.getFriends());

在这里插入图片描述

总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值