在JS中继承是一个非常复杂的话题,比其他任何面向对象语言中的继承都复杂得多。在大多数其他面向对象语言中,继承一个类只需使用一个关键字即可。在JS中想要达到继承公用成员的目的,需要采取一系列措施。
Js的继承在很多书里面细致的分了很多种类型和实现方式,大体上就是两种:对象冒充、原型方式。这两种方式各有优点和缺陷。
对象冒充继承:
【优点】可以实现多重继承;
【缺点】无法继承prototype域的变量和方法;
所有的成员方法都是针对this而创建的,所有的实例都会拥有一份成员方法的副本,属于深copy,比较浪费资源
原型继承:
【优点】所有实例共用一个pototype域,性能好些;
【缺点】不能传递参数;
注意 constructor 会指向父对象;
我们来列举一些出来看看:
(一)对象冒充:
function Parent(name) { this.name = name; this.showName = function () { console.log(this.name); }; }; function Child (name, age) { this.method = Parent; this.method(name); delete this.method; this.age = age; this.showAge = function () { console.log(this.name +":"+ this.age); }; }; var parent = new Parent("Kang"), child = new Child("F7", 30); parent.showName(); child.showName(); child.showAge();
注意这里的上下文环境中的this对象是Child 的实例,所以在执行Parent构造函数脚本时,所有Parent的变量和方法都会赋值给this所指的对象,即Child的实例,这样子就达到Child继承了Parent的属性方法的目的。之后删除临时引用method,是防止维护Child中对Parent的类对象(注意不是实例对象)的引用更改,因为更改method会直接导致类Parent(注意不是类Parent的对象)结构的变化。
(小插曲)
在Js版本更新的过程中,为了更方便的执行这种上下文this的切换以达到继承或者更加广义的目的,增加了call和apply函数。它们的原理是一样的,只是参数不同的版本罢了(一个可变任意参数,一个必须传入数组作为参数集合)。
关于Call方法,官方解释:调用一个对象的一个方法,以另一个对象替换当前对象。 call (thisOb,arg1, arg2…)
下面我们将借助call和apply方法实现继承。
(二)call方法继承:
function ParentCall (name) { this.name = name; this.showName = function () { console.log(this.name); }; }; function ChildCall (name, age) { ParentCall.call(this, name); this.age = age; this.showAge = function () { console.log(this.name +":"+ this.age); }; }; var pc = new ParentCall("Kang"), cc = new ChildCall("F7", 30); pc.showName(); cc.showName(); cc.showAge();
(三)apply方法继承:
function ParentApply (name) { this.name = name; this.showName = function () { console.log(this.name); }; }; function ChildApply (name, age) { ParentApply.apply(this, [name]); this.age = age; this.showAge = function () { console.log(this.name +":"+ this.age); }; }; var pa = new ParentApply("Kang"), ca = new ChildApply("F7", 30); pa.showName(); ca.showName(); ca.showAge();
以上的 call 和 apply 方法继承都是对象冒充原理的一个运用。
(四)原型继承:
function ParentPrototype () { this.name = "F7";// 不能传递参数就只能写常量了 this.showName = function () { console.log(this.name); } }; ParentPrototype.prototype.showInfo = function () { console.log(this.name +":"+ this.age); }; function ChildPrototype () { this.age = 30; }; ChildPrototype.prototype = new ParentPrototype(); var ap = new ChildPrototype(), bp = new ParentPrototype(); bp.showName(); ap.showName(); ap.showInfo();
(五)混合集成【寄生组合模式】:
为了集合两种继承模式的优点,消除缺点产生了这种混合模式。
利用对象冒充机制的call方法把父类的属性给抓取下来,而成员方法尽量写进被所有对象实例共享的prototype域中,以防止方法副本重复创建。然后利用原型继承让子类继承父类prototype域的所有方法。
function ParentBlend (name) { this.name = name; this.showName = function () { console.log(this.name); }; }; ParentBlend.prototype.showInfo = function () { console.log(this.name); }; function ChildBlend (name, age, sex) { ParentBlend.call(this, name); this.age = age; this.sex = sex this.showAge = function () { console.log(this.name +":"+ this.age); }; }; ChildBlend.prototype = new ParentBlend(); ChildBlend.prototype.showSex = function () { console.log(this.name +":"+ this.sex); }; var pb = new ParentBlend("Kang"), cb = new ChildBlend("F7", 30, "男"); pb.showName(); pb.showInfo(); cb.showName(); cb.showInfo(); cb.showAge(); cb.showSex();
博主好厉害!