继承-原型链

198 阅读4分钟

简介

ECMAScript中的集成是实现继承,主要是依靠原型链实现的。

基本思想

利用原型让一个引用类型继承另一个引用类型的属性和方法。

构造函数、原型对象、实例的关系

每个构造函数都有一个原型对象;

原型对象都包含一个指向构造函数的指针;

实例都包含一个指向原型对象的内部指针。

继承的实现

让原型的对象等于另一个类型的实例

本质:重写原型对象

默认原型:所有函数的默认原型都是Object实例

原型链的尽头Object.prototype.__proto__ === null new Object().__proto__.__proto__ === null

原型链继承

// 原型链继承(实践中很少单独使用)
// 重写原型对象
// 问题:1、包含引用类型的原型属性会被所有实例共享。2、在创建子类型的实例时,不能像超类构造函数传参。

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

Parent.prototype.getPropertyValue = function () {
  return this.property;
}

function Child() {
  this.childProperty = false;
}

// 继承Parent
Child.prototype = new Parent();

var instance = new Child();
console.log(instance.getPropertyValue());
console.log(instance.childProperty);

借用构造函数

// 借用构造函数,又叫额日早对象或经典继承
// 在子类型的构造函数内部调用超类型构造函数
// 问题:方法都在构造函数中定义,函数无法复用。

function Parent() {
  this.friends = ['Tom', 'Flare']
}

Parent.prototype.getPropertyValue = function () {
  return this.property;
}

function Child() {
  // 继承Parent
  Parent.call(this)
}

var instance1 = new Child();
instance1.friends.push('Jim');
console.log(instance1.friends); // [ 'Tom', 'Flare', 'Jim' ]
var instance2 = new Child();
console.log(instance2.friends); // [ 'Tom', 'Flare' ]

组合继承 (最常用的继承方式)

// 组合继承(原型链 + 构造函数)
// 使用原型链实现对原型属性和方法的集成,通过构造函数实现对实例属性的继承。
// 最常用的继承模式,可以通过instanceof和isPrototypeOf()识别对象类型。
function Parent(name) {
  this.name = name;
  this.friends = ['Tom', 'Flare']
}

Parent.prototype.sayName = function () {
  return this.name;
}

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

// 继承方法
Child.prototype = new Parent();
// 修改原型对象的constructor
Child.prototype.constructor = Child;

Child.prototype.sayAge = function () {
  return this.age;
}

const instance = new Child('flare', 18);
instance.friends.push('123')
console.log(instance.friends);
console.log(instance.sayName());
console.log(instance.sayAge());

const instance1 = new Child('flare2', 22);
console.log(instance1.friends);
console.log(instance1.sayName());
console.log(instance1.sayAge());

原型式继承

// 原型式继承
// 借助原型可以根据已有对象创建新对象,同时还不比因为创建自定义类型
// 引用类型的值会共享

// 本质:object() 对传入的对象进行了浅复制
function object(o) {
  function F() {}; // 临时构造函数
  F.prototype = o; // 传入对象作为构造函数的原型
  return new F(); // 返回这个临时类型的新实例
}

const person = {
  name: 'Flare',
  friends: ['Tom', 'Jerry']
}

var anotherPerson = object(person);
anotherPerson.name = 'Flare2'
anotherPerson.friends.push('Bob');

var anotherPerson2 = object(person);
anotherPerson2.name = 'Flare3'
anotherPerson2.friends.push('Jack');

console.log(anotherPerson.name);
console.log(anotherPerson2.name);
console.log(person.friends); // 引用类型值会共享

// es5通过新增Object.create()规范了原型式继承
const p = Object.create(person, {
  name: {
    value: 'creacteName'
  }
});
console.log(p.name, p.friends);

寄生式继承

// 寄生式继承
// 创建一个仅用于封装继承过程的函数。
// 不能做到函数复用
function object(o) {
  function F() {}; // 临时构造函数
  F.prototype = o; // 传入对象作为构造函数的原型
  return new F(); // 返回这个临时类型的新实例
}

function createAnother(original) {
  const clone = object(original);
  clone.sayHi = function () {
    console.log('Hi');
  }

  return clone;
}

const person = {
  name: 'Flare',
  friends: ['Tom', 'Jerry']
}

const anotherPerson = createAnother(person);
anotherPerson.friends.push('May')
anotherPerson.sayHi()
console.log(anotherPerson.friends);
const anotherPerson2 = createAnother(person);
console.log(anotherPerson2.friends);

寄生组合式继承(最好的继承方式)

// 寄生组合继承(最好的继承方式)
// 组合继承的缺点:无论什么情况下,都会调用两次超类型的构造函数;一次是在创建子类型原型的时候;一次是在子类构造函数内部。
// 寄生组合继承:借用构造函数来继承属性,通过原型链混成形式来继承方法。
// 思路:不必为了子类型的原型而调用超类的构造函数,我们所需要的其实就是超类型原型的一个副本。

// 本质:object() 对传入的对象进行了浅复制
function object(o) {
  function F() {}; // 临时构造函数
  F.prototype = o; // 传入对象作为构造函数的原型
  return new F(); // 返回这个临时类型的新实例
}

function inheritPrototype(subType, superType){
  const prototype = object(superType.prototype); // 创建对象
  prototype.constructor = subType; // 增强对象
  subType.prototype = prototype; // 指定对象
}

function SuperType(name) {
  this.name = name;
  this.friends = ['Tom', 'Jack'];
}

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

const sub = new SubType('wang', 18);
sub.sayAge();
sub.sayName();