JS常见的七种继承方式

93 阅读2分钟

JS常见的七种继承方式

继承概念的探究

继承可以使得子类具有父类的各种方法和属性

第一种:原型链继承

原型链是较常见的继承方式之一,其中涉及的构造函数、原型和实例对象

  • 每一个构造函数都有一个原型对象

  • 原型对象又包含一个指向构造函数的指针

  • 实例对象包含一个原型对象的指针

    function Father() {
      this.name = 'Tom'
      this.arr = [1,2,3]
    } 
    function Child() {
      this.age = 24
    }
    //继承父类的属性和方法 但会导致共享问题
    Child.prototype = new Father()
    const c1 = new Child() 
    const c2 = new Child()
    c1.arr.push(4)
    console.log(c1.arr); //[ 1, 2, 3, 4 ]
    console.log(c2.arr); //[ 1, 2, 3, 4 ]
    /**
    内存空间是共享的
    当一个发生变化的时候
    另外一个也随之进行了变化
    */
    

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

function Father() {
  this.name = 'Tom'
  this.arr = [1,2,3]
} 
Father.prototype.getName = function () {
  return this.name
}
function Child() {
  Father.call(this)
  this.age = 24
}
const c1 = new Child() 
const c2 = new Child()
c1.arr.push(4)
console.log(c1.arr); //[ 1, 2, 3, 4 ]
console.log(c2.arr); //[ 1, 2, 3]
//无法获取父类的方法
console.log(c1.getName()); //报错 c1.getName is not a function

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

function Father(name) {
  this.name = name
  this.arr = [1,2,3]
} 
Father.prototype.getName = function () {
  return this.name
}
function Child(name) {
  // 第二次调用Father
  Father.call(this,name)
  this.age = 24
}
// 第一次调用 Father
Child.prototype = new Father()
// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child
const c1 = new Child('Tom') 
const c2 = new Child('Jack')
c1.arr.push(4)
console.log(c1.arr); //[ 1, 2, 3, 4 ]
console.log(c2.arr); //[ 1, 2, 3]
console.log(c1.getName()); // 'Tom'
console.log(c2.getName()); // 'Jack'

第四种:原型式继承

Object.create()

  • 可用作新对象原型的对象

  • 为新对象定义额外的属性的对象

    用此方法实现继承对于对象属性为复杂数据类型(浅拷贝)时,会因为共享,导致不同的子类都能篡改其父类属性的数据

let parent4 = {
  name: 'Tom',
  friend: ['p1', 'p2', 'p3'],
  getName: function() {
    return this.name
  }
}

let person4 = Object.create(parent4)
person4.name = 'Jack'
person4.friend.push('p4')
console.log(person4.name);  // Jack
console.log(person4.getName());//[ 'p1', 'p2', 'p3', 'p4' ],
console.log(parent4.getName());//[ 'p1', 'p2', 'p3', 'p4' ],
console.log(person4.__proto__); 
/**
{
  name: 'Tom',
  friend: [ 'p1', 'p2', 'p3', 'p4' ],
  getName: [Function: getName]
}
*/

第五种:寄生式继承

该方法是使用原型式继承可以获得一份目标对象的浅拷贝

然后利用浅拷贝的能力再进行增强添加一些方法

let parent5 = {
  name: 'Tom',
  friend: ['p1', 'p2', 'p3'],
  getName: function() {
    return this.name
  }
}
function clone(original) {
  let clone = Object.create(original)
  clone.getFriends = function() {
    return this.friend
  }
  return clone
}

let person5 = clone(parent5)
console.log(person5.getName()); //Tom
person5.friend.push('p44')
console.log(parent5.friend); // [ 'p1', 'p2', 'p3', 'p44' ]
console.log(person5.friend); // [ 'p1', 'p2', 'p3', 'p44' ]

第六种:寄生组合式继承

function clone(parent, child) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
}

function Parent(name) {
  this.name = name
  this.age = 24
  this.friend = [1,2,3]
}
Parent.prototype.getName = function() {
  return this.name
}
function Child(name) {
  Parent.call(this,name)
  this.friend.push(4)
}
clone(Parent, Child)
Child.prototype.getFriend = function() {
  return this.friend
}
let child1 = new Child('Tom')
let fat = new Parent('Jack')
console.log(child1.name); // Tom
console.log(child1.getName());//  Tom
console.log(child1.friend); // [ 1, 2, 3, 4 ]
console.log(fat.name);// Jack
console.log(fat.getName());// Jack
console.log(fat.friend); //[ 1, 2, 3 ]
console.log(child1.getFriend());
console.log(fat.getFriend()); // 报错 fat.getFriend is not a function

第七种:ES6的 extends 关键字实现逻辑

class Parent {
  constructor(name) {
    this.name = name
    this.friend = [1,2,3]
  }
  getName = function() {
    return this.name
  }
}
class Person extends Parent {
  constructor(name, age) {
    //子类中存在构造函数,则需要在使用 this 之前,先调用super()
    super(name)
    this.age = age
  }
}
const per = new Person('Tom', 26)
per.friend.push(4, 5)
const fat = new Parent('Jack')
console.log(per.friend);// [ 1, 2, 3, 4, 5 ]
console.log(per.getName());// Tom
console.log(fat.friend);// [ 1, 2, 3 ]

总结

09.png