JavaScript 继承详解(三)

释放双眼,带上耳机,听听看~!

注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法。

首先让我们来回顾一下第一章中介绍的例子:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1 function Person(name) {  
2
3this.name = name;  
4
5}  
6
7Person.prototype = {  
8
9getName: function() {  
10
11return this.name;  
12
13}  
14
15}  
16
17function Employee(name, employeeID) {  
18
19this.name = name;  
20
21this.employeeID = employeeID;  
22
23}  
24
25Employee.prototype = new Person();  
26
27Employee.prototype.getEmployeeID = function() {  
28
29return this.employeeID;  
30
31};  
32
33var zhang = new Employee("ZhangSan", "1234");  
34
35console.log(zhang.getName()); // "ZhangSan"  
36
37
38

 

修正constructor的指向错误

 

从上一篇文章中关于constructor的描述,我们知道Employee实例的constructor会有一个指向错误,如下所示:


1
2
3
4
5
6
7
8
1 var zhang = new Employee("ZhangSan", "1234");  
2
3console.log(zhang.constructor === Employee); // false  
4
5console.log(zhang.constructor === Object); // true  
6
7
8

1
2
1我们需要简单的修正:
2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1 function Employee(name, employeeID) {  
2
3this.name = name;  
4
5this.employeeID = employeeID;  
6
7}  
8
9Employee.prototype = new Person();  
10
11Employee.prototype.constructor = Employee;  
12
13Employee.prototype.getEmployeeID = function() {  
14
15return this.employeeID;  
16
17};  
18
19var zhang = new Employee("ZhangSan", "1234");  
20
21console.log(zhang.constructor === Employee); // true  
22
23console.log(zhang.constructor === Object); // false  
24
25
26

 

创建Employee类时实例化Person是不合适的

 

但另一方面,我们又必须依赖于这种机制来实现继承。 解决办法是不在构造函数中初始化数据,而是提供一个原型方法(比如init)来初始化数据。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
1 // 空的构造函数  
2
3function Person() {  
4
5}  
6
7Person.prototype = {  
8
9init: function(name) {  
10
11this.name = name;  
12
13},  
14
15getName: function() {  
16
17return this.name;  
18
19}  
20
21}  
22
23// 空的构造函数  
24
25function Employee() {  
26
27}  
28
29// 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数  
30
31Employee.prototype = new Person();  
32
33Employee.prototype.constructor = Employee;  
34
35Employee.prototype.init = function(name, employeeID) {  
36
37this.name = name;  
38
39this.employeeID = employeeID;  
40
41};  
42
43Employee.prototype.getEmployeeID = function() {  
44
45return this.employeeID;  
46
47};  
48
49
50

1
2
1这种方式下,必须在实例化一个对象后手工调用init函数,如下:
2

1
2
3
4
5
6
7
8
1 var zhang = new Employee();  
2
3zhang.init("ZhangSan", "1234");  
4
5console.log(zhang.getName()); // "ZhangSan"  
6
7
8

 

如何自动调用init函数?

 

必须达到两个效果,构造类时不要调用init函数和实例化对象时自动调用init函数。看来我们需要在调用空的构造函数时有一个状态标示。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
1 // 创建一个全局的状态标示 - 当前是否处于类的构造阶段  
2
3var initializing = false;  
4
5function Person() {  
6
7if (!initializing) {  
8
9this.init.apply(this, arguments);  
10
11}  
12
13}  
14
15Person.prototype = {  
16
17init: function(name) {  
18
19this.name = name;  
20
21},  
22
23getName: function() {  
24
25return this.name;  
26
27}  
28
29}  
30
31function Employee() {  
32
33if (!initializing) {  
34
35this.init.apply(this, arguments);  
36
37}  
38
39}  
40
41// 标示当前进入类的创建阶段,不会调用init函数  
42
43initializing = true;  
44
45Employee.prototype = new Person();  
46
47Employee.prototype.constructor = Employee;  
48
49initializing = false;  
50
51Employee.prototype.init = function(name, employeeID) {  
52
53this.name = name;  
54
55this.employeeID = employeeID;  
56
57};  
58
59Employee.prototype.getEmployeeID = function() {  
60
61return this.employeeID;  
62
63};  
64
65// 初始化类实例时,自动调用类的原型函数init,并向init中传递参数  
66
67var zhang = new Employee("ZhangSan", "1234");  
68
69console.log(zhang.getName()); // "ZhangSan"  
70
71
72

