JavaScript 中的继承


JavaScript 中的继承

1. 原型链继承

原型链的问题:

  • 原型中包含的引用值会在所有实例间共享
  • 子类型在实例化时不能给父类构造函数传参
function SuperType() {
  this.property = true;
  this.colors = ["red", "blue"];
}

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

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

// 继承 SuperType
SubType.prototype = new SuperType();

// 恢复constructor属性
// 这种方式会创建一个[[Enumerable]] 为 true 的属性,而原生 constructor 属性默认是不可枚举的
// SubType.prototype.constructor = SubType

// 使用 Object.defineProperty()方法来定义 constructor 属性
Object.defineProperty(SubType.prototype, "constructor", {
  enumerable: false,
  value: SubType,
});

// 新方法
SubType.prototype.getSubValue = function () {
  return this.subproperty;
};

// 覆盖已有方法
SubType.prototype.getSuperValue = function () {
  return false;
};

let instance1 = new SubType();
instance1.colors.push("black");
console.log("instance1.colors", instance1.colors);

let instance2 = new SubType();
console.log("instance2.colors", instance2.colors);

console.log(SubType.prototype.constructor == SubType);

2. 盗用构造函数 / 对象伪装 / 经典继承

优点:

  • 可以在子类构造函数中向父类构造函数传参;
  • 每个实力都可以有自己的属性

缺点:

  • 必须在构造函数中定义方法,因此函数不能重用
  • 子类也不能访问父类原型上定义的方法
function SuperType(name) {
  this.colors = ["red", "blue"];
  this.name = name;
  // this.getColors = function () {
  //   return this.colors
  // }
}

SuperType.prototype.getColors = function () {
  return this.colors
}

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

let instance1 = new SubType("James");
instance1.colors.push("yellow");
console.log(instance1.colors)
console.log(instance1.name);
console.log(instance1.age);

let instance2 = new SubType("Kobe");
console.log(instance2.colors);
// console.log(instance2.getColors())  // 访问父类原型上的方法会报错

3. 组合继承 / 伪经典继承

组合继承综合了原型链和盗用构造函数,将两者的优点集中起来。
基本思路是使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性,
这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性

缺点: 父类构造函数会执行两次

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

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

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

// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType

SubType.prototype.getAge = function () {
  return this.age;
};

let instance1 = new SubType("James", 31);
instance1.colors.push("yellow");
console.log(instance1.colors);
console.log(instance1.getName());
console.log(instance1.getAge());

let instance2 = new SubType("Kobe", 30);
console.log(instance2.colors);
console.log(instance2.getAge());
console.log(instance2.getName());

console.log(SubType.prototype.constructor == SubType)

4. 原型式继承

原型式继承适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。
属性中包含的引用值会始终在对象间共享

ES5 增加了 Object.create() 方法将原型式继承规范化了

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

let person = {
  name: "Nick",
  friends: ["Jane", "Aimee", "Tina"],
};

let anotherPerson = object(person);
anotherPerson.name = "Jay";
anotherPerson.friends.push("Jeff");

let yetAnother = object(person);
yetAnother.name = "Andy";
yetAnother.friends.push("Lee");

let thirdPerson = Object.create(person, {
    name: {
        value: 'Oli'
    }
})
thirdPerson.friends.push('Nash')

console.log(thirdPerson.name)
console.log(person.friends);

5. 寄生式继承

寄生式继承背后的思路类似于寄生构造函数和工厂模式:

创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象

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

// 参数original 是新对象的基准对象
function createAnthoer(original) {
  let clone = object(original);
  clone.sayHi = function () {
    console.log("Hi");
  };
  return clone;
}

let person = {
  name: "Jack",
  friends: ["Robin", "Andy"],
};

let anthoerPerson = createAnthoer(person);
anthoerPerson.sayHi();

6. 寄生式组合继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法

基本思路: 不必为了指定子类型的原型而调用超类型的构造函数,我们需要的仅是超类型原型的一个副本,

本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型

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

function inheritPrototype(subType, superType) {
  let prototype = object(superType.prototype); // 创建对象
  prototype.constructor = subType; // 增强对象
  subType.prototype = prototype; // 赋值对象
}

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

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

let sub = new SubType("Kobe", 35);
sub.sayName();
sub.sayAge();
console.log(sub.colors);
console.log(sub instanceof SuperType);
console.log(SubType.prototype.isPrototypeOf(sub));
console.log(SuperType.prototype.isPrototypeOf(sub));
console.log(Object.prototype.isPrototypeOf(sub));

 本篇
JavaScript 中的继承 JavaScript 中的继承
JavaScript 中的继承1. 原型链继承原型链的问题: 原型中包含的引用值会在所有实例间共享 子类型在实例化时不能给父类构造函数传参 function SuperType() { this.property = true;
下一篇 
函数科里化 函数科里化
函数科里化定义函数柯里化又叫部分求值,维基百科中对柯里化 (Currying) 的定义为 柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 实现func
  目录