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;
获取根对象,self
或 global
。
// 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
性能较差,且 call
比 apply
快(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
时返回 identity
;value
是函数时返回回调函数;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
,遍历 keys
为 key
,如果 obj
属性 key
的值为 undefined
,或创建此赋值器的 undefinedOnly
为 false
,则对 obj
的 key
属性赋值。
// 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
不存在,则对空的构造函数 Ctor
的 prototype
赋值,用 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
是要作为数组还是对象遍历的方法。当 collection
有 length
属性,且 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
方法。