1
2
1但是这样就必须引入全局变量,这是一个不好的信号。
2

 

如何避免引入全局变量initializing?

 

我们需要引入一个全局的函数来简化类的创建过程,同时封装内部细节避免引入全局变量。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
1 // 当前是否处于创建类的阶段  
2
3var initializing = false;  
4
5function jClass(baseClass, prop) {  
6
7// 只接受一个参数的情况 - jClass(prop)  
8
9if (typeof (baseClass) === "object") {  
10
11prop = baseClass;  
12
13baseClass = null;  
14
15}  
16
17// 本次调用所创建的类(构造函数)  
18
19function F() {  
20
21// 如果当前处于实例化类的阶段,则调用init原型函数  
22
23if (!initializing) {  
24
25this.init.apply(this, arguments);  
26
27}  
28
29}  
30
31// 如果此类需要从其它类扩展  
32
33if (baseClass) {  
34
35initializing = true;  
36
37F.prototype = new baseClass();  
38
39F.prototype.constructor = F;  
40
41initializing = false;  
42
43}  
44
45// 覆盖父类的同名函数  
46
47for (var name in prop) {  
48
49if (prop.hasOwnProperty(name)) {  
50
51F.prototype[name] = prop[name];  
52
53}  
54
55}  
56
57return F;  
58
59};  
60
61
62

1
2
1使用jClass函数来创建类和继承类的方法:
2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1 var Person = jClass({  
2
3init: function(name) {  
4
5this.name = name;  
6
7},  
8
9getName: function() {  
10
11return this.name;  
12
13}  
14
15});  
16
17var Employee = jClass(Person, {  
18
19init: function(name, employeeID) {  
20
21this.name = name;  
22
23this.employeeID = employeeID;  
24
25},  
26
27getEmployeeID: function() {  
28
29return this.employeeID;  
30
31}  
32
33});  
34
35var zhang = new Employee("ZhangSan", "1234");  
36
37console.log(zhang.getName()); // "ZhangSan"  
38
39
40

1
2
1OK,现在创建类和实例化类的方式看起来优雅多了。 但是这里面还存在明显的瑕疵,Employee的初始化函数init无法调用父类的同名方法。
2

 

如何调用父类的同名方法?

 

我们可以通过为实例化对象提供一个base的属性,来指向父类(构造函数)的原型,如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
1 // 当前是否处于创建类的阶段  
2
3var initializing = false;  
4
5function jClass(baseClass, prop) {  
6
7// 只接受一个参数的情况 - jClass(prop)  
8
9if (typeof (baseClass) === "object") {  
10
11prop = baseClass;  
12
13baseClass = null;  
14
15}  
16
17// 本次调用所创建的类(构造函数)  
18
19function F() {  
20
21// 如果当前处于实例化类的阶段,则调用init原型函数  
22
23if (!initializing) {  
24
25// 如果父类存在,则实例对象的base指向父类的原型  
26
27// 这就提供了在实例对象中调用父类方法的途径  
28
29if (baseClass) {  
30
31this.base = baseClass.prototype;  
32
33}  
34
35this.init.apply(this, arguments);  
36
37}  
38
39}  
40
41// 如果此类需要从其它类扩展  
42
43if (baseClass) {  
44
45initializing = true;  
46
47F.prototype = new baseClass();  
48
49F.prototype.constructor = F;  
50
51initializing = false;  
52
53}  
54
55// 覆盖父类的同名函数  
56
57for (var name in prop) {  
58
59if (prop.hasOwnProperty(name)) {  
60
61F.prototype[name] = prop[name];  
62
63}  
64
65}  
66
67return F;  
68
69};  
70
71
72

1
2
1调用方式:
2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
1 var Person = jClass({  
2
3init: function(name) {  
4
5this.name = name;  
6
7},  
8
9getName: function() {  
10
11return this.name;  
12
13}  
14
15});  
16
17var Employee = jClass(Person, {  
18
19init: function(name, employeeID) {  
20
21// 调用父类的原型函数init,注意使用apply函数修改init的this指向  
22
23this.base.init.apply(this, [name]);  
24
25this.employeeID = employeeID;  
26
27},  
28
29getEmployeeID: function() {  
30
31return this.employeeID;  
32
33},  
34
35getName: function() {  
36
37// 调用父类的原型函数getName  
38
39return "Employee name: " + this.base.getName.apply(this);  
40
41}  
42
43});  
44
45var zhang = new Employee("ZhangSan", "1234");  
46
47console.log(zhang.getName()); // "Employee name: ZhangSan"  
48
49
50

 

