每當我們說到 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 方法執行了兩步操作。

?第一:將BaseClassapply傳遞的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

?3call+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"。也就是說nameage沒有被賦值。

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)

?最后,就是多重繼承了,由于jsprototype只能對應一個對象,因此無法實現真正意義上的多重繼承。有一個js庫模擬了多重繼承,但是該庫也額外重寫了 instanceOf 方法,用 _instanceOf_subclassOf 函數來模擬判斷。該庫的名字叫modello.js,感興趣的可以搜索下載。