JavaScript继承详解(五)

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

在本章中,我们将分析John Resig关于JavaScript继承的一个实现 – Simple JavaScript Inheritance。 
John Resig作为jQuery的创始人而声名在外。是《Pro JavaScript Techniques》的作者,而且Resig将会在今年秋天推出一本书《JavaScript Secrets》,非常期待。

调用方式

调用方式非常优雅: 
注意:代码中的Class、extend、_super都是自定义的对象,我们会在后面的代码分析中详解。


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
1        var Person = Class.extend({
2            // init是构造函数
3            init: function(name) {
4                this.name = name;
5            },
6            getName: function() {
7                return this.name;
8            }
9        });
10        // Employee类从Person类继承
11        var Employee = Person.extend({
12            // init是构造函数
13            init: function(name, employeeID) {
14                //  在构造函数中调用父类的构造函数
15                this._super(name);
16                this.employeeID = employeeID;
17            },
18            getEmployeeID: function() {
19                return this.employeeID;
20            },
21            getName: function() {
22                //  调用父类的方法
23                return "Employee name: " + this._super();
24            }
25        });
26
27        var zhang = new Employee("ZhangSan", "1234");
28        console.log(zhang.getName());   // "Employee name: ZhangSan"
29        
30

1
2
1 说实话,对于完成本系列文章的目标-继承-而言,真找不到什么缺点。方法一如jQuery一样简洁明了。
2

 

代码分析

为了一个漂亮的调用方式,内部实现的确复杂了很多,不过这些也是值得的 – 一个人的思考带给了无数程序员快乐的微笑 – 嘿嘿,有点肉麻。 
不过其中的一段代码的确迷惑我一段时间:


1
2
3
1        fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
2        
3

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
51
52
53
54
55
56
57
58
59
60
61
1        // 自执行的匿名函数创建一个上下文,避免引入全局变量
2        (function() {
3            // initializing变量用来标示当前是否处于类的创建阶段,
4            // - 在类的创建阶段是不能调用原型方法init的
5            // - 我们曾在本系列的第三篇文章中详细阐述了这个问题
6            // fnTest是一个正则表达式,可能的取值为(/\b_super\b/ 或 /.*/)
7            // - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的情况
8            // - 不过我对IE7.0,Chrome2.0,FF3.5进行了测试,此测试都返回true。
9            // - 所以我想这样对fnTest赋值大部分情况下也是对的:fnTest = /\b_super\b/;
10            var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
11            // 基类构造函数
12            // 这里的this是window,所以这整段代码就向外界开辟了一扇窗户 - window.Class
13            this.Class = function() { };
14            // 继承方法定义
15            Class.extend = function(prop) {
16                // 这个地方很是迷惑人,还记得我在本系列的第二篇文章中提到的么
17                // - this具体指向什么不是定义时能决定的,而是要看此函数是怎么被调用的
18                // - 我们已经知道extend肯定是作为方法调用的,而不是作为构造函数
19                // - 所以这里this指向的不是Object,而是Function(即是Class),那么this.prototype就是父类的原型对象
20                // - 注意:_super指向父类的原型对象,我们会在后面的代码中多次碰见这个变量
21                var _super = this.prototype;
22                // 通过将子类的原型指向父类的一个实例对象来完成继承
23                // - 注意:this是基类构造函数(即是Class)
24                initializing = true;
25                var prototype = new this();
26                initializing = false;
27                // 我觉得这段代码是经过作者优化过的,所以读起来非常生硬,我会在后面详解
28                for (var name in prop) {
29                    prototype[name] = typeof prop[name] == "function" &&
30                        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
31                        (function(name, fn) {
32                            return function() {
33                                var tmp = this._super;
34                                this._super = _super[name];
35                                var ret = fn.apply(this, arguments);
36                                this._super = tmp;
37                                return ret;
38                            };
39                        })(name, prop[name]) :
40                        prop[name];
41                }
42                // 这个地方可以看出,Resig很会伪装哦
43                // - 使用一个同名的局部变量来覆盖全局变量,很是迷惑人
44                // - 如果你觉得拗口的话,完全可以使用另外一个名字,比如function F()来代替function Class()
45                // - 注意:这里的Class不是在最外层定义的那个基类构造函数
46                function Class() {
47                    // 在类的实例化时,调用原型方法init
48                    if (!initializing && this.init)
49                        this.init.apply(this, arguments);
50                }
51                // 子类的prototype指向父类的实例(完成继承的关键)
52                Class.prototype = prototype;
53                // 修正constructor指向错误
54                Class.constructor = Class;
55                // 子类自动获取extend方法,arguments.callee指向当前正在执行的函数
56                Class.extend = arguments.callee;
57                return Class;
58            };
59        })();
60        
61

