窺探Underscore源碼系列-開篇

開篇說明

對的,如你所見,又開始造輪子了。哈哈,造輪子咱們是認真的~css

源碼閱讀是必須的,Underscore是由於剛剛學習整理了一波函數式編程,加上本身曾經沒有太多閱讀源碼的經驗,先拿Underscore練練手,跟着前輩們走一走,學一學。也相同時可以夯實js基礎,從源碼中學習到更多的編碼技巧java

Underscore源碼閱讀大體按照官方文檔來編寫.儘可能的說明每個函數的寫法,但願本身能夠從中能夠收穫大神的編碼功力。node

github:Personal_Bloggit

閱讀目錄

Underscore源碼+註釋地址github

源碼閱讀

總體結構、變量介紹

(function(){}())
複製代碼

常規操做哈,跟jQuery一毛同樣,經過IIFE來包裹業務邏輯,目的簡單:一、避免全局污染。二、保護隱私編程

var root = typeof self == 'object' && self.self === self && self ||
            typeof global == 'object' && global.global === global && global ||
            this ||
            {};
  var previousUnderscore = root._;
複製代碼

經過global和self來判斷是node環境仍是window環境,說白了,就是爲了拿到全局變量。由於咱們須要一個全局的變量_,因此爲了防止衝突,咱們這裏拿到root後,先暫存下以前的root._設計模式

var ArrayProto = Array.prototype, ObjProto = Object.prototype;
  var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;

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

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

  var Ctor = function(){};

  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  if (typeof exports != 'undefined' && !exports.nodeType) {
    if (typeof module != 'undefined' && !module.nodeType && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  _.VERSION = '1.8.3';
複製代碼

因爲Underscore自己依賴不少原生js的方法,因此這裏爲了不原型鏈的查找性能消耗,Underscore經過局部變量來保存一些經常使用的對象和方法。既能夠提高性能,減小對象成員訪問深度也能夠減小代碼的冗長。數組

下面的Ctor和_ 是爲了面向對象而準備的。瀏覽器

迭代

var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

  var builtinIteratee;

  var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
    return _.property(value);
  };

  _.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
  };
複製代碼

這裏的迭代,咱們須要理清楚一個概念,在Underscore中,咱們須要改變那種命令式的編程方式,具體的能夠看我以前寫的關於函數式編程的文章哈。bash

因此這裏想說的就是關於遍歷迭代的東西。

var results = _.map([1,2,3],function(elem){
  return elem*2;
}); // => [2,4,6]

_.map = _.collect = function (obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length); // 定長初始化數組
    for (var index = 0; index < length; index++) {
        var currentKey = keys ? keys[index] : index;
        results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
};
複製代碼

咱們傳遞給的 _.map 的第二個參數就是一個 iteratee,他多是函數,對象,甚至是字符串。

  • value 爲 null。則 iteratee 的行爲只是返回當前迭代元素自身
  • value 爲一個函數。那麼經過內置函數 optimizeCb 對其進行優化
  • value 爲一個對象。那麼返回的 iteratee(_.matcher)的目的是想要知道當前被迭代元素是否匹配給定的這個對象
  • value 是字面量,如數字,字符串等。他指示了一個對象的屬性 key,返回的 iteratee(_.property)將用來得到該屬性對應的值

optimizeCb()

在上面的分析中,咱們知道,當傳入的 value 是一個函數時,value 還要通過一個叫 optimizeCb 的內置函數才能得到最終的 iteratee:

var cb = function (value, context, argCount) {
  // ...
  if (_.isFunction(value)) return optimizeCb(value, context, argCount);
  // ...
};
複製代碼

因此此處的optimizeCb必然是優化回調的做用了。

// 優化回調的函數,遍歷
  var optimizeCb = function(func, context, argCount) {
    // void 0 會返回真正的undefined 此處確保上下文的存在
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        // argCount爲0時候,迭代過程當中,咱們只須要這個value就能夠了
        return func.call(context, value);
      };
      // The 2-parameter case has been omitted only because no current consumers
      //  3個參數(值,索引,被迭代集合對象).
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      // 4個參數(累加器(好比reducer須要的), 值, 索引, 被迭代集合對象)
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };
複製代碼

總體的代碼很是清晰,待優化的回調函數func,上下文context以及迭代回調須要的參數個數。

上面的這個優化的回調涉及到不一樣地方使用的不一樣迭代。這裏暫時 先放一放。等過了一遍源碼後,看到每個用到迭代的地方,在回頭來看,就會明白不少。

rest參數

在 ES6中,咱們定義不定參方法的時候能夠這麼寫

let a = (b,...c)=>{
console.log(b,c);
}
複製代碼

可是在此以前,Underscore實現了本身的reset,使用以下:

function a(a,b,c,d,e){
      console.log(a,b,c,d,e)
  }
  let aa = restArgs(a);//let aa = restArgs(a,4)
  aa(1,2,3,4,5,6,7,8,8)
複製代碼

看下restArgs的實現:

var restArgs = function(func, startIndex) {
    //未傳則取形參個數減一

    startIndex = startIndex == null ? func.length - 1 : +startIndex;

    return function() {
      //  多傳了幾個參數
      //length爲多傳了幾個參數
      var length = Math.max(arguments.length - startIndex, 0),
          rest = Array(length),
          index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      
      //優化。注意rest參數老是最後一個參數, 不然會有歧義
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      //撇去經常使用的startIndex,這裏循環
      //先拿到前面參數
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      //拿到後面的數組
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };  
複製代碼

面向對象

關於面向對象,這裏不作過多解釋了,能夠參考個人另外一篇文章: javasript設計模式之面向對象

咱們直接看他的繼承實現吧

var Ctor = function(){};
  
  // 定義了一個用於繼承的內部方法
  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    // nativeCreate = Object.create;
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };
複製代碼

es5 中,咱們有一種建立對象的方式,Object.create 。

function Animal(name){
  this.name = name;
}
Animal.prototype.eat = function(){
  console.log(this.name,'鳥爲食亡');
}
var dog = Object.create(Animal.prototype);
dog.name = "毛毛";
dog.eat();
複製代碼

ok,大概從上你們就看出來create的做用了。

baseCrate中,首先判斷是否爲對象,不然退出。瀏覽器能力檢測是否具有Object.create方法,具有則用。不然採用寄生式繼承建立對象。須要注意的是,baseCreate僅僅支持原型繼承,而不能像Object.create那樣傳遞屬性列表。

結束語

開篇簡單的介紹Collection Functions上面的代碼部分。在介紹Collection Function每一個方法實現以前,咱們將在下一篇看一下一些工具方法的編寫方式。

的確在造造輪子,只是更想本身擼一遍優秀代碼。

相關文章
相關標籤/搜索