五种实现JavaScript对象继承的方法

JavaScript是一门基于对象的语言,不是面向对象的语言。因为它没有自己的类(class)!所以对象继承的实现就尤为重要!!!

一、原型链继承

核心: 将父类的实例作为子类的原型

function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
}

function SubType() {
    this.subproperty = false;
}

SubType.prototype = new SuperType(); 

SubType.prototype.getSubValue = function() {
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue()); // true

缺点:

  1. 使用原型创建对象会存在多个实例对引用类型的操作会被篡改的问题,也就是来自原型对象的所有属性被所有实例共享。

  2. 创建子类实例时,无法向父类构造函数传参

二、借用构造函数继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}

function SubType(name, age){
  // 继承自SuperType
  SuperType.call(this, name);
  
  this.age = age;
}

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"

alert(instance1.name); // "Nicholas"
alert(instance1.age); // 29

缺点:

  1. 实例并不是父类的实例,只是子类的实例。只能继承父类的实例属性和方法,不能继承原型属性/方法
  2. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

三、组合继承

核心:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。(原型链方法+构造函数方法)

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

function SubType(name, age){
  //构造函数
  SuperType.call(this, name);
  this.age = age;
}

// 原型链继承方法
SubType.prototype = new SuperType(); 
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

缺点:

  • 调用了两次父类构造函数,生成了两份实例。子类实例将子类原型上的那份屏蔽了。
  • (第一次调用SuperType():给SubType.prototype写入两个属性name,color。第二次调用SuperType():给instance1写入两个属性name,color。)

四、原型式继承

核心:利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数

五、寄生组合式继承

核心:通过寄生方式,砍掉父类的实例属性,结合借用构造函数传递参数实现继承

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
  prototype.constructor = subType;                    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  subType.prototype = prototype;                      // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

// 新增子类原型属性
SubType.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

这是目前这好的一种实现方式,除了实现较为复杂就没有缺点。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章