javascript作为一门脚本语言,在设计之初并没有考虑到面向对象的特性。即便到了当今这个遍布现代浏览器的年代,各种javascript
框架/库如雨后春笋般地疯狂生长,javascript中连个 class
关键字都没有。如果你要编写一个类,你还得借助于function,至于继承、重载什么的,就别奢望了。
可是,没有继承,日子怎么过啊?难道把所有的共有逻辑都拷贝一遍,实现最低级的代码复用?
答案当然是——no,所以,我们要自己实现继承!
最关键的目标当然是继承——子类自动拥有父类的所有公共属性和方法。
支持instanceof,例如c是子类的实例,而p是父类,c instanceof p应该返回true。
其次应该能够重写(override)父类的方法,并且在子类的方法中,能够方便地调用到父类的同名方法。
至于说重载(overload),由于javascript的语言特性(不可以有同名方法,即便它们参数列表不一样),无法实现。
javascript的对象有一个很重要的属性——__proto__,也就是原型。原型实质上也是一个对象,所有它也可以有自己的原型,这样就形成一个原型链。当你调用某个对象的某个方法,或者读取该对象的某个属性,javascript执行器是这样做的:
首先到该对象中找对应的方法或属性,如果找不到,
到该对象的原型中找,如果还找不到,
到原型的原型里面找
...
直到最后找到object的原型为止,如果还没有则返回undefined
如下图所示:
原型链的这个特性,和继承很相似,所以自然而然,我们可以利用它来实现继承机制。而原型链对instanceof的支持,使得它成为很好的选择。
我们定义extend函数,这个函数接受两个参数,第一个是父类,第二个是子类,如下所示:
这个函数对子类进行处理,并返回子类。处理的逻辑如下:
通过将子类的原型链与父类的原型链连接起来,子类可以自动拥有父类的方法和属性:
为了连接原型链,需要创建一个父类的实例,并将其赋给子类的原型属性。但我们不希望在extend方法里面就实例化父类,所以引入了一个中间类t,以解决这个问题。
原型链建立之后,原来子类原型上的方法和属性我们也需要保留下来:
如果父类有同名方法,我们使用一个闭包,来保留对父类方法和子类方法的引用。然后,修改新的原型中该方法的引用,将其指向一个新的
function。在这个function里面,我们创建一个临时属性super,将其指向父类方法,并调用子类方法,这样在子类方法中,通过
this.super可以调用该父类方法:
对于属性,不存在重写的问题,所以直接将子类原来的原型中的属性加到新的原型中即可:
为了让子类能够访问到父类的构造器,我们将父类赋给子类的super属性:
假设我们要设计一个管理系统,里面涉及到客户、工人和经理等。将客户和员工的共性抽象出来,我们得到人(people);然后将工人和经理的共性抽象得到员工(employee)。这样我们得到三级类结构:
实现这个设计的代码如下:
我们希望对每个人都有一个描述,people是姓+名;员工在姓+名之后,还包括公司名称;而经理在员工的描述之后,还包括职位。代码如下:
在所有的成员方法都已经定义好之后,声明类的继承(必须先定义方法,再声明类的继承,否则无法在方法中使用this.super调用父类方法!):
使用这些类就比较简单,直接new就好了: