前端知识学习

老生常谈:原型与原型链

首先,在 js 中每个对象都含有原型的引用,每个对象的原型也可以拥有一个原型,以此类推便形成了原型链。查找特定属性将会被委托到整个原型链上,当没有更多原型可以查找时,才会停止查找。

因此原型链在 ECMAScript 规范中作为实现继承的主要方式。但这种方式缺陷也十分明显:

  • 实例共享引用类型,易造成使用修改的混乱
  • 创建子类时无法传递参数。

使用构造函数

function Foo () {};

Foo.prototype.bar = function() {
  return true;
};

const foo = new Foo();
复制代码

使用 new 操作符调用该函数,便是作为构造器进行调用,创建了新的分配对象并将及设置为函数的上下文(可通过 this 访问)new 返回的结果是这个新对象的引用

缺陷

  • 所有方法都在构造函数中定义,无法做到函数复用,且使用超类定义方法时,该方法对子类也不可见。

原型链与构造函数组合继承

主要思路:通过原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,请看以下代码:

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

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SuperType(name, age) {
  // 继承属性
  SuperType.call(this, name);
  // 先继承,后定义新的自定义属性
  this.age = age
}

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

// 先继承,后定义新的自定义属性
Object.defineProperty(
  SubType.prototype,
  "constructor", {
    enumerable: false,
    value: SubType
});
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

var instance1 = new SubType("james",9);
instance1.colors.push("black");
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
复制代码

优点

  • instanceOf() / isPrototypeOf() 可识别基于组合继承创建的对象。

缺点

  • 调用两次超类构造函数:①创建子类原型时,②子类构造函数内部。子类最终包含超类对象的全部实例属性。

原型式继承

在不创建自定义类型的情况下,借助原型链,基于已有对象创建新对象:传入一个对象,返回一个原型对象为该对象的新对象。(so many 对象可你还是个单身狗。)

原理代码

function object(o) {
  function F () {};
  
  F.prototype = o;
  return new F();
}
复制代码

实际可通过 Object.create() 方法规范原型式继承。

var person1 = {
  name: 'a',
  friends: ['b', 'c'],
}

var person2 = Object.create(person1, {
  name: {
    value: 'Luuu'
  }
})

person2.friends.push('d')
console.log(person2.name) // luuu
var person3 = Object.create(person1)
person3.name = 'xx'
person3.friends.push('e')
console.log(person3.friends) // b, c, d, e
复制代码

优点

  • 可基于一个对象实现继承,不必创建构造函数

缺点

  • 与原型链缺点一致,所有实例会共享对象引用中属性值。

看到这里,已完成 ⅔ 的阅读。

寄生式继承

实现思路与工厂设计模式类似,先创建一个仅用于封装继承过程的函数,在该函数内部用某种方式增强对象,并返回。相当于原型式继承寄生于函数中,故如此命名。

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

function createFoo(o) {
  // 通过调用函数创建一个新对象
  var clone = object(o)
  // 用某种方式增强对象
  clone.hello = function () {
    alert('hi!')
  }
  // 返回该对象
  return clone
}
复制代码

寄生组合式继承

通过借用构造函数来继承属性;
通过原型链的形式继承方法;
思路是:为了指定子类原型,而调用超类构造函数。使用寄生式继承来获得超类原型,再讲结果指定给子类原型。

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

function intProto(sub, super){
  //创建对象
  var proto = object(super.prototype);   
  //增强对象
  proto.constructor = sub;   
  //指定对象
  sub.prototype = proto;           
}

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

intProto(SubType, SuperType);

// 继承原型方法
SubType.prototype.sayAge = function(){
  alert(this.age);
};
复制代码

使用关键字 class

ES6 引入的关键字 class,虽然提供了更优雅的实现继承的方式,本质仍是基于原型的实现的语法糖。

class Foo {
  constructor(name) {
    this.name = name;
  }
  swingSord() {
    return true;
  }
}

var foo = new Foo('luuu')