// 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) {
//context 为 undefined 情况下,直接返回 func
// void 0
// 执行过程:1,对右侧表达式求值。2. 返回 undefined
// 为何这样多此一举:js 中,undefined 不是保留字,可以创建 windows.undefined=anything.void 返回 undefined 较安全。// 用处:1 返回 undefined。2a 标签不跳转 href=javascript:(void 0)3img 空图片 src=javascript:(void 0)
if (context === void 0) return func;
// 有传入 context 情况下,将 func 上下文改为 context
switch (argCount == null ? 3 : argCount) {
case 1:
return function(value) {return func.call(context, value);
};
// The 2-argument case is omitted because we’re not using it.
case 3:
return function(value, index, collection) {return func.call(context, value, index, collection);
};
//_.reduce 中使用
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;
// An 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.
// 内部函数,生成适用于集合每个元素的回调函数
// 传入 null 等空值,得到等价函数
// 传入 function,得到绑定了上下文的函数
// 传入对象,得到匹配函数
// 传入其他,得到属性访问函数
var cb = function(value, context, argCount) {
//??是何用意??if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
// 返回 null
if (value == null) return _.identity;
// 如果是函数,绑定函数上下文
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
// 如果是对象,返回匹配函数
if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
// 返回获取属性值函数
return _.property(value);
};
// External wrapper for our callback generator. Users may customize
// _.iteratee
if they want additional predicate/iteratee shorthand styles.
// This abstraction hides the internal-only argCount argument.
// 外部函数,返回 cb 函数结果
_.iteratee = builtinIteratee = function(value, context) {return cb(value, context, Infinity);
};
// Some functions take a variable number of arguments, or a few expected
// arguments at the beginning and then a variable number of values to operate
// on. This helper accumulates all remaining arguments past the function’s
// argument length (or an explicit startIndex
), into an array that becomes
// the last argument. Similar to ES6’s “rest parameter”.
// 用于实参个数不定的函数的固定参数及剩余参数整理。
// example:_.invoke
// func.length=3,so startIndex=2, 即从第三个参数开始,都存入 rest 变量中,并作为第三个参数传入。第一二个参数不变。
// example:_.without
// func.length=2,so startIndex=1, 即从第二个参数开始,都存入 rest 变量中,并作为第二个参数传入第一个参数不变。
// example:_.union
// func.length=1,so startIndex=0, 即将所有参数都传入 rest 变量中,并作为第一个参数传入。
// 最后一种情况,func 形参个数大于 3,不再使用 call 手动传入参数
// 而是利用 apply 的数组参数特性,将参数保存在数组中,再传入函数。
var restArguments = function(func, startIndex) {console.log('func2')
//func.length-> 形参个数
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() { //func3
// arguments.length-> 实参个数
// 个人理解:此处 arguments 指的是最贴近的这个闭包 function 在执行时传入的参数,并不是 func 在执行时传入的参数个数。console.log('func3')
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; index < length; index++) {rest[index] = arguments[index + startIndex];
}
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);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
// Invoke a method (with arguments) on every item in a collection.
// 在集合的每个元素上调用方法 path,参数为 arguments
// _.invoke([[3,4,5],[65,33,3]],’sort’)函数运行顺序
// 在 underscore.js 加载编译时,restArgument 被执行,console func2。并返回匿名函数 func3
// 在_.invoke() 被执行,即匿名函数 func3 被执行,console func3。此时的 arguments=[[[3,4,5],[65,33,3]],’sort’]。返回匿名函数 func 执行的值
// 执行 func,console func。参数 obj = arguments[0]=[[[3,4,5],[65,33,3]],path = arguemnts[1]=’sort’,args = rest = []
// console func4
// console func4
_.invoke = restArguments(function(obj, path, args) { //func
console.log('func')
var contextPath, func;
if (_.isFunction(path)) {func = path;} else if (_.isArray(path)) {contextPath = path.slice(0, -1);
path = path[path.length - 1];
}
return _.map(obj, function(context) { //func4
console.log('func4')
var method = func;
if (!method) {if (contextPath && contextPath.length) {context = deepGet(context, contextPath);
}
if (context == null) return void 0;
method = context[path];
}
return method == null ? method : method.apply(context, args);
});
});
// 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;
};
// 偏函数与柯里函数区别
// 偏函数:传入某些固定参数后,返回包含固定参数,并可传入其他参数的函数
// 柯里函数:将 n 元参数的函数改变为 n 次函数,每次都只能传入一个参数。
// 访问属性函数
var shallowProperty = function(key) {return function(obj) {return obj == null ? void 0 : obj[key];
};
};
// 判断是否存在自有属性 path
var has = function(obj, path) {return obj != null && hasOwnProperty.call(obj, path);
}
// 访问对象的深度多级属性 函数
var deepGet = function(obj, path) {
var length = path.length;
for (var i = 0; i < length; i++) {if (obj == null) return void 0;
obj = obj[path[i]];
}
return length ? obj : void 0;
};
// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object.
// Related: http://people.mozilla.org/~jo…
// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
// js 最大的精确整数值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = shallowProperty('length');
var isArrayLike = function(collection) {var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
// Collection Functions
// ——————–
// 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 与 map 区别
// each 中 iteratee 函数执行结果不保存,返回 obj 本身
// map 中 iteratee 函数执行结果保存到数组 results 中,并返回。
_.each = _.forEach = function(obj, iteratee, context) {iteratee = optimizeCb(iteratee, context);
var i,
keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length;
for (i = 0; i < length; i++) {var currentKey = keys ? keys[i] : i;
iteratee(obj[currentKey], currentKey, obj);
}
return obj;
};
// Return the results of applying the iteratee to each element.
_.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;
};
// Create a reducing function iterating left or right.
// 创建迭代函数,可选遍历方向
var createReduce = function(dir) {
// Wrap code that reassigns argument variables in a separate function than
// the one that accesses `arguments.length` to avoid a perf hit. (#1991)
// perf hit 是 performance hit 的简写。// arguments 的使用,会导致一些性能问题。如 chrome nodejs 的 v8 引擎在使用 arguments 会代码优化步骤被跳过。var reducer = function(obj, iteratee, memo, initial) { //func2
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
if (!initial) {memo = obj[keys ? keys[index] : index];
index += dir;
}
for (; index >= 0 && index < length; index += dir) {var currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
};
// 此处返回 func1 函数,作用域中包含 initial 变量,存放了 arguments.length,// 用意就在于将 arguments 变量从 reducer 函数中分离出来,// 并在调用 reducer 函数时,释放 argumenrs 变量,避免性能问题。return function(obj, iteratee, memo, context) { //func1
var initial = arguments.length >= 3;
return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
};
};
// Reduce builds up a single result from a list of values, aka inject
,
// or foldl
.
_.reduce = _.foldl = _.inject = createReduce(1);
// The right-associative version of reduce, also known as foldr
.
_.reduceRight = _.foldr = createReduce(-1);
// Return the first value which passes a truth test. Aliased as detect
.
// 返回第一个断言问真的值
_.find = _.detect = function(obj, predicate, context) {var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
var key = keyFinder(obj, predicate, context);
if (key !== void 0 && key !== -1) return obj[key];
};
// Return all the elements that pass a truth test.
// Aliased as select
.
// 返回所有断言为真的值组成的数组,中途不中断
_.filter = _.select = function(obj, predicate, context) {var results = [];
predicate = cb(predicate, context);
_.each(obj, function(value, index, list) {if (predicate(value, index, list)) results.push(value);
});
return results;
};
// Return all the elements for which a truth test fails.
// 返回所有断言为假的值,
// 内部直接调用 filter
_.reject = function(obj, predicate, context) {return _.filter(obj, _.negate(cb(predicate)), context);
};
// Determine whether all of the elements match a truth test.
// Aliased as all
.
// 判断是否所有断言函数都为真,出现假立即返回
_.every = _.all = function(obj, predicate, context) {predicate = cb(predicate, context);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length;
for (var index = 0; index < length; index++) {var currentKey = keys ? keys[index] : index;
if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
};
// Determine if at least one element in the object matches a truth test.
// Aliased as any
.
// 检测是否至少有一个值断言为真,出现真立即返回
_.some = _.any = function(obj, predicate, context) {predicate = cb(predicate, context);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length;
for (var index = 0; index < length; index++) {var currentKey = keys ? keys[index] : index;
if (predicate(obj[currentKey], currentKey, obj)) return true;
}
return false;
};
// Determine if the array or object contains a given item (using ===
).
// Aliased as includes
and include
.
// 判断从 fromIndex 索引开始,是否包含 item
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {if (!isArrayLike(obj)) obj = _.values(obj);
if (typeof fromIndex != 'number' || guard) fromIndex = 0;
return _.indexOf(obj, item, fromIndex) >= 0;
};
// Convenience version of a common use case of map
: fetching a property.
// 萃取数组对象中某属性值,返回一个数组
_.pluck = function(obj, key) {return _.map(obj, _.property(key));
};
// Convenience version of a common use case of filter
: selecting only objects
// containing specific key:value
pairs.
// 遍历 list 中的每一个值,返回一个数组,这个数组里的元素包含 properties 所列出的键 – 值对。
_.where = function(obj, attrs) {return _.filter(obj, _.matcher(attrs));
};
// Convenience version of a common use case of find
: getting the first object
// containing specific key:value
pairs.
// 遍历整个 list,返回 matches(匹配)properties 参数所列出的所有 键 – 值 对的第一个值。
_.findWhere = function(obj, attrs) {return _.find(obj, _.matcher(attrs));
};
// Return the maximum element (or element-based computation).
// 返回 list 中的最大值。如果传递 iteratee 参数,iteratee 将作为 list 中每个值的排序依据。
// 如果 list 为空,将返回 -Infinity,所以你可能需要事先用 isEmpty 检查 list。
_.max = function(obj, iteratee, context) {
var result = -Infinity,
lastComputed = -Infinity,
value, computed;
if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {value = obj[i];
if (value != null && value > result) {result = value;}
}
} else {iteratee = cb(iteratee, context);
_.each(obj, function(v, index, list) {computed = iteratee(v, index, list);
if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
result = v;
lastComputed = computed;
}
});
}
return result;
};
// Return the minimum element (or element-based computation).
// 返回 list 中的最小值。如果传递 iteratee 参数,iteratee 将作为 list 中每个值的排序依据。
// 如果 list 为空,将返回 Infinity,所以你可能需要事先用 isEmpty 检查 list。
_.min = function(obj, iteratee, context) {
var result = Infinity,
lastComputed = Infinity,
value, computed;
if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {value = obj[i];
if (value != null && value < result) {result = value;}
}
} else {iteratee = cb(iteratee, context);
_.each(obj, function(v, index, list) {computed = iteratee(v, index, list);
if (computed < lastComputed || computed === Infinity && result === Infinity) {
result = v;
lastComputed = computed;
}
});
}
return result;
};
// Shuffle a collection.
_.shuffle = function(obj) {return _.sample(obj, Infinity);
};
// Sample n random values from a collection using the modern version of the
// Fisher-Yates shuffle.
// If n is not specified, returns a single random element.
// The internal guard
argument allows it to work with map
.
// 从 list 中产生一个随机样本。传递一个数字表示从 list 中返回 n 个随机元素。否则将返回一个单一的随机项。
_.sample = function(obj, n, guard) {if (n == null || guard) {if (!isArrayLike(obj)) obj = _.values(obj);
return obj[_.random(obj.length - 1)];
}
var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
var length = getLength(sample);
// 取一个符合 0 -length 的合理值
n = Math.max(Math.min(n, length), 0);
var last = length - 1;
for (var index = 0; index < n; index++) {var rand = _.random(index, last);
var temp = sample[index];
sample[index] = sample[rand];
sample[rand] = temp;
}
return sample.slice(0, n);
};
// Sort the object’s values by a criterion produced by an iteratee.
// 返回一个(稳定的)排序后的 list 拷贝副本。
// 如果传递 iteratee 参数,iteratee 将作为 list 中每个值的排序依据。
// 用来进行排序迭代器也可以是属性名称的字符串(比如 length)。
_.sortBy = function(obj, iteratee, context) {
var index = 0;
iteratee = cb(iteratee, context);
return _.pluck(_.map(obj, function(value, key, list) {
return {
value: value,
index: index++,
criteria: iteratee(value, key, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
// An internal function used for aggregate “group by” operations.
// 对集合每个元素调用 iteratee 函数后,得到 key。
// 然后再根据 behavior 进行分组
var group = function(behavior, partition) {return function(obj, iteratee, context) {
var result = partition ? [[],
[]] : {};
iteratee = cb(iteratee, context);
_.each(obj, function(value, index) {var key = iteratee(value, index, obj);
behavior(result, value, key);
});
return result;
};
};
// Groups the object’s values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
// 把一个集合分组为多个集合,通过 iterator 返回的结果进行分组.
// 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组。
_.groupBy = group(function(result, value, key) {if (has(result, key)) result[key].push(value);
else result[key] = [value];
});
// Indexes the object’s values by a criterion, similar to groupBy
, but for
// when you know that your index values will be unique.
// 给定一个 list,和 一个用来返回一个在列表中的每个元素键 的 iterator 函数(或属性名),返回一个每一项索引的对象。
// 和 groupBy 非常像,但是当你知道你的键是唯一的时候可以使用 indexBy。
_.indexBy = group(function(result, value, key) {result[key] = value;
});
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
// 排序一个列表组成多个组,并且返回各组中的对象的数量的计数。
// 类似 groupBy,但是不是返回列表的值,而是返回在该组中值的数目。
_.countBy = group(function(result, value, key) {if (has(result, key)) result[key]++;
else result[key] = 1;
});
var reStrSymbol = /1|ud800-udbff|[ud800-udfff]/g;
// Safely create a real, live array from anything iterable.
_.toArray = function(obj) {if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (_.isString(obj)) {
// Keep surrogate pair characters together
return obj.match(reStrSymbol);
}
if (isArrayLike(obj)) return _.map(obj, _.identity);
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {if (obj == null) return 0;
return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};
// Split a collection into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
// 将 list 拆分为两个数组:第一个数组其元素都满足 predicate 迭代函数,而第二个的所有元素均不能满足 predicate 迭代函数。
// predicate 通过 iteratee 进行转换,以简化速记语法。
_.partition = group(function(result, value, pass) {result[pass ? 0 : 1].push(value);
}, true);
- ud800-udfff ↩