js高级程序设计学习-继承

1,494 阅读5分钟

导言

不得不说,js 这门语言看似简单, 实际上想要深入,真不容易。
继承是 OO 语言中的一个最为人津津乐道的概念。许多OO 语言都支持两种继承方式: 接口继承和实现继承。
其实现继承主要是依靠原型链来实现继承的

基于原型链继承

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
回顾一下构造函数, 原型和实例的关系:
每个构造函数都有一个原型对象, 原型对象都包含一个指向构造函数的指针, 而实例都包含一个指向原型对象的内部指针

那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样?
显然,此时的原型对象将包含一个指向另一个原型的指针,相应地, 另一个原型中也包含着一个指向另一个构造函数的指针。 就构成了实例与原型的链条。 这就是所谓的原型链。

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function () {
    return this.property;
}
function SubType() {
    this.subproperty = false;
}
// 继承了 SuperType
SubType.prototype = new SuperType(); // 关键
var instance = new SubType();
instance.getSuperValue(); // true

基于构造函数继承

这种技术的基本思想相当简单, 即在子类型构造函数的内部调用超类型构造函数。如下

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

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

var instance = new SubType('zs',20);
console.log(instance); // SubType { name: 'zs', age: 20 }

问题:在超类型的原型中定义的方法,对子类型而言也是不可见的

组合继承

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

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;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
    console.log(this.age);
}

var instance = new SubType('zs',12);
console.log(instance);
// SuperType { name: 'zs', colors: [ 'red', 'blue' ], age: 12 }
// 使用instanceof 识别组合继承的对象
console.log(instance instanceof SuperType)// true

组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,称为javaScript 中最常用的继承模式.
instanceof 也能够用于识别基于组合继承创建的对象

原型式继承

在2006年一位大佬,写了一篇文章,介绍了一种继承方式. 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

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

在 oject()函数内部, 先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。 从本质上讲, object()对传入其中的对象执行了一次浅复制。

var person = {
    name : 'zs',
    friends : ['red','green']
}
var anotherPerson = object(person);
anotherPerson.friends.push('zs')
var yetAnotherPerson = object(person);
console.log(person.firends); // ['red','green','zs']

后面,ES通过新增 Object.create()方法规范化了原型式继承。

var person = {
    name : 'zs',
    friends : ['red','blue']
}
var anthoerPerson = Object.create(person, {
    name : {
        value : 'hw'
    }
})
console.log(anthoerPerson.name)

寄生式继承

寄生式继承是与原型式继承很像的一种思路
即创建一个仅仅用于封装继承过程的函数

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

function createAnother(original) {
    var clone = object(original);
    clone.sayHi = function() {
        console.log('hi')
    }
    return clone;
}
var person = {
    name : 'zs',
    friends : ['red','blue']
}
var anotherPerson = createAnother(person);
console.log(anotherPerson.name)

寄生组合式继承

组合继承的问题

前面说过,组合继承是 javaScript最常用的继承模式; 不过, 它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

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); // 第二次调用 SuperType()
    this.age = age;
}
SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.sayAge = function() {
    console.log(this.age)
}
console.log(SubType.prototype);
let s = new SubType('zs',12);
console.log(s)

在第一次调用 SuperType 构造函数时, SubType.prototype 会得到两个属性: name 和 colors; 他们都是 SuperType 的实例属性 ,在只不过现在位于 SubType 的原型中。 当第二次调用 SuperType 构造函数时 , 这一次又在新对象上创建了实例属性 name 和 colors。 于是, 这两个属性就屏蔽了原型中的两个同名属性。
有两组name和 colors属性:一组在实例上,一组在 SubType原型中。这就是调用两次SuperType构造函数的结果。
好在我们已经找到了解决这个问题的方法-寄生组合继承

寄生组合继承

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

function inhertitProtptype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

这个函数接收两个参数:

  • 子类型构造函数
  • 超类型构造函数

在函数内部

  1. 第一步创建超类型原型的一个副本。
  2. 第二步是为创建的副本添加 constructor属性,从而弥补因重写原型而失去的默认的 constructor属性。
  3. 最后一步, 将新创建的对象赋值给与子类型的原型。
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;
}
inhertitProtptype(SubType, SuperType);
SubType.prototype.sayAge = function() {
    console.log(this.age);
}

这个例子高效率体现在它只调用了一次 SuperType 构造函数, 并且因此避免了在 SubType.prototype 上面创建的不必要的, 多余的属性。 与此同时, 原型链还能保持不变; 因此, 还能够正常使用 instanceof.
寄生组合继承是引用类型最理想的继承范式

ES6 继承

其实, ES6继承中的 class , super 和 extends都是语法糖

class本质上是 function
super 本质上是 Parent.call(this,...args)

extends 本质上是 
var prototype = Object.create(Parent.prototype);
prototype.constrictor = Children;
Chilren.prototype = prototype;
class Parent {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name)
    }
}

class Children  extends Parent {
    constructor(name , age) {
        super(name);
        this.age = age;
    }
    sayAge() {
        console.log(this.age)
    }
}
let c = new Children('zs',13);
console.log(c)
c.sayName()