Underscore 源码学习

Underscore.js 1.8.3

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L6

(function() {
}.call(this));

立即执行函数,避免污染外部环境。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L12

var root = this;

获取根对象,selfglobal

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L18

var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

压缩变量名长度,节省字节。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L21

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

为一些方法创建引用,方便访问。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L29

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

ES5 中原生支持的一些方法。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L36

var Ctor = function(){};

一个空的构造函数。


// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L39

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

创建 Underscore 对象。

传入 obj_ 的实例时返回 obj 本身。_ 不是作为构造函数调用时,使用 new _(obj) 生成 _ 的实例。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L48

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

Node.js 环境中导出 _,浏览器环境中将 _ 添加到根对象。


// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L60

// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
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 2: return function(value, other) {
      return func.call(context, value, other);
    };
    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);
  };
};

用来返回高效的(对当前引擎而言)回调函数的内部函数。

回调函数无 context 时直接返回原 func

js引擎访问 arguments 性能较差,且 callapply 快(apply() vs call()),所以在知道参数列表长度的时候,直接用 call 会更好。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L84

// A mostly-internal function to generate callbacks that can be applied
// to each element in a collection, returning the desired result — either
// identity, an arbitrary callback, a property matcher, or a property accessor.
var cb = function(value, context, argCount) {
  if (value == null) return _.identity;
  if (_.isFunction(value)) return optimizeCb(value, context, argCount);
  if (_.isObject(value)) return _.matcher(value);
  return _.property(value);
};

生成可应用于集合中每一个元素的回调函数的内部函数。返回的结果:identity,任意回调,属性匹配器或属性访问器。

未传入 value 时返回 identityvalue 是函数时返回回调函数;value 是对象时返回属性匹配器;其他情况返回属性访问器。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L93

_.iteratee = function(value, context) {
  return cb(value, context, Infinity);
};

返回一个参数长度不确定的迭代器。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L97

// An internal function for creating assigner functions.
var createAssigner = function(keysFunc, undefinedOnly) {
  return function(obj) {
    var length = arguments.length;
    if (length < 2 || obj == null) return obj;
    for (var index = 1; index < length; index++) {
      var source = arguments[index],
          keys = keysFunc(source),
          l = keys.length;
      for (var i = 0; i < l; i++) {
        var key = keys[i];
        if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
      }
    }
    return obj;
  };
};

创建一个赋值器。传入一个用来获取对象所有 key 的函数 keysFunc。与决定是否只对 undefined 属性赋值的布尔值 undefinedOnly

赋值器第一个参数作为 obj,从第 2 个参数开始,获取每个参数的 keys,遍历 keyskey,如果 obj 属性 key 的值为 undefined,或创建此赋值器的 undefinedOnlyfalse,则对 objkey 属性赋值。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L115

// An internal function for creating a new object that inherits from another.
var baseCreate = function(prototype) {
  if (!_.isObject(prototype)) return {};
  if (nativeCreate) return nativeCreate(prototype);
  Ctor.prototype = prototype;
  var result = new Ctor;
  Ctor.prototype = null;
  return result;
};

用来创建继承另一个对象的对象的内部函数。

若传入 prototype 不是对象,则返回对象 {}

Object.create 存在,则用 Object.create 创建新对象;

Object.create 不存在,则对空的构造函数 Ctorprototype 赋值,用 Ctor 创建新的对象。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L125

var property = function(key) {
  return function(obj) {
    return obj == null ? void 0 : obj[key];
  };
};

生成用来获取对象指定 key 属性的值的函数。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L131

// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = property('length');
var isArrayLike = function(collection) {
  var length = getLength(collection);
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

判断一个 collection 是要作为数组还是对象遍历的方法。当 collectionlength 属性,且 length 属性的值为数字时作为数组遍历。


Collection 相关函数

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L145

// The cornerstone, an `each` implementation, aka `forEach`.
// Handles raw objects in addition to array-likes. Treats all
// sparse array-likes as if they were dense.
_.each = _.forEach = function(obj, iteratee, context) {
  iteratee = optimizeCb(iteratee, context);
  var i, length;
  if (isArrayLike(obj)) {
    for (i = 0, length = obj.length; i < length; i++) {
      iteratee(obj[i], i, obj);
    }
  } else {
    var keys = _.keys(obj);
    for (i = 0, length = keys.length; i < length; i++) {
      iteratee(obj[keys[i]], keys[i], obj);
    }
  }
  return obj;
};

each, forEach 实现。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L165

_.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 实现。results = Array(length) 生成长度符合结果的空数组。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L177

// Create a reducing function iterating left or right.
function createReduce(dir) {
  // Optimized iterator function as using arguments.length
  // in the main function will deoptimize the, see #1991.
  function iterator(obj, iteratee, memo, keys, index, length) {
    for (; index >= 0 && index < length; index += dir) {
      var currentKey = keys ? keys[index] : index;
      memo = iteratee(memo, obj[currentKey], currentKey, obj);
    }
    return memo;
  }

  return function(obj, iteratee, memo, context) {
    iteratee = optimizeCb(iteratee, context, 4);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        index = dir > 0 ? 0 : length - 1;
    // Determine the initial value if none is provided.
    if (arguments.length < 3) {
      memo = obj[keys ? keys[index] : index];
      index += dir;
    }
    return iterator(obj, iteratee, memo, keys, index, length);
  };
}

使用 createReduce 来创建 reduce 的方法。接受 dir 作为生成的 reduce 的遍历的方向。没有初始值 memo 时采用第一位或最后一位值作为初始值。

// https://github.com/jashkenas/underscore/blob/1.8.3/underscore.js#L205

_.reduce = _.foldl = _.inject = createReduce(1);

createReduce 生成 reduce 方法。