在介绍Javascript的继承原理之前,我们先来看下继承方法里需要使用的方法call、apply和new。
call和apply
call方法定义
语法:Function.call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:
call 方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。如果没有提供 thisObj 参数,那么 Global对象被用作thisObj。
apply方法定义
语法:Function.apply([thisObj[,argArray]])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:如果没有提供 argArray 和 thisObj任何一个参数,那么 Global 对象将被用作 thisObj,并且无法被传递任何参数。
call方法跟apply方法并没有明显区别,只是传递参数的形式不一样。
call():一个一个的传递参数;
apply():以数组的形式传递。
call模拟方法的实现分为以下几个步骤
1.this参数可以传null,当为null的时候,视为指向window
2.将函数设为对象的属性
3.给定参数并执行该函数
4.删除该函数
apply模拟方法的实现也是类似的
new
new模拟方法的实现
当用new实例化一个构造函数后,我们可以访问到构造函数里的属性以及构造函数原型(prototype)上的属性,所以模拟new的实现分成以下几个步骤
1.创建一个空对象
2.将空对象的原型指向构造函数原型,这样空对象就可以访问到构造函数原型上的属性
3.使用apply,将构造函数this指向改变为新建的对象,这样新建的对象就能访问构造函数上属性
4.如果构造函数中返回的值是对象,则返回这个对象;如果不是,返回新建的对象
寄生组合式继承
在《JavaScript高级程序设计》书中介绍了几种经典的继承方法,但都存在着一些问题。
1.原型链继承:当原型中属性值是个复杂数据类型时,所有的实例都会共享这个数据,其中一个实例修改这个数据其他实例都会受到影响。
2.构造函数继承:方法都在构造函数中定义,每次创建实例都会创建一遍方法。
3.组合继承:最大的缺点是会调用两次父构造函数。一次是设置子类型实例的原型的时候,一次在创建子类型实例的时候。
在高程书的最后介绍了寄生组合式继承,代码实现如下
simple-inheritance库的实现
以下是摘自别人对simple-inheritance的注释。
ES6 Class继承
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。Class的基本语法如下
类的所有方法都是定义在类的prototype属性上面,类的内部所有定义的方法,都是不可枚举的。Class 可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。在constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this
Javascript实现多继承
如果有火柴人的粉丝相信对dojo不会陌生,在这个库里使用dojo.declare实现里类的定义机制,不仅可以实现如前几节介绍的单继承,还能实现多继承方式。
从上面代码看到,classZ拥有classX和classY类的全部方法,一般我们会认为classZ继承了classX和classY,那么classZ instanceof classX、classZ instanceof classY应该都是true,然而在JavaScript中只有一个prototype,实际上classZ的父类是classX,classY类似于聚合类,他的属性和方法mixin到classX,达到实现继承的效果。使用ES6 Class简单模拟多继承的实现。