窺伺Underscore源碼系列-開篇

開篇申明

對的,讓你所見,又最先造輪子了。哈哈,造輪子我們是仔細的~

源碼瀏覽是必需的,Underscore是因為方才進修整理了一波函數式編程,加上本身曾沒有太多瀏覽源碼的履歷,先拿Underscore練練手,隨着先輩們走一走,學一學。也相同時能夠夯實js基本,從源碼中進修到更多的編碼技能

Underscore源碼瀏覽大抵根據官方文檔來編寫.只管的申明每一個函數的寫法,願望本身能夠從中能夠收成大神的編碼功力。

github:Personal_Blog

瀏覽目次

Underscore源碼+詮釋地點

源碼瀏覽

團體構造、變量引見

(function(){}())

通例操縱哈,跟jQuery一毛一樣,經由過程IIFE來包裹營業邏輯,目標簡樸:1、防備全局污染。2、庇護隱私

  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中,我們須要轉變那種敕令式的編程體式格局,詳細的能夠看我之前寫的關於函數式編程的文章哈。

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

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每一個要領完成之前,我們將在下一篇看一下一些東西要領的編寫體式格局。

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

    原文作者:isNealyang
    原文地址: https://segmentfault.com/a/1190000014618548
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