JS继承机制总结

689 阅读6分钟
  • 继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
  • JS继承机制主要为原型链继承、构造函数继承、组合继承、寄生继承、寄生组合继承、原型式继承和混合式继承。关于es6提出的class继承本文不做阐述,会另做一篇文章进行分析总结。
  • 本文主要是做总结,各个继承方式的内容不做特别详细的说明。

原型链继承

继承规则

Child.prototype = new Parent() //将子类的原型指向父类的实例

代码实例

//定义父类,并在父类中定义属性与方法
function Parent() {
    this.name = 'zs'
    this.age = 18
    this.arr = [1, 2]
    this.sing = function () {
        console.log(this.name);
    }
}
//通过原型方式给父类添加属性与方法
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
    console.log(this.sex);
}
​
//定义子类,在子类中定义相同的属性和方法,在原型链继承后可以覆盖父类的属性与方法。
function Child(name) {
    this.name = name
}
​
//进行原型链继承
Child.prototype = new Parent()
Child.prototype.num = 15//将子类实例化
let zdh = new Child('zdh')
​
Child.prototype.nums = 15
​
zdh.sing()  //'zdh'
console.log(zdh.name);  //'zdh'
zdh.song() //maleconsole.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]console.log(zdh.num);//undefined
console.log(zdh.nums);//15let zxy = new Child('zxy')
console.log(zxy.name);//zxy
console.log(zxy.arr);//[1, 2 , 5]

优点

  • 继承了父类的模板,又继承了父类的实例

缺点

  • 无法实现多重继承
  • 创建子类时,无法向父类传参
  • 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响
  • 如果要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面

构造函数继承

继承规则

在子类构造函数内部使用`call或apply`来调用父类构造函数

代码实例

function Parent() {
    this.name = 'zs'
    this.age = 18
    this.arr = [1, 2]
    this.sing = function () {
        console.log(this.name);
    }
}
​
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
    console.log(this.sex);
}
​
function Child(name) {
    this.name = name
    Parent.call(this, ...arguments)//如果有同名的属性与方法会进行覆盖
}
​
//等同于
// function Child(name) {
//  this.name = name
//  this.name = 'zs'
//  this.age = 18
//  this.arr = [1, 2]
//  this.sing = function () {
//      console.log(this.name);
//  }
// }let zdh = new Child('zdh')
​
zdh.sing() //zs
console.log(zdh.name);//zs
zdh.song() //报错console.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
​
​
​
let zxy = new Child('zxy')
console.log(zxy.name);//zs
console.log(zxy.arr);//[1, 2]

优点

  • 解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数

缺点

  • 只能继承父类实例上的属性与方法,无法继承父类原型上的属性与方法。
  • 实例并不是父类的实例,只是子类的实例(子类相对于深拷贝了父类实例上的属性与方法)
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

(无法实现函数复用’父类构造函数中的某个函数可能只是一个功能型的函数,它不论被复制了多少份,输出的结果或者功能都是一样的,那么这类函数是完全可以拿来复用的。但是现在用了构造函数继承,由于它是复制了父类构造函数中的属性和方法,这样产生的每个子类实例中都会有一份自己各自的方法,可是有的方法完全没有必要复制,可以用来共用的,所以就说不能够「函数复用」。)

组合继承

继承规则

function Parent() {
    ...
}
function Child() {
    Parent.call(this, ...arguments)//构造函数继承
}
Child.prototype = new Parent() //原型链继承

代码实例

function Parent() {
    this.name = 'zs'
    this.age = 18
    this.arr = [1, 2]
    this.sing = function () {
        console.log(this.name);
    }
}
​
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
    console.log(this.sex);
}
​
function Child(name) {
    this.name = name
    Parent.call(this, ...arguments)//如果有同名的属性与方法会进行覆盖
}
​
Child.prototype = new Parent()
​
​
let zdh = new Child('zdh')
​
zdh.sing() //zs
console.log(zdh.name);//zs
zdh.song() //maleconsole.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
​
​
​
let zxy = new Child('zxy')
console.log(zxy.name);//zs
console.log(zxy.arr);//[1, 2]

优点

  • 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
  • 弥补了原型链继承中引用属性共享的问题
  • 可传参,可复用

缺点

  • 使用组合继承时,父类构造函数会被调用两次。
  • 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。

