ES5中的继承实现

首先说结论吧,ES6有了类和继承机制以后,就尽量使用规范了,毕竟跟着规范走方便方方面面。但是在没有ES6以前想要实现继承,就需要走一点弯路了。

什么是继承

继承是面向对象编程的三个主要特征之一,目的是子类可以获得父类的所有属性和方法(当然如果private属性除外,JS的private规范还没有完全落地,所以这里不作考虑),并且可以对子类进行不会影响到父类的扩展。

那么跟模板方法(如PHP trait)有什么区别呢,个人觉得主要有下面两种区别:
1) 父子类之间是存在抽象关系的联系的,例如人 - 男人,动物 - 狗,交通工具 - 车等等这样的继承关系。而模板方法更多是单纯的方法复制。
2) 根据上面一点,那么继承之间,子类应该在运行态的时候同样获得父类的更新的,例如程序运行中获得speak()的新技能,那么子类也应该会动态继承到。而模板方法更多是在编译状态的时候就把代码复制到使用模板的function中,运行时模板和使用模板的function再没有关系。

实现继承的方式

实现继承的方式有很多,这里只总结三种我觉得有用的。

1.原型链继承

实现

直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父类
function Parent() {
this.type = 'parent'
this.motion = ['happy']
}

Parent.prototype.speak = function () {
console.log('I am person')
}

// 子类
function Son () {
this.type = 'son'
}
Son.prototype = new Parent()

var son = new Son()
son.speak()
console.log(son.type) // son
console.log(son instanceof Parent) // true

这样就是简单的原型链继承,主要原理就是把Son的原型对象从原本指向Object的改成指向一个Parent的对象,

为什么不指向Parent本身或者Parent.__proto__或者Parent.prototype
这就涉及原型链的基础知识了,__proto__指向的是原型对象,是描述当前实例是什么样子的,Parent.__proto__就是用来描述Parent本身,可以看到Parent本身是一个构造函数,而非对象,所以Parent.__proto__ == Function.prototype
而如果指向Parent.prototype的话,那么SonParent就会共用一个原型对象,这样的话,在Son对原型方法的修改就会直接反应到Parent上,不符合我们继承的原则。

缺点

还是刚才的代码,来做个测试:

1
console.log(son.motion) // undefined

可以看到,实际上是没有执行到Parent的构造函数,所以无法继承到Parent的属性

2.构造函数继承

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父类
function Parent() {
this.type = 'parent'
this.motion = ['happy']
}

Parent.prototype.speak = function () {
console.log('I am person')
}

// 子类
function Son () {
Parent.apply(this, [].slice.apply(arguments)) // 就相当于类中的super()方法
this.type = 'son'
}

var son = new Son()
console.log(son.type) // son
console.log(son.motion)

原理就是通过作用当前作用域去调用Parent的构造函数,就能把里面的属性赋给当前作用域了。

缺点

可以看到:

1
2
console.log(son instanceof Parent) // false
son.speak() // not a function

显而易见,因为SonParent之间没有构成原型链,因此instanceof必然为false,因而不能使用到Parent的原型方法。在我看来这种方法更像构造函数的模板方法而已,算不上继承

组合继承

可以看到,上面两种方法,其实是可以互补的,原型链继承缺少构造函数,构造函数继承缺少原型链方法。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 父类
function Parent() {
this.type = 'parent'
this.motion = ['happy']
}

Parent.prototype.speak = function () {
console.log('I am person')
}

// 子类
function Son () {
Parent.apply(this, [].slice.apply(arguments))
this.type = 'son'
}

// 如果使用父类实例的话,会执行两次构造函数
// Son.prototype = new Parent()
Son.prototype = Object.create(Parent.prototype)
Son.prototype.constructor = Son
Son.prototype.run = function () {
console.log('running')
}

var son = new Son()
son.speak()
son.run()
console.log(son.type) // son
console.log(son instanceof Parent) // true

这样的话,才是一个比较完整的继承,