// 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; // If it's totally an object
}; //说明item不影响数组或者伪数组本身
// 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)
var reducer = function(obj, iteratee, memo, initial) {
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;
};
return function(obj, iteratee, memo, context) {
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.
_.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`.
_.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;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = restArgs(function(obj, path, args) {
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) {
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);
});
});
终于到了函数各个功能实现的部分了,先回顾一下之前我们看到的准备函数:
1. getLength (获取所有跟length有关的属性)
2. isArrayLike (该对象是否跟数组类似,拥有长度属性,并且在0到最大数组长之内)
3. baseCreate (继承原型所用)
4. cb (用以创造一个新的迭代函数,根据不同的情况进行处理,如果第一个传入参数是函数,则等同与optimizeCb处理)
5. optmizeCb (优化函数,绑定函数this,并且规定传入参数的数量)
_.each
_.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没什么好说的,就是在迭代器中传入对象每一个值,
不同情况不同的处理方式,伪数组或者数组则处理1,2,3,4的部分,如果是一个对象,则遍历keys。
_.map
_.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;
};
相当于在内部先创造一个空数组,然后在当中分别对应迭代器返回的值,迭代器传入参数分别为:每个值,index,对象本身,然后返回这个空数组
从代码中可以看出,_.each用作纯遍历工具比该方法更加高效。
_.reduce
// 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)
var reducer = function(obj, iteratee, memo, initial) {
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;
};
return function(obj, iteratee, memo, context) {
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);
我们知道reduce的就是
_.reduce(obj, function () {}, accumulator, context)
其中这个
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)
var reducer = function(obj, iteratee, memo, initial) {
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;
};
return function(obj, iteratee, memo, context) {
var initial = arguments.length >= 3;
return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
};
};
即为构造器,构造reduce起点或者终点,dir参数正负决定方向,大小决定遍历的方式
memo代表之前存放的数值,如未规定,则默认为开头
迭代器中的参数大概为:memo, currentItem, currentIndex, obj
然后再return一个新的memo出来
完全可以把这个函数简化点:
var createReduce = function (dir) {
function reduce (obj, iteratee, memo, initial) {
var index = 0
var length = obj.length
if (!initial) {
memo = obj[index]
index+= dir
}
for (; index >=0 && index < length; index += dir) {
var currentItem = obj[index]
memo = iteratee(memo, currentItem, index, obj)
}
return memo
}
return function (obj, iteratee, memo) {
var initial = arguments.length >= 3
return reduce(obj, iteratee, memo, initial)
}
}
其中initial = arguments.length >= 3用于自动处理当规定memo后,reduce自动将其initial为true
那么reduce就方便了:
_.reduce = _.foldl = _.inject = createReduce(1);
_.reduceRight = _.foldr = createReduce(-1);
_.find
_.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];
};
看到这个函数。 第一眼就发现一个.findIndex和.findKey
估计是在后面出现的,于是搜索一下找到这两个函数:
_.findIndex = createPredicateIndexFinder(1);
_.findKey = function(obj, predicate, context) {
predicate = cb(predicate, context);
var keys = _.keys(obj), key;
for (var i = 0, length = keys.length; i < length; i++) {
key = keys[i];
if (predicate(obj[key], key, obj)) return key;
}
};
var createPredicateIndexFinder = function(dir) {
return function(array, predicate, context) {
predicate = cb(predicate, context);
var length = getLength(array);
var index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
if (predicate(array[index], index, array)) return index;
}
return -1;
};
};
我们先看findKey:
_.findKey = function(obj, predicate, context) {
predicate = cb(predicate, context);
var keys = _.keys(obj), key;
for (var i = 0, length = keys.length; i < length; i++) {
key = keys[i];
if (predicate(obj[key], key, obj)) return key;
}
};
我们可以发现当中的predicate是个用来作判断的函数,如果其返回值为true, 则直接返回第一个符合条件的索引,其中向predicate传入的参数为item, key, obj
再看_.findIndex:
_.findIndex = createPredicateIndexFinder(1);
var createPredicateIndexFinder = function(dir) {
return function(array, predicate, context) {
predicate = cb(predicate, context);
var length = getLength(array);
var index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
if (predicate(array[index], index, array)) return index;
}
return -1;
};
};
没得说,跟我们刚才那个createReduce的dir作用一样,正负决定方向,dir参数大小决定方式,同样return一个新的function,找到符合条件的立即返回,若找不到则返回-1
回到_.find:
_.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];
};
我们发现这是对对象和数组类型两种不同情况更高一层的封装,最底层的方法当然是createPredicateIndexFinder与_.findKey这个可能较为重要,以后可能会继续用到。
_.filter
_.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;
};
果不其然,之前类似的方法我们又得用到一次,只不过这回内部先造出了一个新的数组,然后用.each遍历我们的obj,然后用函数判断是否符合要求,是则将其推入数组,最终返回一个数组,看来.each是最高效的一个遍历方法,也更底层一些。
_.reject
// Return all the elements for which a truth test fails.
_.reject = function(obj, predicate, context) {
return _.filter(obj, _.negate(cb(predicate)), context);
};
这个么,
_.negate = function(predicate) {
return function() {
return !predicate.apply(this, arguments);
};
};
说白了就是将predicate的判断结果反过来
同理reject就是说将所有满足条件的值全部弹出,保留下不满足条件的值。
_.every
_.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;
};
从内部的for循环我们可以看出,一旦obj内发现任何不满足条件的值,就返回false,如果obj内的所有值遵纪守法最终就返回true
_.some
// 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;
};
我们发现,这个函数与.every/.all最大的区别就是。。。。return的值反了过来,也就是说,一旦有任何函数满足条件,就返回true,都不满足最终会返回false
感觉这个用_.any比较合适,因为some实在是不能立即判断出这个函数的作用。
_.contains
// Determine if the array or object contains a given item (using `===`).
// Aliased as `includes` and `include`.
_.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;
};
真是要吐了,怎么又出现了奇怪的函数。。。能在这好好写不行吗?
没办法,开搜
_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
// _.findIndex我们已经知道就是返回第一个满足条件的值的index
_.sortedIndex = function(array, obj, iteratee, context) {
iteratee = cb(iteratee, context, 1);
var value = iteratee(obj);
var low = 0, high = getLength(array);
while (low < high) {
var mid = Math.floor((low + high) / 2);
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
return low;
};
//二分搜索,输入的值需要为升序,返回的值为第一个比条件数字大的值的索引
```
然后就是createIndexFinder:
该方法参考网址
// Generator function to create the indexOf and lastIndexOf functions
// _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
// _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
function createIndexFinder(dir, predicateFind, sortedIndex) {
// API 调用形式
// _.indexOf(array, value, [isSorted])
// _.indexOf(array, value, [fromIndex])
// _.lastIndexOf(array, value, [fromIndex])
return function(array, item, idx) {
var i = 0, length = getLength(array);
// 如果 idx 为 Number 类型
// 则规定查找位置的起始点
// 那么第三个参数不是 [isSorted]
// 所以不能用二分查找优化了
// 只能遍历查找
if (typeof idx == 'number') {
if (dir > 0) { // 正向查找
// 重置查找的起始位置
i = idx >= 0 ? idx : Math.max(idx + length, i);
} else { // 反向查找
// 如果是反向查找,重置 length 属性值
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
} else if (sortedIndex && idx && length) {
// 能用二分查找加速的条件
// 有序 & idx !== 0 && length !== 0
// 用 _.sortIndex 找到有序数组中 item 正好插入的位置
idx = sortedIndex(array, item);
// 如果正好插入的位置的值和 item 刚好相等
// 说明该位置就是 item 第一次出现的位置
// 返回下标
// 否则即是没找到,返回 -1
return array[idx] === item ? idx : -1;
}
// 特判,如果要查找的元素是 NaN 类型
// 如果 item !== item
// 那么 item => NaN
if (item !== item) {
idx = predicateFind(slice.call(array, i, length), _.isNaN);
return idx >= 0 ? idx + i : -1;
}
// O(n) 遍历数组
// 寻找和 item 相同的元素
// 特判排除了 item 为 NaN 的情况
// 可以放心地用 `===` 来判断是否相等了
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
回到_.contains
_.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;
};
这中间guard的参数意义也就变得明了,即保证传入的obj是升序的,则此时会用二分法查找,效率会变得更高。
总结:createPredicateIndex –> .indexof –> .contains
一步步封装,第一层是构造一个寻找函数,第二层则是对应不同情况进行加强修补调用,第三层则更是简化传入参数步骤。