JS继承方式总结

JS继承方式总结

原型链继承

  • 核心:子构造函数.prototype == 父类实例
    • 优点:复用了父类构造函数的方法
  • 缺点:
    • 创建子类实例时,不能传父类的参数
    • 子类实例共享了父类构造函数的引用属性
    • 无法实现多继承
  • 演示:
// 将属性封装到构造函数上(强调私有)
function Parent(name) {
    this.name = name || '父亲'
    this.arr = [1]
}
// 将需要复用、共享的方法定义在⽗类原型上
Parent.prototype.say = function () { 
    console.log('hello')
}

// 子类构造函数
function Child(like) {
    this.like = like
}


// 核心,但此时 Child.prototype.constructor == Parent
Child.prototype = new Parent() 
// 修正constructor指向
Child.prototype.constructor = Child


let boy1 = new Child()
let boy2 = new Child()
// 优点:共享了⽗类构造函数的say⽅法
console.log(boy1.say(), boy2.say(), boy1.say === boy2.say) // hello,hello,true
// 缺点1:不能向⽗类构造函数传参
console.log(boy1.name, boy2.name, boy1.name === boy2.name) // ⽗亲,⽗亲,true
// 缺点2:子类实例共享了父类构造函数的引用属性
// 修改了boy1的arr属性,boy2的arr属性,也会变化,因为两个实例的原型上(Child.prototype)有了⽗类构造函数的实例属性arr;所以只要修改了boy1.arr,那么boy2.arr的属性也会变化。
boy1.arr.push(2)
console.log(boy2.arr) // [1,2]

注意1: 修改boy1的name属性, 是不会影响到boy2.name。 因为设置boy1.name相当于在子类实例新增了name属性。
注意2:
console.log(boy1.constructor); // Parent 你会发现实例的构造函数居然是Parent。而实际上,我们希望子类实例的构造函数是Child,所以要记得修复构造函数指向。
修复如下: Child.prototype.constructor = Child;

构造函数继承

  • 核心:子构造函数内部用 apply 或者 call 方法调用父类构造函数

  • 优点:

    • 可以向父类构造函数传参数
    • 子类实例之间相互独立,不共享父类构造函数的引用属性
    • 可以实现多继承(通过多个 call 或者 apply 继承多个父类)
  • 缺点

    • 父类的方法不能复用

      由于方法在父构造函数中定义,导致方法不能复用,每次创建子类实例都要创建一遍方法

    • 子类实例继承不了父类原型上的属性和方法(因为没有用到原型)

  • 演示:

function Parent(name) {
    this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
    this.arr = [1]; // (该属性,强调私有)
    this.say = function () { // 实例引用属性 (该属性,强调复用,需要共享)
        console.log('hello')
    }
}


function Child(name, like) {
    Parent.call(this, name) // 核心 拷贝了⽗类的实例属性和⽅法
    this.like = like;
}


let boy1 = new Child('⼩红', 'apple')
let boy2 = new Child('⼩明', 'orange')
// 优点1:可向父类构造函数传参
console.log(boy1.name, boy2.name) // ⼩红,⼩明
// 优点2:不共享父类构造函数的引用属性
boy1.arr.push(2)
console.log(boy1.arr, boy2.arr) // [1,2] [1]
// 缺点1:方法不能复用
console.log(boy1.say === boy2.say) // false (说明,boy1 和 boy2 的say 方法是独立的,不是共享的)
// 缺点2:不能继承父类原型上的方法
// 在⽗类的原型对象上定义一个 walk ⽅法。
Parent.prototype.walk = function () { 
	console.log('我会走路')
}
boy1.walk; // undefined (说明,实例不能获得父类原型上的方法)

组合继承

  • 核心:结合原型链继承和构造函数继承

  • 优点:

    • 保留了构造函数继承的优点:创建子类实例,可以向父类构造函数传参数
    • 保留原型链的优点:父类的方法定义在父类的原型对象上,可以实现方法的复用
    • 不共享父类的引用属性
  • 缺点

    • 调用了两次父类的构造函数,会存在一份多余的父类实例属性(一次是在创建子类型原型的时候,另一次是在子类型构造函数内部)
  • 演示:

