louzhedong / blog

前端基础,深入以及算法数据结构

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Javascript继承

louzhedong opened this issue · comments

Javascript的继承和传统的OO语言不一样,有一些特别的地方需要我们去注意。文章作为阅读红皮书(JS高程)的笔记,记录Javascript语言中的继承方式。

1.原型链继承
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;
}

const instance = new SubType();
instance.getSuperValue()  // 输出SuperType中的true

原型链继承有它的缺点,就是包含引用类型值的原型属性会被所有实例共享,如下面这个例子

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

function SubType() { }

SubType.prototype = new SuperType();
const instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors);  // "red, blue, green, black";
const instance2 = new SubType();
alert(instance2.colors);  // "red, blue, green, black";

这个例子中的colors就是一个会被所有实例共享的对象。

原型继承的另一个缺点是在创建子类的实例时,不能向超类的构造函数传递参数。

2.借用构造函数(经典继承)
function SuperType() {
  this.colors = ['red', 'blue', 'green'];
}

function SubType() {
	// 继承
    SuperType.call(this);
}

const instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors);  // "red, blue, green, black";
const instance2 = new SubType();
alert(instance2.colors);  // "red, blue, green";

通过SuperType.call(this)在SubType的对象上执行SuperType函数中定义的所有对象初始化代码。

并且借用构造函数还可以传递参数

function SuperType(name) {
  this.name = name;
}

function SubType() {
    // 继承
    SuperType.call(this, 'Michael');
    this.age = 25;
}

const instance = new SubType();
alert(instance.name);		// 'Michael'
alert(instance.age);		// 25

但借用改造函数继承也有它的缺点,即无法避免构造函数模式存在的函数复用的问题,因为所有的方法都在构造函数中定义。

3.组合继承(伪经典继承)

组合继承将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。

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;
}

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

const instance1 = new SubType('Michael', 25);
instance1.colors.push('black');
alert(instance1.colors);    // "red, blue, green, black"
instance1.sayName();    // "Michael"
instance1.sayAge();     // 25

const instance2 = new SubType('John', 27);
alert(instance2.colors);    // "red, blue, green"
instance2.sayName();    // "John"
instance2.sayAge();     // 27

组合继承解决了原型链与借用构造函数的缺陷,虽然它也有一定的问题,但它还是Javascript中最常用的继承模式。

4.原型式继承

原型式继承的**是基于已有的对象创建新的对象,并添加自己需要的其他属性,而不必创建自定义类型。

function object(o) {
    function F(){};
    F.prototype = o;
    return new F();
}
// 即对传入的对象执行了一次浅复制,ES6添加了Object.create()方法来规范这中继承方式
const person = {
    name: 'Michael',
    friends: ["aa", "bb"]
}
const anotherPerson = Object.create(person, {
    name: {
        value: 'John'
    }
});
alert(anotherPerson.name);		// "John"

这种继承方式的所有实例也都会共享引用属性的值。

5.寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象。

function createAnother(original) {
    const clone = new Object(original); // 这里可以换成其他自定义的生成对象的方法,如果是浅拷贝,则同样会共享引用类型的对象
    clone.sayHi = function() {
        alert("Hi");
    };
    return clone;
}

const person = {
    name: 'Michael',
    friends: ["aa", "bb"]
};
const anotherPerson = createAnother(person);
anotherPerson.sayHi();		// "Hi"
6.寄生组合式继承

前面所述的组合继承的最大问题就是会调用两次超类型构造函数,一次是创建子类原型的时候,另一次是在子类型构造函数内部。其结果是子类型的实例属性屏蔽了它的原型属性,原型上依然存在着相应的属性。

寄生组合式继承的基本思路是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型

function inheritPrototype(subType, superType) {
  // 创建超类的一个副本
  const prototype = new Object(superType.prototype);
  // 为创建的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性
  prototype.constructor = subType;
  //  将新创建的对象赋值给子类型的原型
  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);
};