每当我们说到
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,感兴趣的可以搜索下载。