1
2
1 下面我会对其中的for-in循环进行解读,把自执行的匿名方法用一个局部函数来替换, 这样有利于我们看清真相:
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
51
1        (function() {
2            var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
3            this.Class = function() { };
4            Class.extend = function(prop) {
5                var _super = this.prototype;
6                initializing = true;
7                var prototype = new this();
8                initializing = false;
9
10                // 如果父类和子类有同名方法,并且子类中此方法(name)通过_super调用了父类方法
11                // - 则重新定义此方法
12                function fn(name, fn) {
13                    return function() {
14                        // 将实例方法_super保护起来。
15                        // 个人觉得这个地方没有必要,因为每次调用这样的函数时都会对this._super重新定义。
16                        var tmp = this._super;
17                        // 在执行子类的实例方法name时,添加另外一个实例方法_super,此方法指向父类的同名方法
18                        this._super = _super[name];
19                        // 执行子类的方法name,注意在方法体内this._super可以调用父类的同名方法
20                        var ret = fn.apply(this, arguments);
21                        this._super = tmp;
22                        
23                        // 返回执行结果
24                        return ret;
25                    };
26                }
27                // 拷贝prop中的所有属性到子类原型中
28                for (var name in prop) {
29                    // 如果prop和父类中存在同名的函数,并且此函数中使用了_super方法,则对此方法进行特殊处理 - fn
30                    // 否则将此方法prop[name]直接赋值给子类的原型
31                    if (typeof prop[name] === "function" &&
32                            typeof _super[name] === "function" && fnTest.test(prop[name])) {
33                        prototype[name] = fn(name, prop[name]);
34                    } else {
35                        prototype[name] = prop[name];
36                    }
37                }
38
39                function Class() {
40                    if (!initializing && this.init) {
41                        this.init.apply(this, arguments);
42                    }
43                }
44                Class.prototype = prototype;
45                Class.constructor = Class;
46                Class.extend = arguments.callee;
47                return Class;
48            };
49        })();
50        
51

 

写到这里,大家是否觉得Resig的实现和我们在第三章一步一步实现的jClass很类似。 其实在写这一系列的文章之前,我已经对prototype、mootools、extjs、 jQuery-Simple-Inheritance、Crockford-Classical-Inheritance这些实现有一定的了解,并且大部分都在实际项目中使用过。 在第三章中实现jClass也参考了Resig的实现,在此向Resig表示感谢。 
下来我们就把jClass改造成和这里的Class具有相同的行为。

我们的实现

将我们在第三章实现的jClass改造成目前John Resig所写的形式相当简单,只需要修改其中的两三行就行了:


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
1        (function() {
2            // 当前是否处于创建类的阶段
3            var initializing = false;
4            jClass = function() { };
5            jClass.extend = function(prop) {
6                // 如果调用当前函数的对象(这里是函数)不是Class,则是父类
7                var baseClass = null;
8                if (this !== jClass) {
9                    baseClass = this;
10                }
11                // 本次调用所创建的类(构造函数)
12                function F() {
13                    // 如果当前处于实例化类的阶段,则调用init原型函数
14                    if (!initializing) {
15                        // 如果父类存在,则实例对象的baseprototype指向父类的原型
16                        // 这就提供了在实例对象中调用父类方法的途径
17                        if (baseClass) {
18                            this._superprototype = baseClass.prototype;
19                        }
20                        this.init.apply(this, arguments);
21                    }
22                }
23                // 如果此类需要从其它类扩展
24                if (baseClass) {
25                    initializing = true;
26                    F.prototype = new baseClass();
27                    F.prototype.constructor = F;
28                    initializing = false;
29                }
30                // 新创建的类自动附加extend函数
31                F.extend = arguments.callee;
32
33                // 覆盖父类的同名函数
34                for (var name in prop) {
35                    if (prop.hasOwnProperty(name)) {
36                        // 如果此类继承自父类baseClass并且父类原型中存在同名函数name
37                        if (baseClass &&
38                        typeof (prop[name]) === "function" &&
39                        typeof (F.prototype[name]) === "function" &&
40                        /\b_super\b/.test(prop[name])) {
41                            // 重定义函数name -
42                            // 首先在函数上下文设置this._super指向父类原型中的同名函数
43                            // 然后调用函数prop[name],返回函数结果
44                            // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,
45                            // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。
46                            // 这是JavaScript框架开发中常用的技巧。
47                            F.prototype[name] = (function(name, fn) {
48                                return function() {
49                                    this._super = baseClass.prototype[name];
50                                    return fn.apply(this, arguments);
51                                };
52                            })(name, prop[name]);
53                        } else {
54                            F.prototype[name] = prop[name];
55                        }
56                    }
57                }
58                return F;
59            };
60        })();
61        // 经过改造的jClass
62        var Person = jClass.extend({
63            init: function(name) {
64                this.name = name;
65            },
66            getName: function(prefix) {
67                return prefix + this.name;
68            }
69        });
70        var Employee = Person.extend({
71            init: function(name, employeeID) {
72                //  调用父类的方法
73                this._super(name);
74                this.employeeID = employeeID;
75            },
76            getEmployeeIDName: function() {
77                // 注意:我们还可以通过这种方式调用父类中的其他函数
78                var name = this._superprototype.getName.call(this, "Employee name: ");
79                return name + ", Employee ID: " + this.employeeID;
80            },
81            getName: function() {
82                //  调用父类的方法
83                return this._super("Employee name: ");
84            }
85        });
86
87        var zhang = new Employee("ZhangSan", "1234");
88        console.log(zhang.getName());   // "Employee name: ZhangSan"
89        console.log(zhang.getEmployeeIDName()); // "Employee name: ZhangSan, Employee ID: 1234"
90        
91

  

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

Ubuntu下安装Mysql 以及mysql-query-browser

2022-1-11 12:36:11

病毒疫情

福建省新型冠状病毒肺炎疫情情况

2020-7-13 9:05:00

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