js继承探讨

每当我们说到 js 的继承时,在您的脑袋的第一反应就是 prototype 原型机制来实现。但是您是否使用过其他的方法来实现继承呢,或者您是否了解其他 实现方式及各种不同的继承实现机制的优缺点呢?

好了,下面我们就来看看几种比较常见的继承实现吧。

1、 prototype方式

 1    var   BaseClass   =  function  ()
 2   
 3    {
 4  
 5      this .name  =   " 3zfp " ;
 6  
 7      this .age  =   100 ;
 8  
 9       this .ToString  =   function () {
10  
11          return   this .name + "   "   + this .age;
12  
13     } 

14  
15 } 

16   
17    var   Derived   =     function  ()
18   
19   
20  
21      this .address  =   " ShenZhen " ;
22  
23 } 

24   
25  Derived.prototype   =     new   BaseClass();
26   
27    var   instance   =     new   Derived();
28   
29  instance.ToString();
30 

这种方式最为简单,只需要让一个类的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 ;如何验证呢?看如下代码:

1             var   foo   =     function  ()  {}  ;
2   
3           var   result   =   (foo.prototype  ==  Object);

这段代码的result 所得值为 false;

以下几个需要注意:

 1          typeof  (Object.prototype)   ==     "  object  "  ;
 2   
 3   
 4   
 5           typeof  (Object.prototype.prototype)   ==     "  undefined  "  ;
 6   
 7   
 8   
 9           var   obj   =     new   Object();
10   
11           typeof  (obj.prototype)   ==     "  undefined  "  ;
12   
13         
14   
15            var   obj   =     {}  ;
16   
17           typeof  (obj.prototype)   ==     "  undefined  "  ;
18   
19   
20   
21 

2 、 apply 方式

 1    var   BaseClass   =  function  ()
 2   
 3    {
 4  
 5      this .name  =   " 3zfp " ;
 6  
 7      this .age  =   100 ;
 8  
 9       this .ToString  =   function () {
10  
11          return   this .name + "   "   + this .age;
12  
13     } 

14  
15 } 

16   
17    var   Derived   =     function  ()
18   
19   
20  
21        BaseClass.apply( this , new  Array());
22  
23      this .address  =   " ShenZhen " ;
24  
25 } 

26   
27    var   instance   =     new   Derived();
28   
29  instance.ToString();
30   
31   
32 

在这种方式下,我们最需要理解的就是 apply 函数的作用。

该方法普遍的解释为用 A 方法去替换 B 方法。第一个参数为 B 方法的对象本身,第二个参数为一个数组,该数组内的值集合为需要传递给 A 方法对应的 参数列表,如果参数为空,即没有参数传递,则通过 new Array() 来传递, null 无效。

一般的方式为:

但是在本例当中,   apply   方法执行了两步操作。

 第一:将BaseClass以apply传递的Array数组作为初始化参数进行实例化。

第二:将新生成的实例对象的所有属性( name , age , ToString 方法)复制到 instance 实例对象。 这样就实现了继承。

 1    var   foo   =     function  ()
 2   
 3    {
 4  
 5          this .fooA  =   function () {
 6  
 7                this .fooB.apply( this , new  Array( " sorry " ));
 8  
 9        } 

10  
11         this .fooB  = function (str)
12  
13          {
14  
15               alert(str);
16  
17        } 

18  
19 } 

20   
21    new   foo().fooA();
22 

 3、call+prototype 方式

 1    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  ()
18   
19   
20  
21        BaseClass.call( this , " 3zfp " , 100 );
22  
23      this .address  =   " ShenZhen " ;
24  
25 } 

26   
27  Derived.prototype   =     new   BaseClass();
28   
29    var   instance   =     new   Derived();
30   
31  instance.ToString();
32   
33 

       其实,call函数和apply方式有很类似的作用,都是用A方法去替换B方法,但是参数传递不一样,call方法的第一个参数为B方法的对象本身,和面的参数列不用Array对象包装,直接依次传递就可以。

      为什么作用类似, call 方式的实现机制却要多一条 Derived.prototype = new BaseClass(); 语句呢?那是因为 call 方法只实现了方法的替换而没有作对象属性的复制操作。

call 方法实际上是做了如下几个操作:

例:

 1    var   foo   =     function  ()
 2   
 3    {
 4  
 5          this .fooA  =   function () {
 6  
 7                this .fooB.call( this , " sorry " );
 8  
 9        } 

10  
11         this .fooB  = function (str)
12  
13          {
14  
15               alert(str);
16  
17        } 

18  
19 } 

20   
21    new   foo().fooA();
22   
23 

则 this.fooB.call(this,"sorry")执行了如下几个操作:

1    this  .temp   =     this  .fooB;
2   
3    this  .temp(  "  sorry  "  );
4   
5    delete   (  this  .temp);
6 

其实,google Map API 的继承就是使用这种方式。大家可以下载的参考参考(maps.google.com)。

4 、 prototype.js 中的实现方式

 1    Object.extend   =     function  (destination, source)   {
 2  
 3    for  (property  in  source)  {
 4  
 5     destination[property]  =  source[property];
 6  
 7  } 

 8  
 9         return  destination;
10  
11 } 

12   
13    var   BaseClass   =  function  (name,age)  {
14  
15         this .name  =  name;
16  
17         this .age  =  age;
18  
19          this .ToString  =   function () {
20  
21                return   this .name + "   "   + this .age;
22  
23        } 

24  
25 } 

26   
27    var   Derived   =  function  ()
28   
29    {     
30  
31        BaseClass.call( this , " foo " , 100 );
32  
33         this .address  =   " singapore " ;
34  
35          this .ToString  =   function () {
36  
37                var  string  =  Derived.prototype.ToString.call( this );
38  
39                return  string  + "   " +   this .address;
40  
41        } 

42  
43 } 

44   
45  Object.extend(Derived.prototype,  new   BaseClass());
46   
47    var   instance   =     new   Derived();
48   
49  document.write(instance.ToString());

该方式,实际上是显式的利用了 apply 的原理来实现继承。先 var temp = new BaseClass() ,再将 temp 的属性遍历复制到 Derived.prototype 中。 for (property in source) 表示遍历某个对象的所有属性。但是私有属性无法遍历。 例 :

 1    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); 
34   
35 

输出为 "name age"而没有"innerString" 和 "innerToString()";具体原理,以后有机会可以解释(包括私有变量,私有函数,私有函数的变量可访问性等等)。上面总结了种种继承方式的实现。但是每种方法都有其优缺点。

第一种方式,如何实现如下需求,需要显示 "3zfp__100";

 1    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! 天无绝人之路。第二和第三种方法可以实现,具体如下:

 1    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,感兴趣的可以搜索下载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值