JavaScript继承详解(五)

在本章中,我们将分析John Resig关于JavaScript继承的一个实现 - Simple JavaScript Inheritance

John Resig作为jQuery的创始人而声名在外。是《Pro JavaScript Techniques》的作者,而且Resig将会在今年秋天推出一本书《JavaScript Secrets》,非常期待。

调用方式

调用方式非常优雅:

注意:代码中的Class、extend、_super都是自定义的对象,我们会在后面的代码分析中详解。

 var Person = Class.extend({
            // init是构造函数
            init: function(name) {
                this.name = name;
            },
            getName: function() {
                return this.name;
            }
        });
        // Employee类从Person类继承
        var Employee = Person.extend({
            // init是构造函数
            init: function(name, employeeID) {
                //  在构造函数中调用父类的构造函数
                this._super(name);
                this.employeeID = employeeID;
            },
            getEmployeeID: function() {
                return this.employeeID;
            },
            getName: function() {
                //  调用父类的方法
                return "Employee name: " + this._super();
            }
        });

        var zhang = new Employee("ZhangSan", "1234");
        console.log(zhang.getName());   // "Employee name: ZhangSan"

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

代码分析

为了一个漂亮的调用方式,内部实现的确复杂了很多,不过这些也是值得的 - 一个人的思考带给了无数程序员快乐的微笑 - 嘿嘿,有点肉麻。

不过其中的一段代码的确迷惑我一段时间:

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

下面我会对其中的for-in循环进行解读,把自执行的匿名方法用一个局部函数来替换, 这样有利于我们看清真相:

 (function() {
            var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
            this.Class = function() { };
            Class.extend = function(prop) {
                var _super = this.prototype;
                initializing = true;
                var prototype = new this();
                initializing = false;

                // 如果父类和子类有同名方法,并且子类中此方法(name)通过_super调用了父类方法
                // - 则重新定义此方法
                function fn(name, fn) {
                    return function() {
                        // 将实例方法_super保护起来。
                        // 个人觉得这个地方没有必要,因为每次调用这样的函数时都会对this._super重新定义。
                        var tmp = this._super;
                        // 在执行子类的实例方法name时,添加另外一个实例方法_super,此方法指向父类的同名方法
                        this._super = _super[name];
                        // 执行子类的方法name,注意在方法体内this._super可以调用父类的同名方法
                        var ret = fn.apply(this, arguments);
                        this._super = tmp;

                        // 返回执行结果
                        return ret;
                    };
                }
                // 拷贝prop中的所有属性到子类原型中
                for (var name in prop) {
                    // 如果prop和父类中存在同名的函数,并且此函数中使用了_super方法,则对此方法进行特殊处理 - fn
                    // 否则将此方法prop[name]直接赋值给子类的原型
                    if (typeof prop[name] === "function" &&
                            typeof _super[name] === "function" && fnTest.test(prop[name])) {
                        prototype[name] = fn(name, prop[name]);
                    } else {
                        prototype[name] = prop[name];
                    }
                }

                function Class() {
                    if (!initializing && this.init) {
                        this.init.apply(this, arguments);
                    }
                }
                Class.prototype = prototype;
                Class.constructor = Class;
                Class.extend = arguments.callee;
                return Class;
            };
        })();

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

下来我们就把jClass改造成和这里的Class具有相同的行为。

我们的实现

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

(function() {
            // 当前是否处于创建类的阶段
            var initializing = false;
            jClass = function() { };
            jClass.extend = function(prop) {
                // 如果调用当前函数的对象(这里是函数)不是Class,则是父类
                var baseClass = null;
                if (this !== jClass) {
                    baseClass = this;
                }
                // 本次调用所创建的类(构造函数)
                function F() {
                    // 如果当前处于实例化类的阶段,则调用init原型函数
                    if (!initializing) {
                        // 如果父类存在,则实例对象的baseprototype指向父类的原型
                        // 这就提供了在实例对象中调用父类方法的途径
                        if (baseClass) {
                            this._superprototype = baseClass.prototype;
                        }
                        this.init.apply(this, arguments);
                    }
                }
                // 如果此类需要从其它类扩展
                if (baseClass) {
                    initializing = true;
                    F.prototype = new baseClass();
                    F.prototype.constructor = F;
                    initializing = false;
                }
                // 新创建的类自动附加extend函数
                F.extend = arguments.callee;

                // 覆盖父类的同名函数
                for (var name in prop) {
                    if (prop.hasOwnProperty(name)) {
                        // 如果此类继承自父类baseClass并且父类原型中存在同名函数name
                        if (baseClass &&
                        typeof (prop[name]) === "function" &&
                        typeof (F.prototype[name]) === "function" &&
                        /\b_super\b/.test(prop[name])) {
                            // 重定义函数name -
                            // 首先在函数上下文设置this._super指向父类原型中的同名函数
                            // 然后调用函数prop[name],返回函数结果
                            // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,
                            // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。
                            // 这是JavaScript框架开发中常用的技巧。
                            F.prototype[name] = (function(name, fn) {
                                return function() {
                                    this._super = baseClass.prototype[name];
                                    return fn.apply(this, arguments);
                                };
                            })(name, prop[name]);
                        } else {
                            F.prototype[name] = prop[name];
                        }
                    }
                }
                return F;
            };
        })();
        // 经过改造的jClass
        var Person = jClass.extend({
            init: function(name) {
                this.name = name;
            },
            getName: function(prefix) {
                return prefix + this.name;
            }
        });
        var Employee = Person.extend({
            init: function(name, employeeID) {
                //  调用父类的方法
                this._super(name);
                this.employeeID = employeeID;
            },
            getEmployeeIDName: function() {
                // 注意:我们还可以通过这种方式调用父类中的其他函数
                var name = this._superprototype.getName.call(this, "Employee name: ");
                return name + ", Employee ID: " + this.employeeID;
            },
            getName: function() {
                //  调用父类的方法
                return this._super("Employee name: ");
            }
        });

        var zhang = new Employee("ZhangSan", "1234");
        console.log(zhang.getName());   // "Employee name: ZhangSan"
        console.log(zhang.getEmployeeIDName()); // "Employee name: ZhangSan, Employee ID: 1234"

