实现继承的几种方式

174 阅读2分钟

ES5的方式:

方式一:通过call调用父类构造函数

缺点:1. 该方法无法继承父类原型上的属性; 2. 父类构造函数多次调用,造成性能损失。

function Parent(name, age) {      
  this.name = name;      
  this.age = age;    
}    
function Child(name, age, sex) {      
  Parent.call(this, name, age);      
  this.sex = sex;    
}    
var c1 = new Child('c1', 18, 'male');    
Parent.prototype.a = 'a';    
console.log(c1); // {name: "c1", age: 18, sex: "male"}


方式二:通过父类原型继承

为了解决方式一的问题,将子类的原型对象直接修改为与父类原型一致,这样父类原型对象上的属性和方法子类也能继承。

function Parent(name, age) {      
  this.name = name;      
  this.age = age;    
}    
function Child(name, age, sex) {      
  this.name = name;      
  this.age = age;      
  this.sex = sex;    
}    
Child.prototype = Parent.prototype;    
Parent.prototype.b = 'b'; // 给父类原型添加一个属性        
var c1 = new Child('c1', 18, 'male');    
console.log(c1); (1)    
Child.prototype.a = 'a'; (3) // 给子类原型添加属性    
var p1 = new Parent('p1', 38);    
console.log(p1); (2)

(1)和(2)的执行结果如下:


由上图可以看出,子类确实继承到了父类的原型上的属性b,但是(3)处,当子类的原型属性被修改时,父类的原型属性也发生了变化,这显然是不行的,因为父类被添加上了原本不需要的属性。

方式三:通过new父类构造函数的方式

function Parent(name, age) {      
  this.name = name;      this.age = age;    
}    
function Child(name, age, sex) {      
  this.name = name;      
  this.age = age;      
  this.sex = sex;    
}    
Child.prototype = new Parent();    
Parent.prototype.b = 'b'; // 给父类原型添加一个属性        
var c1 = new Child('c1', 18, 'male');    
console.log(c1); (1)    
Child.prototype.a = 'a'; // 给子类原型添加属性    
var p1 = new Parent('p1', 38);    
console.log(p1); (2)
console.log(Child.prototype.constructor); // Parent(name, age){ ... }

(1) 和 (2)处的执行结果如下:


这种方法的第一个缺点是Child的构造函数指向了Parent(){};第二是如果父类的属性是引用类型,修改子类的某个实例的该属性时,其他实例都会受到影响,如下:

function Parent(name, age) {      
  this.name = name;      
  this.age = age;      
  this.play = [1, 2, 3];    
}    
function Child(name, age, sex) {      
  this.name = name;      
  this.age = age;      
  this.sex = sex;    
}    
Child.prototype = new Parent();        
var c1 = new Child('c1', 18, 'male');    
console.log(c1.play); // [1, 2, 3]    
var c2 = new Child('c2', 18, 'male');    
console.log(c2.play); // [1, 2, 3]    
c2.play.push(4);    
console.log(c1.play); // [1, 2, 3, 4]    
console.log(c2.play); // [1, 2, 3, 4]

方式四:组合继承方式

function Parent(name, age) {      
  this.name = name;      
  this.age = age;      
  this.play = [1, 2, 3];    
}    
function Child(name, age, sex) {      
  Parent.call(this, name, age);      
  this.sex = sex;    
}    
Child.prototype = Parent.prototype;    
Child.prototype.constructor = Child; // 修正构造函数指向        
var c1 = new Child('c1', 18, 'male');    
console.log(c1.play); // [1, 2, 3]    
var c2 = new Child('c2', 18, 'male');    
console.log(c2.play); // [1, 2, 3]    
c2.play.push(4);    
console.log(c1.play); // [1, 2, 3]    
console.log(c2.play); // [1, 2, 3, 4]

这种方案是用得比较多的一种方式,不过依然存在修改子类原型对象,父类原型对象也会受到影响的问题。

方式五:组合继承优化1

let Parent = (function () {      
  function Parent (name) {        
    this.name = name || 'Parent';        
    this.a = 'a';        
    this.sleep = function(){          
    console.log(this.name + '正在睡觉!');        
  }      
}      
Parent.prototype.eat = function(food) {        
  console.log(this.name + '正在吃:' + food);      
}      
return Parent;    
})()        
let Child = (function () {      
function Child(name){        
Parent.call(this, name);      
}      
(function(sub, sup){        
  var Super = function(){};        
  Super.prototype = sup.prototype;        
  sub.prototype = new Super();        
  sub.prototype.constructor = sub      
})(Child, Parent);      
Child.prototype.b = 'b';      
return Child;    
})()

这种方式是比较完美的继承方式,但是依然存在多次调用父类构造函数的问题。