解析John Resig Simple JavaScript Inheritance代码


Posted in Javascript onDecember 03, 2012

由于作者翻译会加入 自己的理解 以便自己学习和使用, 如果英文好的同学可看下面   如文章中有翻译错误还请留言. 交流并改正. (:
======================Enein翻译=========================

        John Resig 写了一篇关于 JavaScript 里 类似其它语言的 "继承", 灵感来自于  base2 and PrototypeJS.  他为文章起名为"Simple JavaScript Inheritance" . 他使用的一些很巧妙的技术来实现 super 方法.
        你还可以看原文也会有详细的说明, 他也在他的 "Secrets of a JavaScript Ninja"里有所介绍. 在书中可能方法有一些不同, 它在Object中加入了subClass 方法, 而不是创建一个全局变量.
Original Script - John Resig Simple JavaScript Inheritance
下面是原谅代码, 我移除了一些注释使用它看起来更清晰.

(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;
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];
}
function Class() {
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
Class.prototype = prototype;
Class.constructor = Class;
Class.extend = arguments.callee;
return Class;
};
})();

Breakdown of the Simple Inheritance script
下面我们来分析一下, 它是如何实现和有哪些技术被使用.

(function(){ // ... })();

首先我们创建一个自执行匿名函数, 为代码创建一个作用域.

      
var initializing = false

这 initializing 变量意思很直接, 它是boolean来检查Class Function(稍后介绍)什么时候被调用. 在创建实例时设置 initializing 为true/false 或者只是返回一个对象指向当前的原型链上来达到"继承"的目的.

如果我们创建一个实例(initializing == false), 正好Class有一个init方法, 这样 init 会自动执行。 再或者, 如果我们仅仅将它分配给原型上(initializing == true), 将不会发生什么, init 方法不会被执行。这样做是为了避免 每次调用构造方法都要执行 init 方法. (var prototype = new this());.

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

          这个fnTest的目的就是为了验证 class method 中是否使用了 "_super()" 调用. 这种技术叫做 " function decompilation(函数反编译)" 也叫做 "function serialisation(函数序列化)", Function serialisation 是在一个函数被转换成字符串时发生的. 现在很多浏览器都支持 toString 方法。

测试 Function serialisation, fnTest 使用一个匿名函数 funciton(){xyz;} 设置内容为 "xyz", 在转变成字符串后使用正则对 "xyz" 进行查找. 它将返回true (如果浏览器支持 function serialisation) 因为 函数将转变成字符串所以 "xyz" 也民属于字符串的一部分. 在这个例子中 fnTest 将返回 "/\b_super\b/", 另一种则返回 "/.*/" 如果浏览器不支持 function serialisation 则始终返回 true。(这个指的是原始代码中的fnTest.test)使用 fnTest 正则, 和 函数序列化技术, 我们能很容易方法中是否使用了 "_super" 如果它们使用, 则执行一些特殊方法. 反之正常.  这个特殊方法是为了避免在 父类与子类中同时出现同一个方法. 父类将会被覆盖. 

        浏览器不支持 Function serialisation 将会始终返回 true, 那么会始终对 _super 进行额外的操作, 导致这些新的方法不能在 _super 中使用. 这会有一些小的性能消耗. 但能保证在所有浏览器中 正常执行.

this.Class = function(){};

        创建一个空的构造方法, 放到全局变量中. 这将会是最上层的构造方法. 它没有定义内容, 或一个原型对象. 除了下面的 extends 方法. this 指的是window对象. 使 Class 变量为全局对象.

      
Class.extend = function(prop) { // ...}

        加入 extends 方法和一个简单的 prop(一个对象) 参数. 它将返回 新构造方法的原型 + 父对象的原型; 

var _super = this.prototype;

        将当前对象的原型对象存储在 _super中. this.prototype是被扩展对象的原型, 它可以访问父级方法在你需要的地方,  这个变量叫什么 _super , 是因为 super 是保留字. 尽管现在还没有应用起来.

initializing = true;var prototype = new this();initializing = false;

        实例 class 对象存储在 prototype 变量中, 但不执行 init 方法. 之前设置 initializing 为 true 所以在 new Class的时候 不会 fire init 方法. prototype变量分配后, initializing 被设置回 false, 为了下一步可以正常工作. (e.g 当想要创建一个真正的实例的时候)

    
for (var name in prop) { // ...}

        使用一个 for 循环, 我们迭代出 prop 里的属性和方法. 该属性是通过 extends 方法传递进来的, 除了一些对 _super 的特殊处理, 我们将值赋给 prototype 属性.

    
prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() {  // special handling for _super }; })(name, prop[name]) : prop[name];

        当我们遍历 prop 里的每个对象时, 如果 满足 (typeof prop[name] == "function")  (typeof _super[name] == "function") (fnTest.test(prop[name]) == true)
我们将会加入新的方法来处理 绑定到 父类 新的方法 以及 原始方法.
        以上方式代码 看起来可能很有些 混乱 下面改使用 一种清晰的方式查看一下.

     
if (typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name])) { prototype[name] = (function(name, fn){ return function() {  // special handling for _super }; })(name, prop[name]);} else { // just copy the property prototype[name] = prop[name];}

        另一个自执行匿名函数, 在处理 super 中的 name prop[name] 被使用 . 没有这个闭包. 当返回这个function时 这个变量的引用将会出错.(e.g 它始终会返回 循环的最后一个)
        遍历所有, 我们将返回一个新的函数, 这个函数来处理 原生方法(via super) 和 新方法.

        
// special handling for supervar tmp = this._super;this._super = _super[name];var ret = fn.apply(this, arguments);this._super = tmp;return ret;

        对 super 的特殊处理, 我们首先要存储 已存在 _super 属性和类的一些参数. 存储在 临时 tmp 里, 这是为了防止 _super 中已存在的方法被重写
完事儿后我们将 tmp 在赋给 this._super 这样它就可以正常工作了.
         下一步, 我们将 _super[name] 方法赋给 当前对象的 this._super, 这样当 fn 通过 apply 被执行的时候 this._super()就会指向 父类方法, 这个
父类方法中的 this 也同样可以访问 当前对象.
         最后我们将返回值存储在 ret 中, 在将 _super 设置回来后返回该对象.
        下面有个简单的例子,  定义个简单的 Foo , 创建继承对象 Bar:

var Foo = Class.extend({ qux: function() { return "Foo.qux"; }});var Bar = Foo.extend({ qux: function() { return "Bar.qux, " + this._super(); }});

         当 Foo.extends 被执行, 在 qux 方法中由于存在 this._super 所以 Bar原型上的qux 实际上应该是这样的:

       
Bar.prototype.qux = function () { var tmp = this._super; this._super = Foo.prototype.qux; var ret = (function() { return "Bar.qux, " + this._super(); }).apply(this, arguments); this._super = tmp; return ret;}

        在脚本中完成这步后, 构造方法将被调用

function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments);}

        这段代码调用 Class 创建一个新的构造方法, 这不同于之前创建的 this.Class, 作为本地的 Class.extend. 这个构造方法返回 Class.extend 的调用(比如之前 Foo.extends).  new Foo() 实例后这个构造方法将被执行.
        构造方法将会自动执行 init() 方法(如果存在的话) 正好上面说的那样, 这个 initializing 变量来控制 init 是否被执行.

       
Class.prototype = prototype;

        最后这个 prototype,  从父类的构造方法返回一个混合后的 父类原型对象. (e.g var prototype = new this()), 这个结果是通过 extend 函数里的for循环.

Class.constructor = Class;
        因为我们重写了整个原型对象, 在这个类型中存储这个 原生的构造方法,  让它在一个实例的构造方法中能保持默认形为.

        
Class.extend = arguments.callee;

        将赋其自身, 通过  arguments.callee, 在本例中表示 “自身” 其实这里我们可以 避免使用 arguments.callee , 如果我们修改一下我的原生方法(e.g Class.extend = function extend(prop)) 之后我们就可以通过 使用

        
Class.extend = extend;.return Class;

        实例之后会返回, 一个原型对象, 一个构造属性, 一个 extend 方法 和一个可自执行的 方法 init.!!!

Javascript 相关文章推荐
JS对象与JSON格式数据相互转换
Feb 20 Javascript
Javascript学习指南
Dec 01 Javascript
JS实现让访问者自助选择网页文字颜色的方法
Feb 24 Javascript
jquery代码实现多选、不同分享功能
Jul 31 Javascript
jquery radio的取值_radio的选中_radio的重置方法
Sep 20 Javascript
JavaScript动态检验密码强度的实现方法
Nov 09 Javascript
javascript中this关键字详解
Dec 12 Javascript
JS正则表达式完美实现身份证校验功能
Oct 18 Javascript
基于Require.js使用方法(总结)
Oct 26 Javascript
Vue 实现前进刷新后退不刷新的效果
Jun 14 Javascript
微信小程序简单的canvas裁剪图片功能详解
Jul 12 Javascript
原生微信小程序开发中 redux 的使用详解
Feb 18 Javascript
cookie在javascript中的使用技巧以及隐私在服务器端的设置
Dec 03 #Javascript
js函数调用常用方法详解
Dec 03 #Javascript
JS随即打乱数组实现代码
Dec 03 #Javascript
JS图片预加载 JS实现图片预加载应用
Dec 03 #Javascript
输入密码检测大写是否锁定js实现代码
Dec 03 #Javascript
js操作textarea 常用方法总结
Dec 03 #Javascript
javascript object array方法使用详解
Dec 03 #Javascript
You might like
PHP程序61条面向对象分析设计的经验小结
2008/11/12 PHP
phpExcel导出大量数据出现内存溢出错误的解决方法
2013/02/28 PHP
PHP函数microtime()用法与说明
2013/12/04 PHP
对PHP新手的一些建议(PHP学习经验总结)
2014/08/20 PHP
PHP实现对png图像进行缩放的方法(支持透明背景)
2015/07/15 PHP
PHP中header用法小结
2016/05/23 PHP
利用PHP绘图函数实现简单验证码功能的方法
2016/10/18 PHP
php读取和保存base64编码的图片内容
2017/04/22 PHP
Javascript this指针
2009/07/30 Javascript
js 实现无干扰阴影效果 简单好用(附文件下载)
2009/12/27 Javascript
JavaScript 嵌套函数指向this对象错误的解决方法
2010/03/15 Javascript
js和as的稳定传值问题解决
2013/07/14 Javascript
判断客户浏览器是否支持cookie的示例代码
2013/12/23 Javascript
复制网页内容,粘贴之后自动加上网址的实现方法(脚本之家特别整理)
2014/10/16 Javascript
JavaScript修改作用域外变量的方法
2016/03/25 Javascript
JS如何判断json是否为空
2016/07/06 Javascript
深入理解Commonjs规范及Node模块实现
2017/05/17 Javascript
gulp教程_从入门到项目中快速上手使用方法
2017/09/14 Javascript
vue多种弹框的弹出形式的示例代码
2017/09/18 Javascript
详解如何去除vue项目中的#——History模式
2017/10/13 Javascript
Vue中正确使用jQuery的方法
2017/10/30 jQuery
Element UI框架中巧用树选择器的实现
2018/12/12 Javascript
JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【圆形情况】
2018/12/13 Javascript
微信jssdk踩坑之签名错误invalid signature
2020/05/19 Javascript
基于JavaScript实现简单抽奖功能代码实例
2020/10/20 Javascript
Vue中引入svg图标的两种方式
2021/01/14 Vue.js
Scrapy-redis爬虫分布式爬取的分析和实现
2017/02/07 Python
使用pandas实现连续数据的离散化处理方式(分箱操作)
2019/11/22 Python
Python填充任意颜色,不同算法时间差异分析说明
2020/05/16 Python
英国领先的电子、技术和办公用品购物网站:Ebuyer
2018/04/04 全球购物
美津浓巴西官方网站:Mizuno巴西
2019/07/24 全球购物
我的梦中国梦演讲稿
2014/04/23 职场文书
清明节演讲稿
2014/05/27 职场文书
汉语言文学专业求职信
2014/06/19 职场文书
“六查”、“三学”、“三干”查摆问题整改措施
2014/09/27 职场文书
PyQt5爬取12306车票信息程序的实现
2021/05/14 Python