寄生式组合继承

继承规则

function Parent() {
    ...
}
function Child() {
    Parent.call(this, ...arguments)//构造函数继承
}   
Child.prototype = Object.create(Parent.prototype)

代码实例

function Parent() {
    this.name = 'zs'
    this.age = 18
    this.arr = [1, 2]
    this.sing = function () {
        console.log(this.name);
    }
}
​
Parent.prototype.sex = 'male'
Parent.prototype.song = function () {
    console.log(this.sex);
}
​
function Child(name) {
    this.name = name
    Parent.call(this, ...arguments)//如果有同名的属性与方法会进行覆盖
}
​
//Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype)
​
let zdh = new Child('zdh')
​
zdh.sing() //zs
console.log(zdh.name);//zs
zdh.song() //maleconsole.log(zdh.arr);//[1, 2]
zdh.arr.push(5)
console.log(zdh.arr);//[1, 2 , 5]
​
​
​
let zxy = new Child('zxy')
console.log(zxy.name);//zs
console.log(zxy.arr);//[1, 2]

优点

寄生组合继承算是ES6之前一种比较完美的继承方式吧。

它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。

所以它拥有了上述所有继承方式的优点:

  • 只调用了一次父类构造函数,只创建了一份父类属性
  • 子类可以用到父类原型链上的属性和方法
  • 能够正常的使用instanceOfisPrototypeOf方法

原型式继承

继承规则

//原型式继承的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。
function objcet (obj) {
    function F () {};
    F.prototype = obj;
    F.prototype.constructor = F;
    return new F();
}

代码实例

function objcet (obj) {
    function F () {};
    F.prototype = obj;
    F.prototype.constructor = F;
    return new F();
}
var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}
​
var guaiguai = create(cat)
var huaihuai = create(cat)
​
console.log(guaiguai)
console.log(huaihuai)
​
console.log(guaiguai.heart)
console.log(huaihuai.colors)

优点

  • 在不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。

缺点

  • 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
  • 谨慎定义方法,以免定义方法也继承对象原型的方法重名
  • 无法直接给父级构造函数使用参数

寄生式继承

继承规则

//寄生式继承也没啥东西的,它就是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
function createAnother (original) {
    var clone = Object.create(original);; // 通过调用 Object.create() 函数创建一个新对象
    clone.fn = function () {}; // 以某种方式来增强对象
    return clone; // 返回这个对象
}

代码实例

var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}
function createAnother (original) {
    var clone = Object.create(original);
    clone.actingCute = function () {
      console.log('我是一只会卖萌的猫咪')
    }
    return clone;
}
var guaiguai = createAnother(cat)
var huaihuai = Object.create(cat)
​
guaiguai.actingCute()
console.log(guaiguai.heart)
console.log(huaihuai.colors)
console.log(guaiguai)
console.log(huaihuai)

优点

  • 再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。

缺点

  • 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
  • 谨慎定义方法,以免定义方法也继承对象原型的方法重名
  • 无法直接给父级构造函数使用参数

混合式继承

继承规则

//这个**混入方式继承**其实很好玩,之前我们一直都是以一个子类继承一个父类,而**混入方式继承**就是教我们如何一个子类继承多个父类的。
//在这边,我们需要用到`ES6`中的方法`Object.assign()`。
//它的作用就是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的会覆盖前面。(当然,这种拷贝是一种浅拷贝啦)
function Child () {
    Parent.call(this)
    OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child

代码实例

function Parent(sex) {
    this.sex = sex
}
Parent.prototype.getSex = function () {
    console.log(this.sex)
}
function OtherParent(colors) {
    this.colors = colors
}
OtherParent.prototype.getColors = function () {
    console.log(this.colors)
}
function Child(sex, colors) {
    Parent.call(this, sex)
    OtherParent.call(this, colors) // 新增的父类
    this.name = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype) // 新增的父类原型对象
Child.prototype.constructor = Childvar child1 = new Child('boy', ['white'])
child1.getSex() //boy
child1.getColors() //[ 'white' ]
console.log(child1)//Child { sex: 'boy', colors: [ 'white' ], name: 'child' }

引用

# 💦【何不三连】做完这48道题彻底弄懂JS继承(1.7w字含辛整理-返璞归真)