解析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 相关文章推荐
jQuery的强大选择器小结
Dec 27 Javascript
网页源代码保护(禁止右键、复制、另存为、查看源文件)
May 23 Javascript
JS小功能(setInterval实现图片效果显示时间)实例代码
Nov 28 Javascript
关闭浏览器输入框自动补齐 兼容IE,FF,Chrome等主流浏览器
Feb 11 Javascript
jquery获取css的color值返回RGB的方法
Dec 18 Javascript
WordPress中鼠标悬停显示和隐藏评论及引用按钮的实现
Jan 12 Javascript
javascript url几种编码方式详解
Jun 06 Javascript
Vue.js弹出模态框组件开发的示例代码
Jul 26 Javascript
JavaScript实现短信倒计时60s
Oct 09 Javascript
Vue利用路由钩子token过期后跳转到登录页的实例
Oct 26 Javascript
JavaScript find()方法及返回数据实例
Apr 30 Javascript
如何在postman中添加cookie信息步骤解析
Jun 30 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目录管理函数小结
2008/09/10 PHP
php set_time_limit()函数的使用详解
2013/06/05 PHP
ThinkPHP CURD方法之order方法详解
2014/06/18 PHP
php禁用cookie后session设置方法分析
2016/10/19 PHP
PHP读取XML文件的方法实例总结【DOMDocument及simplexml方法】
2019/09/10 PHP
不错的asp中显示新闻的功能
2006/10/13 Javascript
jQuery maxlength文本字数限制插件
2010/04/16 Javascript
javascript preload&lazy load
2010/05/13 Javascript
仅IE6/7/8中innerHTML返回值忽略英文空格的问题
2011/04/07 Javascript
通过JQuery将DIV的滚动条滚动到指定的位置方便自动定位
2014/05/05 Javascript
javascript事件冒泡和事件捕获详解
2015/05/26 Javascript
jQuery实现固定在网页顶部的菜单效果代码
2015/09/02 Javascript
JavaScript组件开发完整示例
2015/12/15 Javascript
jQuery实现发送验证码并60秒倒计时功能
2016/11/25 Javascript
laydate.js日期时间选择插件
2017/01/04 Javascript
Angular 2父子组件数据传递之@Input和@Output详解 (上)
2017/07/05 Javascript
jquery.rotate.js实现可选抽奖次数和中奖内容的转盘抽奖代码
2017/08/23 jQuery
Vue CLI3 如何支持less的方法示例
2018/08/29 Javascript
解决Layui当中的导航条动态添加后渲染失败的问题
2019/09/25 Javascript
JS箭头函数和常规函数之间的区别实例分析【 5 个区别】
2020/05/27 Javascript
Vue+Spring Boot简单用户登录(附Demo)
2020/11/12 Javascript
理解Python中函数的参数
2015/04/27 Python
为什么入门大数据选择Python而不是Java?
2018/03/07 Python
python实现坦克大战
2020/04/24 Python
利用Python如何实时检测自身内存占用
2020/05/09 Python
python中Ansible模块的Playbook的具体使用
2020/05/28 Python
对Python 字典元素进行删除的方法
2020/07/31 Python
服务员自我评价
2014/01/25 职场文书
员工试用期考核自我鉴定
2014/04/13 职场文书
酒店餐厅2014重阳节活动策划方案
2014/09/16 职场文书
2015元旦联欢晚会结束语
2014/12/14 职场文书
2014年加油站站长工作总结
2014/12/23 职场文书
冰雪公主观后感
2015/06/16 职场文书
大学毕业典礼致辞
2015/07/29 职场文书
2016优秀护士先进个人事迹材料
2016/02/25 职场文书
SpringBoot 整合mongoDB并自定义连接池的示例代码
2022/02/28 MongoDB