JavaScript继承详解(一)

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

面向对象与基于对象

几乎每个开发人员都有面向对象语言(比如C++、C#、Java)的开发经验。 在传统面向对象的语言中,有两个非常重要的概念 – 类和实例。 类定义了一类事物公共的行为和方法;而实例则是类的一个具体实现。 我们还知道,面向对象编程有三个重要的概念 – 封装、继承和多态。

但是在JavaScript的世界中,所有的这一切特性似乎都不存在。 因为JavaScript本身不是面向对象的语言,而是基于对象的语言。 这里面就有一些有趣的特性,比如JavaScript中所有事物都是对象, 包括字符串、数组、日期、数字,甚至是函数,比如下面这个例子:


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
1// 定义一个函数 - add
2      
3
4      
5
6
7        function 
8        add(a, b) {
9      
10
11      
12
13
14            
15        add.invokeTimes++;
16      
17
18      
19
20
21            
22        return 
23        a + b;
24      
25
26      
27
28
29        }
30      
31
32      
33
34
35        // 因为函数本身也是对象,这里为函数add定义一个属性,用来记录此函数被调用的次数
36      
37
38      
39
40
41        add.invokeTimes = 0;
42      
43
44      
45
46
47        add(1 + 1);
48      
49
50      
51
52
53        add(2 + 3);
54      
55
56      
57
58
59        console.log(add.invokeTimes);
60        // 2
61

 

模拟JavaScript中类和继承

在面向对象的语言中,我们使用类来创建一个自定义对象。然而JavaScript中所有事物都是对象,
那么用什么办法来创建自定义对象呢?

这就需要引入另外一个概念 – 原型(prototype),我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模版(prototype)的一个拷贝 (实际上不是拷贝而是链接,只不过这种链接是不可见,给人们的感觉好像是拷贝)。

让我们看一下通过prototype创建自定义对象的一个例子:


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
1// 构造函数
2      
3
4      
5
6
7           
8        function 
9        Person(name, sex) {
10      
11
12      
13
14
15               
16        this
17        .name = name;
18      
19
20      
21
22
23               
24        this
25        .sex = sex;
26      
27
28      
29
30
31           
32        }
33      
34
35      
36
37
38           
39        // 定义Person的原型,原型中的属性可以被自定义对象引用
40      
41
42      
43
44
45           
46        Person.prototype = {
47      
48
49      
50
51
52               
53        getName:
54        function
55        () {
56      
57
58      
59
60
61                   
62        return 
63        this
64        .name;
65      
66
67      
68
69
70               
71        },
72      
73
74      
75
76
77               
78        getSex:
79        function
80        () {
81      
82
83      
84
85
86                   
87        return 
88        this
89        .sex;
90      
91
92      
93
94
95               
96        }
97      
98
99      
100
101
102           
103        }
104

这里我们把函数Person称为构造函数,也就是创建自定义对象的函数。可以看出,JavaScript通过构造函数和原型的方式模拟实现了类的功能。 
创建自定义对象(实例化类)的代码:


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
1var 
2        zhang =  
3        new 
4        Person(
5        "ZhangSan"
6        ,
7        "man"
8        );
9      
10
11      
12
13
14        console.log(zhang.getName());
15        // "ZhangSan"
16      
17
18      
19
20
21        var 
22        chun =  
23        new 
24        Person(
25        "ChunHua"
26        ,
27        "woman"
28        );
29      
30
31      
32
33
34        console.log(chun.getName());
35        // "ChunHua"
36

当代码var zhang = new Person("ZhangSan", "man")执行时,其实内部做了如下几件事情:

  • 创建一个空白对象(new Object())。
  • 拷贝Person.prototype中的属性(键值对)到这个空对象中(我们前面提到,内部实现时不是拷贝而是一个隐藏的链接)。
  • 将这个对象通过this关键字传递到构造函数中并执行构造函数。
  • 将这个对象赋值给变量zhang。

 

为了证明prototype模版并不是被拷贝到实例化的对象中,而是一种链接的方式,请看如下代码:


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
1function 
2        Person(name, sex) {
3      
4
5      
6
7
8            
9        this
10        .name = name;
11      
12
13      
14
15
16            
17        this
18        .sex = sex;
19      
20
21      
22
23
24        }
25      
26
27      
28
29
30        Person.prototype.age = 20;
31      
32
33      
34
35
36        var 
37        zhang =  
38        new 
39        Person(
40        "ZhangSan"
41        ,
42        "man"
43        );
44      
45
46      
47
48
49        console.log(zhang.age);
50        // 20
51      
52
53      
54
55
56        // 覆盖prototype中的age属性
57      
58
59      
60
61
62        zhang.age = 19;
63      
64
65      
66
67
68        console.log(zhang.age);
69        // 19
70      
71
72      
73
74
75        delete 
76        zhang.age;
77      
78
79      
80
81
82        // 在删除实例属性age后,此属性值又从prototype中获取
83      
84
85      
86
87
88        console.log(zhang.age);
89        // 20
90

这种在JavaScript内部实现的隐藏的prototype链接,是JavaScript赖以生存的温润土壤, 也是模拟实现继承的基础。

 

如何在JavaScript中实现简单的继承? 
下面的例子将创建一个雇员类Employee,它从Person继承了原型prototype中的所有属性。


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
1function 
2        Employee(name, sex, employeeID) {
3      
4
5      
6
7
8            
9        this
10        .name = name;
11      
12
13      
14
15
16            
17        this
18        .sex = sex;
19      
20
21      
22
23
24            
25        this
26        .employeeID = employeeID;
27      
28
29      
30
31
32        }
33      
34
35      
36
37
38        // 将Employee的原型指向Person的一个实例
39      
40
41      
42
43
44        // 因为Person的实例可以调用Person原型中的方法, 所以Employee的实例也可以调用Person原型中的所有属性。
45      
46
47      
48
49
50        Employee.prototype =  
51        new 
52        Person();
53      
54
55      
56
57
58        Employee.prototype.getEmployeeID =  
59        function
60        () {
61      
62
63      
64
65
66            
67        return 
68        this
69        .employeeID;
70      
71
72      
73
74
75        };
76      
77
78      
79
80
81        var 
82        zhang =  
83        new 
84        Employee(
85        "ZhangSan"
86        ,
87        "man"
88        ,
89        "1234"
90        );
91      
92
93      
94
95
96        console.log(zhang.getName());
97        // "ZhangSan
98

 

上面关于继承的实现很粗糙,并且存在很多问题:

  • 在创建Employee构造函数和原型(以后简称类)时,就对Person进行了实例化,这是不合适的。
  • Employee的构造函数没法调用父类Person的构造函数,导致在Employee构造函数中对name和sex属性的重复赋值。
  • Employee中的函数会覆盖Person中的同名函数,没有重载的机制(和上一条是一个类型的问题)。
  • 创建JavaScript类的语法过于零散,不如C#/Java中的语法优雅。
  • 实现中有constructor属性的指向错误,这个会在第二篇文章中讨论。

我们会在第三章完善这个例子。

 

JavaScript继承的实现

正因为JavaScript本身没有完整的类和继承的实现,并且我们也看到通过手工实现的方式存在很多问题, 因此对于这个富有挑战性的任务网上已经有很多实现了:

  • Douglas Crockford – Prototypal Inheritance in JavaScript
  • Douglas Crockford – Classical Inheritance in JavaScript
  • John Resig – Simple JavaScript Inheritance
  • Dean Edwards – A Base Class for JavaScript Inheritance
  • Prototype
  • Mootools
  • Extjs

这个系列的文章将会逐一深入分析这些实现,最终达到对JavaScript中如何实现类和继承有一个深入的了解。

 

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

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全技术

SpringBoot配置HTTPS

2022-1-12 12:36:11

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