underscore源碼剖析之總體架構

underscore源碼分析之總體架構


最近打算好好看看underscore源碼,一個是由於本身確實水平不夠,另外一個是underscore源碼比較簡單,比較易讀。
本系列打算對underscore1.8.3中關鍵函數源碼進行分析,但願作到最詳細的源碼分析。
今天是underscore源碼剖析系列第一篇,主要對underscore總體架構和基礎函數進行分析。node

基礎模塊

首先,咱們先來簡單的看一下總體的代碼:web

// 這裏是一個當即調用函數,使用call綁定了外層的this(全局對象)
(function() {

  var root = this;
  
  // 保存當前環境中已經存在的_變量(在noConflict中用到)
  var previousUnderscore = root._;
  
  // 用變量保存原生方法的引用,以防止這些方法被重寫,也便於壓縮
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  var
    push             = ArrayProto.push,
    slice            = ArrayProto.slice,
    toString         = ObjProto.toString,
    hasOwnProperty   = ObjProto.hasOwnProperty;

  var
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind,
    nativeCreate       = Object.create;

  var Ctor = function(){};
  // 內部實現省略
  var _ = function(obj) {};
  
    // 這裏是各類方法的實現(省略)
    
  // 導出underscore方法,若是有exports則用exports導出,若是    沒有,則將其設爲全局變量
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }
  
  // 版本號
  _.VERSION = '1.8.3';
  
  // 用amd的形式導出
  if (typeof define === 'function' && define.amd) {
    define('underscore', [], function() {
      return _;
    });
  }
}.call(this))

全局對象

這段代碼總體比較簡單,不過我看後來的underscore版本有一些小改動,主要是將var root = this;替換爲下面這句:數組

var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this;

這裏增長了對self和global的判斷,self屬性能夠返回對窗口自身的引用,等價於window,這裏主要是爲了兼容web worker,由於web worker中是沒有window的,global則是爲了兼容node,並且在嚴格模式下,當即執行函數內部的this是undefined。瀏覽器

void(0) ? undefined

掃一眼源碼,咱們會發如今源碼中並無見到undefined的出現,反而是用void(0)或者void 0來代替的,那麼這個void究竟是什麼?爲何不能直接用undefined呢?
關於void的解釋,咱們能夠看這裏:MDN
void 運算符一般只用於獲取 undefined的原始值,通常使用void(0),由於undefined不是保留字,在低版本瀏覽器或者局部做用域中是能夠被當作變量賦值的,這樣就會致使咱們拿不到正確的undefined值,在不少壓縮工具中都是將undefined用void 0來代替掉了。
其實這裏不只是void 0能夠拿到undefined,還有其餘不少方法也能夠拿到,好比0["ygy"]、Object.__undefined__、Object.__ygy__,這些原理都是訪問一個不存在的屬性,因此最後必定會返回undefined架構

noConflict

也許有時候咱們會碰到這樣一種狀況,_已經被當作一個變量聲明瞭,咱們引入underscore後會覆蓋這個變量,可是又不想這個變量被覆蓋,還好underscore提供了noConflict這個方法。app

_.noConflict = function() {
    root._ = previousUnderscore;
    return this;
};
var underscore = _.noConflict();

顯而易見,這裏正常保留原來的_變量,並返回了underscore這個方法(this就是_方法)函數

_

接下來說到了本文的重點,關於_方法的分析,在看源碼以前,咱們先熟悉一下_的用法。
這裏總結的是我平常的用法,若是有遺漏,但願你們補充。
一種是直接調用_上的方法,好比_.map([1, 2, 3]),另外一種是經過實例訪問原型上的方法,好比_([1, 2, 3]).map(),這裏和jQuery的用法很像,$.extend調用jQuery對象上的方法,而$("body").click()則是調用jQuery原型上的方法。工具

既然_可使用原型上面的方法,那麼說明執行_函數的時候確定會返回一個實例。源碼分析

這裏來看源碼:測試

// instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性。
// 我這裏有個不夠準確但容易理解的說法,就是檢查一個對象是否爲另外一個構造函數的實例,爲了更容易理解,下面將所有以XXX是XXX的實例的方式來講。
var _ = function(obj) {
    // 若是obj是_的實例(這種狀況我真的沒碰到過)
    if (obj instanceof _) return obj;
    // 若是this不是_構造函數的實例,那就以obj爲參數 new一個實例(相等於修改了_函數)
    if (!(this instanceof _)) return new _(obj);
    // 對應_([1,2,3])這種狀況
    this._wrapped = obj;
  };

我先從源碼上來解釋,這裏能夠看出來_是一個構造函數,咱們都知道,我既能夠在構造函數上面增長方法,還能夠在原型上面增長方法,前者只能經過構造函數自己訪問到,後者因爲原型鏈的存在,能夠在構造函數的實例上面訪問到。

var Person = function() {
    this.name = "ygy";
    this.age = 22;
}
Person.say = function() {
    console.log("hello")
}
Person.prototype.say = function() {
    console.log("world")
}
var ygy = new Person();
Person.say(); // hello
ygy.say(); // world

因此咱們平時用的_.map就是Person.say()這種用法,而_([1, 2, 3]).map則是ygy.say()這種用法。

在繼續講這個以前,咱們再來複習一下原型的知識,當咱們new一個實例的時候處處發生了什麼?

首先,這裏會先建立一個空對象,這個空對象繼承了構造函數的原型(或者理解爲空對象上增長一個指向構造函數原型的指針__proto__),以後會根據實例傳入的參數執行一遍構造函數,將構造函數內部的this綁定到這個新對象中,最後返回這個對象,過程和以下相似:

var ygy = {};
ygy.__proto__ = Person.prototype 
// 或者var ygy = Object.create(Person.prototype)
Person.call(ygy);

這樣就很好理解了,要是想調用原型上面的方法,必須先new一個實例出來。咱們再來分析_方法的源碼:
_接收一個對象做爲參數,若是這個對象是_的一個實例,那麼直接返回這個對象。(這種狀況我卻是沒見過)

若是this不是_的實例,那麼就會返回一個新的實例new _(obj),這個該怎麼理解?
咱們須要結合例子來看這句話,在_([1, 2, 3])中,obj確定是指[1, 2, 3]這個數組,那麼this是指什麼呢?我以爲this是指window,不信你直接執行一下上面例子中的Person()?你會發如今全局做用域中是能夠拿到name和age兩個屬性的。

那麼既然this指向window,那麼this確定不是_的實例,因此this instanceof _必然會返回false,這樣的話就會return一個new _([1, 2, 3]),因此_([1, 2, 3])就是new _([1, 2, 3]),從咱們前面對new的解釋來看,這個過程表現以下:

var obj = {}
obj.__proto__ = _.prototype
// 此時_函數中this的是new _(obj),this instanceof _是true,因此就不會從新return一個new _(obj),這樣避免了循環調用
_.call(obj) // 實際上作了這一步: obj._wrapped = [1, 2, 3]

這樣咱們就理解了爲何_([1, 2, 3]).map中map是原型上的方法,由於_([1, 2, 3])是一個實例。

我這裏再提供一個本身實現的_思路,和jQuery的實現相似,這裏就不做解釋了:

var _ = function(obj) {
    return new _.prototype.init(obj)
}
_.prototype = {
    init: function(obj) {
        this.__wrapped = obj
        return this
    },
    name: function(name) {
        console.log(name)
    }
}
_.prototype.init.prototype = _.prototype;
var a = _([1, 2, 3])
a.name("ygy"); // ygy

underscore中全部方法都是在_方法上面直接掛載的,而且用mixin方法將這些方法再一次掛載到了原型上面。不過,因爲篇幅有限,mixin方法的實現會在後文中給你們講解。若是本文有錯誤和不足之處,但願你們指出。

相關文章
相關標籤/搜索