首先说结论吧,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
的话,那么Son
和Parent
就会共用一个原型对象,这样的话,在Son
对原型方法的修改就会直接反应到Parent
上,不符合我们继承的原则。
缺点
还是刚才的代码,来做个测试:1
console.log(son.motion) // undefined
可以看到,实际上是没有执行到Parent
的构造函数,所以无法继承到Parent
的属性
2.构造函数继承
实现
1 | // 父类 |
原理就是通过作用当前作用域去调用Parent
的构造函数,就能把里面的属性赋给当前作用域了。
缺点
可以看到:1
2console.log(son instanceof Parent) // false
son.speak() // not a function
显而易见,因为Son
和Parent
之间没有构成原型链,因此instanceof
必然为false
,因而不能使用到Parent
的原型方法。在我看来这种方法更像构造函数的模板方法而已,算不上继承
组合继承
可以看到,上面两种方法,其实是可以互补的,原型链继承缺少构造函数,构造函数继承缺少原型链方法。
实现
1 | // 父类 |
这样的话,才是一个比较完整的继承,