每当我们说到 js 的继承时,在您的脑袋的第一反应就是 prototype原型机制来实现。但是您是否使用过其他的方法来实现继承呢,或者您是否了解其他实现方式及各种不同的继承实现机制的优缺点呢?
好了,下面我们就来看看几种比较常见的继承实现吧。
1、 prototype方式
var BaseClass = function () { this .name = " 3zfp " ; this .age = 100 ; this .ToString = function () { return this .name + " " + this .age; } } var Derived = function () { this .address = " ShenZhen " ; } Derived.prototype = new BaseClass(); var instance = new Derived(); instance.ToString();
这种方式最为简单,只需要让一个类的prototype为被继承的一个实例就ok,然后直接使用BaseClass的方法。
prototype 属性是啥意思呢? prototype即为原型,每一个对象( 由 function 定义出来)都有一个默认的原型属性,该属性是个对象类型。 并且该默认属性用来实现链的向上攀查。意思就是说,如果某个对象的属性不存在,那个将通过prototype属性对应的对象的来查找该对象的属性。 如果 prototype查找不到呢?js 会自动地找 prototype 的 prototype属性对应的对象来查找,这样就通过 prototype一直往上索引攀查,直到查找到了 该属性或者 prototype 最后为空("undefined");
例如:上例中的 instance.ToString()方法。js 会先在 instance 实例中查找是否有ToString()方法,因为没有,所以查找 Derived.prototype 属性, 而prototype 为 NewClass的一个实例,该实例有ToString() 方法,于是调用成功;同样给 instance 的name 属性赋值时也是查找 prototype来实现的。
注意,每一个对象得 prototype都默认对应一个object 对象,但是该对象不等于 Object;如何验证呢?看如下代码:
var foo = function () {} ; var result = (foo.prototype == Object);
这段代码的result 所得值为 false;
以下几个需要注意:
typeof (Object.prototype) == " object " ; typeof (Object.prototype.prototype) == " undefined " ; var obj = new Object(); typeof (obj.prototype) == " undefined " ; var obj = {} ; typeof (obj.prototype) == " undefined " ;
2 、 apply 方式
var BaseClass = function () { this .name = " 3zfp " ; this .age = 100 ; this .ToString = function () { return this .name + " " + this .age; } } var Derived = function () { BaseClass.apply( this , new Array()); this .address = " ShenZhen " ; } var instance = new Derived(); instance.ToString();
在这种方式下,我们最需要理解的就是 apply 函数的作用。
该方法普遍的解释为用 A 方法去替换 B 方法。第一个参数为 B 方法的对象本身,第二个参数为一个数组,该数组内的值集合为需要传递给 A 方法对应的 参数列表,如果参数为空,即没有参数传递,则通过 new Array()来传递,null 无效。
一般的方式为:
但是在本例当中, apply 方法执行了两步操作。
第一:将BaseClass以apply传递的Array数组作为初始化参数进行实例化。
第二:将新生成的实例对象的所有属性( name,age , ToString 方法)复制到instance实例对象。 这样就实现了继承。
var foo = function () { this .fooA = function () { this .fooB.apply( this , new Array( " sorry " )); } this .fooB = function (str) { alert(str); } } new foo().fooA();
3、call+prototype 方式
var BaseClass = function (name,age) { this .name = name; this .age = age; this .ToString = function () { return this .name + " " + this .age; } } var Derived = function () { BaseClass.call( this , " 3zfp " , 100 ); this .address = " ShenZhen " ; } Derived.prototype = new BaseClass(); var instance = new Derived(); instance.ToString();
其实,call函数和apply方式有很类似的作用,都是用A方法去替换B方法,但是参数传递不一样,call方法的第一个参数为B方法的对象本身,和面的参数列不用Array对象包装,直接依次传递就可以。
为什么作用类似, call 方式的实现机制却要多一条 Derived.prototype = new BaseClass(); 语句呢?那是因为 call方法只实现了方法的替换而没有作对象属性的复制操作。
call 方法实际上是做了如下几个操作:
例:
var foo = function () { this .fooA = function () { this .fooB.call( this , " sorry " ); } this .fooB = function (str) { alert(str); } } new foo().fooA();
则 this.fooB.call(this,"sorry")执行了如下几个操作:
2
3 this .temp( " sorry " );
4
5 delete ( this .temp);
6
其实,google Map API 的继承就是使用这种方式。大家可以下载的参考参考(maps.google.com)。
4 、 prototype.js 中的实现方式
Object.extend = function (destination, source) { for (property in source) { destination[property] = source[property]; } return destination; } var BaseClass = function (name,age) { this .name = name; this .age = age; this .ToString = function () { return this .name + " " + this .age; } } var Derived = function () { BaseClass.call( this , " foo " , 100 ); this .address = " singapore " ; this .ToString = function () { var string = Derived.prototype.ToString.call( this ); return string + " " + this .address; } } Object.extend(Derived.prototype, new BaseClass()); var instance = new Derived(); document.write(instance.ToString());
该方式,实际上是显式的利用了 apply 的原理来实现继承。先 var temp = new BaseClass(),再将temp 的属性遍历复制到 Derived.prototype中。for (property in source) 表示遍历某个对象的所有属性。但是私有属性无法遍历。例:
var foo = function () 2 3 { 4 5 var innerString = "" ; 6 7 this .name = " 3zfp " ; 8 9 this .age = 100 ; 10 11 function innerToString() 12 13 { 14 15 return innerString; 16 17 } 18 19 } 20 21 var f = new foo(); 22 23 var eles = "" ; 24 25 for (property in f) 26 27 { 28 29 eles += " " + property; 30 31 } 32 33 document.write(eles);
输出为 "name age"而没有"innerString" 和 "innerToString()";具体原理,以后有机会可以解释(包括私有变量,私有函数,私有函数的变量可访问性等等)。上面总结了种种继承方式的实现。但是每种方法都有其优缺点。
第一种方式,如何实现如下需求,需要显示 "3zfp__100";
var BaseClass = function (name,age) 2 3 { 4 5 this .name = name; 6 7 this .age = age; 8 9 this .ToString = function () { 10 11 return this .name + " " + this .age; 12 13 } 14 15 } 16 17 var Derived = function (name,age) 18 19 { 20 21 this .address = " ShenZhen " ; 22 23 } 24 25 Derived.prototype = new BaseClass(); 26 27 var instance = new Derived( " 3zfp " , 100 ); 28 29 document.write(instance.ToString()); 30 31
我们通过运行可以发现,实际上输出的是 "undefined__undefined"。也就是说name和age没有被赋值。
oh,my god! 天无绝人之路。第二和第三种方法可以实现,具体如下:
var BaseClass = function (name,age) 2 3 { 4 5 this .name = name; 6 7 this .age = age; 8 9 this .ToString = function () { 10 11 return this .name + " " + this .age; 12 13 } 14 15 } 16 17 var Derived = function (name,age) 18 19 { 20 21 BaseClass.apply( this , new Array(name,age)); 22 23 this .address = " ShenZhen " ; 24 25 } 26 27 var instance = new Derived( " 3zfp " , 100 ); 28 29 document.write(instance.ToString()); 30 31 ______________________________________________ 32 33 --------------------------------------------------------------------- 34 35 var BaseClass = function (name,age) 36 37 { 38 39 this .name = name; 40 41 this .age = age; 42 43 this .ToString = function () { 44 45 return this .name + " " + this .age; 46 47 } 48 49 } 50 51 var Derived = function (name,age) 52 53 { 54 55 BaseClass.call( this ,name,age); 56 57 this .address = " ShenZhen " ; 58 59 } 60 61 Derived.prototype = new BaseClass(); 62 63 var instance = new Derived( " 3zfp " , 100 ); 64 65 66 67 document.write(instance.ToString()); 68 69 70
但是用apply方法也还是有缺点的,为什么?在js中,我们有个非常重要的运算符就是"instanceof",该运算符用来比较某个对向是否为某种类型。对于继承,我们除了是属于 Derived类型,也应该是BaseClass类型,但是。apply方式返回值为false((instance instanceof BaseClass) == false).由于prototype.js使用了类似apply的方式,所以也会出现这个问题。
啊,终极方法就是 call+prototype方式了,还是google 牛 X 。您可以试一下是否正确((instance instanceof BaseClass) == true)。
最后,就是多重继承了,由于js中prototype只能对应一个对象,因此无法实现真正意义上的多重继承。有一个js库模拟了多重继承,但是该库也额外重写了 instanceOf方法,用 _instanceOf和_subclassOf函数来模拟判断。该库的名字叫modello.js,感兴趣的可以搜索下载。