JavaScript继承详解(五)

2023-10-27

http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html

在本章中,我们将分析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"
        
JUST COOL!
分类:  Javascript
8
3
(请您对文章做出评价)
« 上一篇: JavaScript继承详解(四)
» 下一篇: ExtAspNet新版本发布,集成Extjs3.0,兼容IE浏览器
posted @  2009-07-14 17:17  三生石上 阅读( 6099) 评论( 8编辑  收藏

评论
  
#1楼   2009-07-14 17:27 |  iTech   
  
#2楼   2009-07-15 16:57 |  大气象   
js也可以搞这么复杂的语法,不错,我得先找本书瞧瞧。
我现在还仅限于一般的用法,想不到js语法也搞得这么高深。
  
#3楼 [ 楼主2009-07-15 17:08 |  sanshi   
@大气象
语法越是灵活的语言,越能写出稀奇古怪的代码。呵呵
  
#4楼   2009-08-26 14:36 |  love4J   
三石兄,推荐几本你看过的比较好的js书籍吧
  
#5楼 [ 楼主2009-08-26 14:42 |  sanshi   
引用 love4J:三石兄,推荐几本你看过的比较好的js书籍吧

买过两本JS的书,一本是 Douglas Crockford的JavaScript:The Good Part, 还有一本是 John Resig的 Pro JavaScript(感觉这本书前半部分写的挺好的,后半部分有凑页数的感觉没啥意思)。

其实网上有很多很好的资源
  
#6楼   2010-08-03 22:21 |  BlueDream   
其实我现在也没搞懂
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
这段正则的意义. 是为了防止Function.prototype.toString被覆写? 还是?
  
#7楼   2011-09-05 16:40 |  hua_js   
对楼主这里的有关继承的详细代码注释,有些相见恨晚。。
楼主这里写道:
// 个人觉得这个地方没有必要,因为每次调用这样的函数时都会对this._super重新定义。
var tmp = this._super;

这个地方我个人感觉是必要的,因为派生类中的也可以有_super的方法,比如:
var Employee = Person.extend({
init: function (name, employeeID) {
this._super(name);
this.employeeID = employeeID;
},
getEmployeeID: function () {
return this.employeeID;
},
_super:function () {
return "this is supert";
}
});
这样不就需要暂存_super方法,然后再恢复了吗?
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • AngularJS中的$http缓存以及处理多个$http请求

    在AngularJS的实际项目中 经常需要处理多个 http请求 每个 http请求返回一个promise 我们可以把多个promise放到 q all 方法接受的一个数组实参中去 处理多个 http请求 angular module ap
  • C++ 返回值为数组的函数

    首先整理一下new的用法 1 new 分配一个数的空间 2 new 分配一个数组的空间 空间大小在 中指定 3 当使用new运算符定义一个多维数组变量或数组对象时 它产生一个指向数组第一个元素的指针 返回的类型保持了除最左边 维数外的所有维
  • 使用 MBean 和 日志查看 Tomcat 线程池核心属性数据

    文章目录 CustomTomcatThreadPoolMBean CustomTomcatThreadPool CustomTomcatThreadPoolMBean com qww config public interface Cust
  • Python爬虫之JS逆向分析技巧

    Python爬虫之JS逆向分析技巧 当我们使用爬虫时 遇到被JS加密的参数怎么办 有人会说用Selenium不就可以了么 但是Selenium执行又没效率怎么办 答案是 使用Python的execjs库直接执行JS脚本来获取加密后的参数 J
  • 【Linux操作小试牛刀】如何使用systemctl 命令管理 systemd服务

    前言 需求 当Linux中有许多系统服务 无法通过外部管理工具或者接口开启 关闭 需要手动管理时就需要用到systemctl命令 Systemd是基于Linux的操作系统的系统和服务管理器 用于管理服务 Systemd比旧的Sysvinit
  • [结构体]Cpp里结构体的几种常用写法

    1 定义并声明结构体变量 struct Date int year int month int day int main Date date 声明并定义了一个Date类型的变量date date year 2023 date month 9
  • Ubuntu 安装指定版本 python

    场景 在构建 docker 镜像的时候为了与 TensorFlow Pytorch 或者其他程序能兼容使用 需要安装指定版本的 python 常用的安装命令 apt get 有时候不能很 精确 指定 甚至是找不到源 隔山修路 遇水搭桥 py
  • golang和rust嵌入式开发初探

    本文简单的介绍了golang和rust语言在openwrt系统 mips架构下的交叉编译 环境 主机 系统 内核 架构 host主机 Centos 7 2 linux 3 10 0 327 x86 64 target主机 openwrt 1
  • unity拖拽drag_Unity全方位拖拽物体攻略

    Unity中UGUI控件和3D物体拖拽实现 基本原理 Unity拖拽的基本原理 射线检测 鼠标位置增量转换为统一空间的位置增量 将位置增量添加到拖拽物体原位置上 统一空间指的是将所有向量转换为同一空间下再进行计算 项目演示 左测 UGUI
  • blender基础认识(选项开关、工具栏、视图等)

    文章目录 引言 一 大纲选项开关和保存启动文件 1 大纲选项 1 禁用选中 2 视图影藏 3 视图禁用 4 渲染禁用 2 保存启动文件 二 工具栏和侧边栏 1 左侧工具栏 2 右侧工具栏 三 视图 1 视角 2 缩放 3 拖拽 4 摄像机视
  • AD PCB导出Gerber文件(非常详细的步骤)

    当我们的PCB绘制好 并仔细检查后 就可以把文件交给工厂生产了 一般有两种方式 第一种最简单 就是直接将PCB文件压缩打包 发给工厂 发给工厂的途径一般有两种 一种是在其官网上提交 一种是在其开发的应用程序上提交 嘉立创工厂就可以在其开发的
  • Hibernate笔记_如何处理OO中的一些特点

    1 对象属性是复合数据类型 composite user type 这其实是OO中的aggregation 和 composition Embeddable Embedded span style font size 14px packag
  • linux ctrl+z之后如何恢复

    在linux中使用matlab的时候 常常用ctrl z将matlab挂起 一开始并不知道怎么处理 也关不掉 后来发现用fg再回车就可以将后台挂起程序切换的前台来
  • 如何利用github搭建个人网站(无需购买云服务器)

    请看原创 转载来源 1 建立GithubPage 这里的作用就是说在github上建立一个仓库 并且将它设置成github的网页模式 其实我们后面的域名只是跳转到这个仓库的页面 首先新建一个仓库 然后注意设置仓库名字时要和你的githubI
  • Graph Correspondence Transfer for Person Re-Identification论文笔记

    摘要 提出了GCT 图关系迁移 模型解决行人重识别问题 与现存的方法不一样 GCT将行人重识别视为一个离线的图匹配问题和一个在线的关系迁移问题 在训练过程中 通过patch级别的图匹配 在具有不同姿势对配置的正样本对中离线的学习得到一个关系
  • 详解numpy.random.randn函数

    文章目录 正态分布 函数原型 参数解析 该函数的注意事项 示例代码 示例结果 参考 正态分布曲线绘制代码 numpy的random模块中的randn函数用于从 标准正态 方差为1 均值为0的正态分布 分布返回一个 或多个 float类型数据
  • Ubuntu20.04下交叉编译树莓派能运行的c++程序(不含第三方库)

    参见博主之前的博客 里面生成了test目标文件 现在将这个目标文件传到树莓派上 运行出现如下报错信息 这里因为我的编译平台 x64 Ubuntu操作系统 和目标平台 ARM raspbian操作系统 所以前者编译出来的东西并不能在目标平台上
  • Ant 组件动态表单多行输入框设置禁止编辑

    deep textarea ant input background color f5f5f5 cursor not allowed
  • ModelAndView: materialized View is [null];和Action的onSubmit()方法不被执行

    ModelAndView materialized View is null
  • JavaScript继承详解(五)

    http www cnblogs com sanshi archive 2009 07 14 1523523 html 在本章中 我们将分析John Resig关于JavaScript继承的一个实现 Simple JavaScript In