underscore源碼解析

前言

underscore是最適合初級人士閱讀的源碼,在閱讀源碼時,有一些有趣的實現,記錄以下。
基於underscore1.8.3。javascript

留存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方法,就能把原來的 _ 恢復,而後從新賦值到不衝突的變量上便可。html

保留原生方法、減小變量查詢

在underscore源碼常看到會將一些經常使用的方法保留起來。java

// 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;

這樣作的好處有兩個:jquery

  1. 減少*.min.js的體積。 在壓縮時,some.func1只會被壓縮成a.func1。若是把一個對象上經常使用的方法存爲一個變量func1,那麼壓縮後將節省不少字節。
  2. 加快變量訪問速度。
    在實際中,點操做符的使用會使得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方法,就認爲接下來會進行鏈式調用,就會將這個實例包裹以後,繼續返回。app

鏈式調用的關鍵就在於,函數return原對象ecmascript

構造函數

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

這個方法是一個優化方法,根劇不一樣的參數個數,返回不一樣的調用方式。
好處有三:

  1. call比apply性能優異,由於apply傳入數組,也是用調用底層的CALL方法,能夠查看ecmascript262規範
  2. 由於arguments這個類數組對象較爲消耗性能,因此不直接使用arguments來作判斷。
  3. 綁定上下文。

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,因此,不會有特別大的問題。

相關文章
相關標籤/搜索