时间: 2024-07-30 05:13:38

JavaScript继承详解(五)的相关文章

JavaScript继承详解(四)

在本章中,我们将分析Douglas Crockford关于JavaScript继承的一个实现 - Classical Inheritance in JavaScript. Crockford是JavaScript开发社区最知名的权威,是JSON.JSLint.JSMin和ADSafe之父,是<JavaScript: The Good Parts>的作者. 现在是Yahoo的资深JavaScript架构师,参与YUI的设计开发. 这里有一篇文章详细介绍了Crockford的生平和著作. 当然Cr

JavaScript继承详解(一)

面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++.C#.Java)的开发经验. 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例. 类定义了一类事物公共的行为和方法:而实例则是类的一个具体实现. 我们还知道,面向对象编程有三个重要的概念 - 封装.继承和多态. 但是在JavaScript的世界中,所有的这一切特性似乎都不存在. 因为JavaScript本身不是面向对象的语言,而是基于对象的语言. 这里面就有一些有趣的特性,比如JavaScript中所有事物都是对象, 包

JavaScript继承详解(二)

这一章我们将会重点介绍JavaScript中几个重要的属性(this.constructor.prototype), 这些属性对于我们理解如何实现JavaScript中的类和继承起着至关重要的作用. this this表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window: 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用. 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向. 先看一个在全局作用范围内使用this

JavaScript继承详解(三)

在第一章中,我们使用构造函数和原型的方式在JavaScript的世界中实现了类和继承, 但是存在很多问题.这一章我们将会逐一分析这些问题,并给出解决方案. 注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法. 首先让我们来回顾一下第一章中介绍的例子: function Person(name) { this.name = name; } Person.prototype = { getName: function() { return this.

javascript继承详解

常见继承分两种,一种接口继承,继承方法签名:一种实现继承,继承实际方法.js只支持后一种. 1原型链 首先看原型.构造函数.实例的关系.如果我们让一个函数的原型对象等于另一个的实例,然后另一个的原型对象又等于另一个的实例,以此类推,就构成了原型链. 代码: function SuperType(){ this.name=true; } superType.prototype.getValue=function(){ return this.name; } function SubType(){

JavaScript prototype 详解(对prototype 使用的一些讲解)

对JavaScript有一定了解的你,对jquery不陌生吧,那你看jQuery源代码的时候对prototype 也一定有见过,如果对prototype有疑问或者想更深入的去了解与使用它,欢迎你继续往下阅读. 最初的用法是, 为了避免方法在构造器里随机数据被实例化时而产生重复的副本  后来被用在"继承"上面了, 注意, JS语义上是没有继承的, 这里说的是人为的实现.对于下面对JavaScript中类型名称叫做"对象"."函数"."类型

JavaScript事件详解-zepto的事件实现

zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们可以看到,整个event模块,事件绑定核心就是on和off,还有一个trigger用来触发,类观察者模式,可以先看看汤姆大叔的深入理解JavaScript系列(32):设计模式之观察者模式,其余皆为实现的处理函数.首先来个demo: $("#btn").on("click&quo

JavaScript 对象详解

1 创建对象的方法 最常见的创建对象方法是通过new调用构造函数,此外还有一个方法就是使用Object.create(),将一个原型对象作为参数传递给Object.create也可以创建一个继承了其属性的新对象.但是和使用new创建对象还有一点差别,那就是构造函数不会被调用.所以如果使用这种方法创建一个foo新对象,其foo.f是undefined的: function Foo(z) { this.f = z; } Foo.prototype.add = function(x, y) { ret

javascript运动详解

javascript运动详解 本文给大家详细介绍下如何使用javascript来实现运动效果,总结的十分全面,附上各种效果的详细示例和演示图,有需要的小伙伴可以参考下. 物体运动原理:通过改变物体的位置,而发生移动变化. 方法: 1.运动的物体使用绝对定位 2.通过改变定位物体的属性(left.right.top.bottom)值来使物体移动.例如向右或左移动可以使用offsetLeft(offsetRight)来控制左右移动. 步骤: 1.开始运动前,先清除已有定时器 (因为:是连续点击按钮,