JavaScript 实现继承的几种方式

80 阅读3分钟

JavaScript 实现继承的几种方式

1. 原型链继承

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

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

//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.getSubValue = function () {
  return this.subproperty;
};

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

缺点:

  1. 包含引用类型值的原型属性会被所有实例共享,原型链上的属性为引用类型时,某个实例修改原型链上的属性会导致其他实例也会受到影响
  2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数

2. 借用构造函数继承

function SuperType(){
 this.colors = ["red", "blue", "green"];
}
function SubType(){
 //继承了 SuperType
 SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"

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

缺点:

  1. 无法复用函数,因为方法都在构造函数中定义(没有通过原型链引用)
  2. 在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式

3. 组合继承(原型链 + 借用构造函数)

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
 }
 SuperType.prototype.sayName = function(){
  console.log(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(){
  console.log(this.age);
 };

 var instance1 = new SubType("Nicholas", 29);
 instance1.colors.push("black");
 console.log(instance1.colors); //"red,blue,green,black"
 instance1.sayName(); //"Nicholas";
 instance1.sayAge(); //29
 
 var instance2 = new SubType("Greg", 27);
 console.log(instance2.colors); //"red,blue,green"
 instance2.sayName(); //"Greg

说明:组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式

缺点:

  1. 会调用两次超类型构造函数,导致实例和原型链上有两份相同的属性

4. 原型式继承

原型式继承适用于一个对象继承自另一个对象的情况,可以不必引入构造函数

ECMAScript5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象

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

var anotherPerson = Object.create(person)
anotherPerson.name = "Greg"
anotherPerson.friends.push("Rob")

var anotherPerson2 = Object.create(person)
anotherPerson2.name = "Linda"
anotherPerson2.friends.push("Barbie")
console.log(person.friends) // "Shelby,Court,Van,Rob,Barbie" 

在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的,不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样

5. 寄生式继承

寄生式继承是结合了原型式继承和工厂模式的一种方式,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。适用于通过函数返回一个增强了的对象。

function createAnother(original) {
  var clone = Object.create(original); // 通过调用函数创建一个新对象
  clone.sayHi = function () {  //以某种方式来增强这个对象
    console.log("hi");
  };
  return clone; //返回这个对象
}

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"],
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

缺点:

  1. 需要在构造函数中给每个对象添加函数,导致不能复用函数

6. 寄生 + 组合继承 (推荐)

寄生 + 原型链是引用类型最理想的继承范式推荐使用

es6 的 extends class 继承方式在编译后也是这种方式

function inheritPrototype(subType, superType) {
  subType.prototype = Object.create(superType.prototype)
  subType.prototype.constructor = subType
}

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

function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {
  console.log(this.age);
};

7. 混入式继承

function MyClass() {
  SuperClass.call(this);
  OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
  // do something
};

8. Class 继承 (推荐)

class Parent {
    constructor(name) {
        this.name = name
    }
    static sayHello() {
        console.log('hello')
    }
    sayName() {
        console.log('my name is ' + this.name)
        return this.name
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name)
        this.age = age
    }
    sayAge() {
        console.log('my age is ' + this.age)
        return this.age
    }
}

let parent = new Parent('Parent')
let child = new Child('Child', 18)

console.log('parent: ', parent) // parent:  Parent {name: 'Parent'}
Parent.sayHello() // hello
parent.sayName() // my name is Parent

console.log('child: ', child) // child:  Child {name: 'Child', age: 18}
Child.sayHello() // hello
child.sayName() // my name is Child
child.sayAge() // my age is 18

// 1、构造器原型链
Child.__proto__ === Parent // true
Parent.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true

// 2、实例原型链
child.__proto__ === Child.prototype // true
Child.prototype.__proto__ === Parent.prototype // true
Parent.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true