// 实例基本属性 (强调私有,不共享)
function Parent(name) {
    this.name = name
    this.arr = [1]
}
Parent.prototype.say = function () { // --- 将需要复用、共享的方法定义在⽗类原型上
    console.log('hello')
}

// 核心
function Child(name, like) {
    Parent.call(this, name, like) // 第二次调用
    this.like = like;
}
Child.prototype = new Parent() // 第一次调用
Child.prototype.constructor = Child // 修正 constructor 指向


let boy1 = new Child('⼩红', 'apple')
let boy2 = new Child('⼩明', 'orange')
// 优点1:可以向父类构造函数传参数
console.log(boy1.name, boy1.like); // ⼩红,apple
// 优点2:可复用父类原型上的方法
console.log(boy1.say === boy2.say) // true
// 优点3:不共享父类的引用属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr,boy2.arr); // [1,2] [1] 可以看出没有共享 arr 属性
// 缺点:由于调用了2次父类的构造方法,会存在一份多余的父类实例属性

组合继承优化

  • 核心:让子类构造函数的 prototype 指向父类构造函数的 prototype
  • 优点
    • 只调用一次父类的构造函数
    • 可以向父类构造函数传参
    • 将父类的实例方法定义在原型身上,可以实现函数复用
  • 缺点
  • 演示:
// 实例基本属性 (强调私有,不共享)
function Parent(name) {
    this.name = name;
    this.arr = [1];
}
// 需要复用、共享的方法定义在父类原型上
Parent.prototype.say = function() { 
    console.log('hello')
}


function Child(name,like) {
    Parent.call(this,name,like) // 核心
    this.like = like;
}
Child.prototype = Parent.prototype // 核心 子类原型和父类原型,实质上是同一个
<!--这里是修复构造函数指向的代码-->
Child.prototype.constructor = Child


// 优点1:可以向⽗类构造函数传参数
let boy1 = new Child('⼩红','apple')
let boy2 = new Child('⼩明','orange')
let p1 = new Parent('爸爸')
console.log(boy1.name,boy1.like); // ⼩红,apple
// 优点2:可复用父类原型上的⽅法
console.log(boy1.say === boy2.say) // true
// 缺点:当修复子类构造函数的指向后,父类实例的构造函数指向也会跟着变了。
//没修复之前:console.log(boy1.constructor); // Parent
//修复代码:Child.prototype.constructor = Child
//修复之后:
console.log(boy1.constructor); // Child
console.log(p1.constructor);// Child 这里就是存在的问题(我们希望是Parent)

具体原因:因为这种方式是通过原型来实现继承的,Child.prototype的上面是没有constructor 属性的,构造函数上找不到就会往原型链上找,这样就找到了Parent.prototype上面的 constructor 属性;因此当修改了子类实例的 construtor属性,所有的 constructor 的指向都会发生变化。

寄生组合继承

  • 核心:利用 Object.create(object,[propertiesObject])
    • Object.create() 方法创建⼀个新对象,使用第一个参数来提供新创建对象的__proto__(以第一个参数作为新对象的构造函数的原型对象); 方法还有第二个可选参数,是添加到新创建对象的属性
  • 最完美的继承方式
  • 演示:
// 实例基本属性(强调私有,不共享)
function Parent(name) {
    this.name = name;
    this.arr = [1];
}
// --- 将需要复用、共享的方法定义在父类原型上
Parent.prototype.say = function() { 
    console.log('hello')
}


function Child(name,like) {
    Parent.call(this, name, like)  // 核心
    this.like = like;
}
// 核心 通过创建中间对象,子类原型和父类原型就会隔离开。有效避免了⽅式4的缺点。
Child.prototype = Object.create(Parent.prototype)
// 修复构造函数指向
Child.prototype.constructor = Child


let boy1 = new Child('⼩红','apple')
let boy2 = new Child('⼩明','orange')
let p1 = new Parent('⼩爸爸')

代码参考自blibili视频,有修改,侵权删

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值