每當我們說到
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,感興趣的可以搜索下載。