解析John Resig Simple JavaScript Inheritance代碼
由于作者翻譯會加入 自己的理解 以便自己學(xué)習(xí)和使用, 如果英文好的同學(xué)可看下面 如文章中有翻譯錯誤還請留言. 交流并改正. (:
======================Enein翻譯=========================
John Resig 寫了一篇關(guān)于 JavaScript 里 類似其它語言的 "繼承", 靈感來自于 base2 and PrototypeJS. 他為文章起名為"Simple JavaScript Inheritance" . 他使用的一些很巧妙的技術(shù)來實(shí)現(xiàn) super 方法.
你還可以看原文也會有詳細(xì)的說明, 他也在他的 "Secrets of a JavaScript Ninja"里有所介紹. 在書中可能方法有一些不同, 它在Object中加入了subClass 方法, 而不是創(chuàng)建一個全局變量.
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
下面我們來分析一下, 它是如何實(shí)現(xiàn)和有哪些技術(shù)被使用.
(function(){ // ... })();
首先我們創(chuàng)建一個自執(zhí)行匿名函數(shù), 為代碼創(chuàng)建一個作用域.
var initializing = false
這 initializing 變量意思很直接, 它是boolean來檢查Class Function(稍后介紹)什么時候被調(diào)用. 在創(chuàng)建實(shí)例時設(shè)置 initializing 為true/false 或者只是返回一個對象指向當(dāng)前的原型鏈上來達(dá)到"繼承"的目的.
如果我們創(chuàng)建一個實(shí)例(initializing == false), 正好Class有一個init方法, 這樣 init 會自動執(zhí)行。 再或者, 如果我們僅僅將它分配給原型上(initializing == true), 將不會發(fā)生什么, init 方法不會被執(zhí)行。這樣做是為了避免 每次調(diào)用構(gòu)造方法都要執(zhí)行 init 方法. (var prototype = new this());.
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
這個fnTest的目的就是為了驗(yàn)證 class method 中是否使用了 "_super()" 調(diào)用. 這種技術(shù)叫做 " function decompilation(函數(shù)反編譯)" 也叫做 "function serialisation(函數(shù)序列化)", Function serialisation 是在一個函數(shù)被轉(zhuǎn)換成字符串時發(fā)生的. 現(xiàn)在很多瀏覽器都支持 toString 方法。
測試 Function serialisation, fnTest 使用一個匿名函數(shù) funciton(){xyz;} 設(shè)置內(nèi)容為 "xyz", 在轉(zhuǎn)變成字符串后使用正則對 "xyz" 進(jìn)行查找. 它將返回true (如果瀏覽器支持 function serialisation) 因?yàn)?函數(shù)將轉(zhuǎn)變成字符串所以 "xyz" 也民屬于字符串的一部分. 在這個例子中 fnTest 將返回 "/\b_super\b/", 另一種則返回 "/.*/" 如果瀏覽器不支持 function serialisation 則始終返回 true。(這個指的是原始代碼中的fnTest.test)使用 fnTest 正則, 和 函數(shù)序列化技術(shù), 我們能很容易方法中是否使用了 "_super" 如果它們使用, 則執(zhí)行一些特殊方法. 反之正常. 這個特殊方法是為了避免在 父類與子類中同時出現(xiàn)同一個方法. 父類將會被覆蓋.
瀏覽器不支持 Function serialisation 將會始終返回 true, 那么會始終對 _super 進(jìn)行額外的操作, 導(dǎo)致這些新的方法不能在 _super 中使用. 這會有一些小的性能消耗. 但能保證在所有瀏覽器中 正常執(zhí)行.
this.Class = function(){};
創(chuàng)建一個空的構(gòu)造方法, 放到全局變量中. 這將會是最上層的構(gòu)造方法. 它沒有定義內(nèi)容, 或一個原型對象. 除了下面的 extends 方法. this 指的是window對象. 使 Class 變量為全局對象.
Class.extend = function(prop) { // ...}
加入 extends 方法和一個簡單的 prop(一個對象) 參數(shù). 它將返回 新構(gòu)造方法的原型 + 父對象的原型;
var _super = this.prototype;
將當(dāng)前對象的原型對象存儲在 _super中. this.prototype是被擴(kuò)展對象的原型, 它可以訪問父級方法在你需要的地方, 這個變量叫什么 _super , 是因?yàn)?super 是保留字. 盡管現(xiàn)在還沒有應(yīng)用起來.
initializing = true;var prototype = new this();initializing = false;
實(shí)例 class 對象存儲在 prototype 變量中, 但不執(zhí)行 init 方法. 之前設(shè)置 initializing 為 true 所以在 new Class的時候 不會 fire init 方法. prototype變量分配后, initializing 被設(shè)置回 false, 為了下一步可以正常工作. (e.g 當(dāng)想要創(chuàng)建一個真正的實(shí)例的時候)
for (var name in prop) { // ...}
使用一個 for 循環(huán), 我們迭代出 prop 里的屬性和方法. 該屬性是通過 extends 方法傳遞進(jìn)來的, 除了一些對 _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];
當(dāng)我們遍歷 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];}
另一個自執(zhí)行匿名函數(shù), 在處理 super 中的 name prop[name] 被使用 . 沒有這個閉包. 當(dāng)返回這個function時 這個變量的引用將會出錯.(e.g 它始終會返回 循環(huán)的最后一個)
遍歷所有, 我們將返回一個新的函數(shù), 這個函數(shù)來處理 原生方法(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 屬性和類的一些參數(shù). 存儲在 臨時 tmp 里, 這是為了防止 _super 中已存在的方法被重寫
完事兒后我們將 tmp 在賦給 this._super 這樣它就可以正常工作了.
下一步, 我們將 _super[name] 方法賦給 當(dāng)前對象的 this._super, 這樣當(dāng) fn 通過 apply 被執(zhí)行的時候 this._super()就會指向 父類方法, 這個
父類方法中的 this 也同樣可以訪問 當(dāng)前對象.
最后我們將返回值存儲在 ret 中, 在將 _super 設(shè)置回來后返回該對象.
下面有個簡單的例子, 定義個簡單的 Foo , 創(chuàng)建繼承對象 Bar:
var Foo = Class.extend({ qux: function() { return "Foo.qux"; }});var Bar = Foo.extend({ qux: function() { return "Bar.qux, " + this._super(); }});
當(dāng) Foo.extends 被執(zhí)行, 在 qux 方法中由于存在 this._super 所以 Bar原型上的qux 實(shí)際上應(yīng)該是這樣的:
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;}
在腳本中完成這步后, 構(gòu)造方法將被調(diào)用
function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments);}
這段代碼調(diào)用 Class 創(chuàng)建一個新的構(gòu)造方法, 這不同于之前創(chuàng)建的 this.Class, 作為本地的 Class.extend. 這個構(gòu)造方法返回 Class.extend 的調(diào)用(比如之前 Foo.extends). new Foo() 實(shí)例后這個構(gòu)造方法將被執(zhí)行.
構(gòu)造方法將會自動執(zhí)行 init() 方法(如果存在的話) 正好上面說的那樣, 這個 initializing 變量來控制 init 是否被執(zhí)行.
Class.prototype = prototype;
最后這個 prototype, 從父類的構(gòu)造方法返回一個混合后的 父類原型對象. (e.g var prototype = new this()), 這個結(jié)果是通過 extend 函數(shù)里的for循環(huán).
Class.constructor = Class;
因?yàn)槲覀冎貙懥苏麄€原型對象, 在這個類型中存儲這個 原生的構(gòu)造方法, 讓它在一個實(shí)例的構(gòu)造方法中能保持默認(rèn)形為.
Class.extend = arguments.callee;
將賦其自身, 通過 arguments.callee, 在本例中表示 “自身” 其實(shí)這里我們可以 避免使用 arguments.callee , 如果我們修改一下我的原生方法(e.g Class.extend = function extend(prop)) 之后我們就可以通過 使用
Class.extend = extend;.return Class;
實(shí)例之后會返回, 一個原型對象, 一個構(gòu)造屬性, 一個 extend 方法 和一個可自執(zhí)行的 方法 init.!!!
相關(guān)文章
JavaScript實(shí)現(xiàn)列出數(shù)組中最長的連續(xù)數(shù)
這篇文章主要介紹了JavaScript實(shí)現(xiàn)列出數(shù)組中最長的連續(xù)數(shù)的方法及使用,需要的朋友可以參考下2014-12-12IntersectionObserver實(shí)現(xiàn)圖片懶加載的示例
下面小編就為大家?guī)硪黄狪ntersectionObserver實(shí)現(xiàn)圖片懶加載的示例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09js結(jié)合css實(shí)現(xiàn)登錄后才能復(fù)制的效果實(shí)例
很多網(wǎng)站都有登錄后才能復(fù)制的限制,什么原理呢?css屬性user-select:none,通常會采用這種方式來禁止復(fù)制文本。但瀏覽開發(fā)者工具-審查元素,取消此樣式后,就可以選中文本了。想要完整地禁止復(fù)制,還需要通過js控制選擇的內(nèi)容。2023-07-07詳解js如何優(yōu)雅處理后端返回的單元格數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了JavaScript如何優(yōu)雅處理后端返回的單元格數(shù)據(jù),文中的示例代碼講解詳細(xì),有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10PHPMyAdmin導(dǎo)入時提示文件大小超出PHP限制的解決方法
這篇文章主要介紹了PHPMyAdmin導(dǎo)入時提示文件大小超出PHP限制的解決方法,造成這個問題的原因是PHP上傳大小限制為2MB,修改PHP.ini配置即可解決這問題,需要的朋友可以參考下2015-03-03簡單幾行JS Code實(shí)現(xiàn)IE郵件轉(zhuǎn)發(fā)新浪微博
大概就是說我們可以用window.external.menuArguments這個對象獲取到內(nèi)部的信息,如window,document這些常用的對象2013-07-07