全文转载自:http://www.blogjava.net/baoyaer/articles/105481.html

 

每当我们说到 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 方法只实现了方法的替换而没有作对象属性的复制操作。

 

例:

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