Skip to content

Javascript 6大继承 最优是寄生组合继承

原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着三角的关系,即每一个构造函数都有一个原型对象「prototype」,原型对象又包含一个指向构造函数的指针「constructor」,然后实例则包含一个隐式原型对象的指针「__ proto __ 称为隐式原型我想做一层区分而已帮助理解,抱歉因为Markdown语法关系,__ proto __ 存在空格」。

示例代码:

ts
function Parent(){
  this.name='parent'
  this.list=[1,2,3,4]

}

function Child(){
  this.type='child'
}
Child.prototype=new Parent()

const c=new Child()
const c1=new Child()
c1.list.push(5)
console.log(c.name) //parent
console.log(c.list) //[1,2,3,4,5]
console.log(c1.list) //[1,2,3,4,5]

WARNING

无法实现多继承

构造函数继承 借助 call/apply

ts
  function Parent(name){
  this.name=name
  this.list=[1,2,3,4]

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

function Child(name){
  this.type='child'
  Parent.call(this,name)
}

const c=new Child('c')
console.log(c.name) //c
console.log(c.getName()) //报错

除了 Child2 的属性 type 之外,也继承了 Parent2 的属性 name。这样写的时候子类虽然能够拿到父类的属性值,解决了第一种继承方式的弊端,但问题是,只能继承父类的实例属性和方法,不能继承原型属性或者方法。

WARNING

无法继承父类的prototype

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

组合继承

ts
function Parent(name) {
  this.name = name
  this.list = [1, 2, 3, 4]

}
Parent.prototype.getName = function () {
  return this.name
}
function Child(name) {
  this.type = 'child'
  // 第2次调用 Parent()
  Parent.call(this, name)
}
// 第一次调用 Parent()
Child.prototype = new Parent()
Child.prototype.constructor = Child //构造函数指向自己
var s3 = new Child('s3');
var s4 = new Child('s4');
s3.list.push(5)
console.log(s3.list, s4.list);  // 不互相影响
console.log(s3.getName()); // 正常输出'c'
console.log(s4.getName()); // 正常输出'c'

又增加了一个新问题:通过注释我们可以看到 Parent3 执行了两次,第一次是改变Child3 的 prototype 的时候,第二次是通过 call 方法调用 Parent3 的时候,那么 Parent3 多构造一次就多进行了一次性能开销,这是我们不愿看到的。 ​ 这里还有一个比较细节的问题是第二次调用的Parent3,出现了属性在不同层级重复,Parent3的age也会在实例第一层对象上面,拥有这个“多余的”属性也按照原型链的规则,没什么问题。但在某些情况下会造成错误,例如删除实例上的age属性后,实际上还能访问到,此时获取到的是原型上的属性。

原型式继承(利用Object.create)

ts
let parent4={
    name:'parent4',
    friends:["p1", "p2", "p3"],
    getName:function(){
        retrun this.name
    }
}
let person4 = Object.create(parent4)
  person4.name = "tom";
  person4.friends.push("jerry")

let person5 = Object.create(parent4)
 person5.friends.push("lucy")
console.log(person4.name); // tom
  console.log(person4.name === person4.getName()); // tom === tom ===> tom
  console.log(person5.name); // parent4
  console.log(person4.friends); // ["p1", "p2", "p3",'jerry','lucy']
  console.log(person5.friends); // ["p1", "p2", "p3",'jerry','lucy']

WARNING

这种继承方式弊端是拷贝对象引用,这种可能会导致对象被修改。

寄生式继承

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

ts

 let parent5 = {
    name: "parent5",
    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 person5 = clone(parent5);

  console.log(person5.getName());
  console.log(person5.getFriends());

寄生组合式继承

ts

function Parent() {
  this.name = 'parent6';
  this.play = [1, 2, 3];
}

function Child() {
  Parent.call(this)
  this.friends = 'child6';
}

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

clone(Parent, Child)
Child6.prototype.getFriends = function () {
  return this.friends;
}

let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());