目前为止,我们已经修正了在第一章手工实现继承的种种弊端。 通过我们自定义的jClass函数来创建类和子类,通过原型方法init初始化数据, 通过实例属性base来调用父类的原型函数。

唯一的缺憾是调用父类的代码太长,并且不好理解, 如果能够按照如下的方式调用岂不是更妙:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1 var Employee = jClass(Person, {  
2
3init: function(name, employeeID) {  
4
5// 如果能够这样调用,就再好不过了  
6
7this.base(name);  
8
9this.employeeID = employeeID;  
10
11}  
12
13});  
14
15
16

 

优化jClass函数

 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
1 // 当前是否处于创建类的阶段  
2
3var initializing = false;  
4
5function jClass(baseClass, prop) {  
6
7// 只接受一个参数的情况 - jClass(prop)  
8
9if (typeof (baseClass) === "object") {  
10
11prop = baseClass;  
12
13baseClass = null;  
14
15}  
16
17// 本次调用所创建的类(构造函数)  
18
19function F() {  
20
21// 如果当前处于实例化类的阶段,则调用init原型函数  
22
23if (!initializing) {  
24
25// 如果父类存在,则实例对象的baseprototype指向父类的原型  
26
27// 这就提供了在实例对象中调用父类方法的途径  
28
29if (baseClass) {  
30
31this.baseprototype = baseClass.prototype;  
32
33}  
34
35this.init.apply(this, arguments);  
36
37}  
38
39}  
40
41// 如果此类需要从其它类扩展  
42
43if (baseClass) {  
44
45initializing = true;  
46
47F.prototype = new baseClass();  
48
49F.prototype.constructor = F;  
50
51initializing = false;  
52
53}  
54
55// 覆盖父类的同名函数  
56
57for (var name in prop) {  
58
59if (prop.hasOwnProperty(name)) {  
60
61// 如果此类继承自父类baseClass并且父类原型中存在同名函数name  
62
63if (baseClass &&  
64
65typeof (prop[name]) === "function" &&  
66
67typeof (F.prototype[name]) === "function") {  
68
69// 重定义函数name -  
70
71// 首先在函数上下文设置this.base指向父类原型中的同名函数  
72
73// 然后调用函数prop[name],返回函数结果  
74
75// 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,  
76
77// 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。  
78
79// 这是JavaScript框架开发中常用的技巧。  
80
81F.prototype[name] = (function(name, fn) {  
82
83return function() {  
84
85this.base = baseClass.prototype[name];  
86
87return fn.apply(this, arguments);  
88
89};  
90
91})(name, prop[name]);  
92
93} else {  
94
95F.prototype[name] = prop[name];  
96
97}  
98
99}  
100
101}  
102
103return F;  
104
105};  
106
107
108

1
2
1此时,创建类与子类以及调用方式都显得非常优雅,请看:
2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
1 var Person = jClass({  
2
3init: function(name) {  
4
5this.name = name;  
6
7},  
8
9getName: function() {  
10
11return this.name;  
12
13}  
14
15});  
16
17var Employee = jClass(Person, {  
18
19init: function(name, employeeID) {  
20
21this.base(name);  
22
23this.employeeID = employeeID;  
24
25},  
26
27getEmployeeID: function() {  
28
29return this.employeeID;  
30
31},  
32
33getName: function() {  
34
35return "Employee name: " + this.base();  
36
37}  
38
39});  
40
41var zhang = new Employee("ZhangSan", "1234");  
42
43console.log(zhang.getName()); // "Employee name: ZhangSan"  
44
45
46

 

至此,我们已经创建了一个完善的函数jClass, 帮助我们在JavaScript中以比较优雅的方式实现类和继承。

在以后的章节中,我们会陆续分析网上一些比较流行的JavaScript类和继承的实现。 不过万变不离其宗,那些实现也无非把我们这章中提到的概念颠来簸去的“炒作”, 为的就是一种更优雅的调用方式。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++回调函数

2022-1-11 12:36:11

病毒疫情

援宜医疗队正式与宜昌三院并轨交接

2020-2-14 8:08:00

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索