留存root javascript // Establish the root object, ( ) in the browser, // on the server, or in some virtual machines. We use // instead of for support. v ...
前言
underscore是最適合初級人士閱讀的源碼,在閱讀源碼時,有一些有趣的實現,記錄如下。
基於underscore1.8.3。
留存root
// Establish the root object, `window` (`self`) in the browser, `global`
// on the server, or `this` in some virtual machines. We use `self`
// instead of `window` for `WebWorker` support.
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// .......
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
在瀏覽器情況下,self是window自身的引用。上面的語法主要是為了保證在sever端和服務端都能正常獲得根對象。
將root._ 存起來,是為了防止命名衝突。調用noConflict方法,就能把原來的 _ 恢復,然後重新賦值到不衝突的變數上即可。
保留原生方法、減少變數查詢
在underscore源碼常看到會將一些常用的方法保留起來。
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
// Create quick reference variables for speed access to core prototypes.
var push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
這樣做的好處有兩個:
- 減小*.min.js的體積。 在壓縮時,some.func1只會被壓縮成a.func1。如果把一個對象上常用的方法存為一個變數func1,那麼壓縮後將節省很多位元組。
- 加快變數訪問速度。
在實際中,點操作符的使用會使得JavaScript引擎檢索該對象下的所有成員。如果嵌套越深,那麼讀取速度越慢,花費時間越久。如果不是該對象的實例屬性,引擎甚至要去檢索原型鏈,將更加耗費時間。
題外話:實際上,為了更好得提高性能,通常將變數保存到局部作用域,檢索將會加快。
鏈式調用
var chainResult = function(instance, obj) {
// 如果_chain為true,則return一個加了鏈式屬性的underscore對象。
return instance._chain ? _(obj).chain() : obj;
};
// Add your own custom functions to the Underscore object.
// 可以把自己寫的擴展方法通過mixin加入到underscore (_) 上。
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return chainResult(this, func.apply(_, args));
};
});
return _;
};
// Add all of the Underscore functions to the wrapper object.
// 對underscore使用mixin,可以將全部實例方法掛載到原型上。
_.mixin(_);
// 鏈式調用方法,不過是加了一個Boolean型開關,來對返回值做判斷
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
.mixin方法用來把obj上的方法,都內置到下劃線 上,相當於jquery的extends方法。
此處調用 _ mixin( _ );實際上,是將 _ 上的方法,都掛載到 _ .prototype上,以便於之後的鏈式調用。
再來關註一下 .chain這個方法,調用之後會返回一個underscore對象,並且把該對象的 chain屬性賦為true。在chainResult這個方法里,會對當前的這個實例的 _ chain屬性進行判斷,如果調用了chain方法,就認為接下來會進行鏈式調用,就會將這個實例包裹之後,繼續返回。
鏈式調用的關鍵就在於,函數return原對象。
構造函數
var _ = function(obj) {
// 如果是underscore的實例,就直接返回obj
if (obj instanceof _) return obj;
// 如果this不是underscore的實例,就new一個新的underscore實例並返回
if (!(this instanceof _)) return new _(obj);
// 將this._wrapped屬性置為obj
this._wrapped = obj;
};
需要註意第二步,this的指向,因為如果直接調用 _ 函數,則this指向為window,使用new構造函數,this指向為新創建的對象。
一些函數
接下來對一些函數做分析。
optimizeCb
這個方法是一個優化方法,根劇不同的參數個數,返回不同的調用方式。
好處有三:
- call比apply性能優異,因為apply傳入數組,也是用調用底層的CALL方法,可以查看ecmascript262規範
- 因為arguments這個類數組對象較為消耗性能,所以不直接使用arguments來做判斷。
- 綁定上下文。
isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
目前判斷數組的方法,較為公認的做法就是通過toString,查看是否是[object Array]。在ES5之後,原生帶有isArray方法,相容性不是很完善,IE9之後支持。可以把這個改一下,作為polyfill。
在zepto中的isArray實現稍有不同:
isArray = Array.isArray ||
function(object){ return object instanceof Array }
這兩種方法有所區別,zepto的實現在iframe的情況下會有bug,具體參見這篇博客。
不過由於移動端通常不會使用iframe,所以,不會有特別大的問題。
...未完待續