在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();
博主好厉害!