// 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);
});
});
// 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.
_.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.
_.findWhere = function(obj, attrs) {
return _.find(obj, _.matcher(attrs));
};
// Return the maximum element (or element-based computation).
_.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).
_.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](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
// If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
_.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);
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);
};
_.invoke
// 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);
});
});
先看restArg,我们知道这个就是给函数赋予一个rest param,但invoke里的函数有是怎么会是呢?
传入参数obj和后面的args不难理解, 但中间的path参数到底是什么意思?
看后面
if (_.isFunction(path)) {
func = path; //func
} else if (_.isArray(path)) {
contextPath = path.slice(0, -1); // pop
path = path[path.length - 1]; // the last
}
原来是函数或者一个数组
然后开始遍历obj:
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);
});
这个部分很耐人寻味,看似很难弄懂,但我们发现,最后返回的数组部分有return method
这个method顺着向上,我们找到了func这个东西
我们假设,path部分填充的是一个函数,那么会发生什么?
var result = $.invoke([{a:1}], function () {console.log(this.a)})
console.log(result) // [undefined]
我们从源代码可知:一旦path为一个function, 则func被指定为该函数,而method也会被填充,会跳过!method部分,进而直接调用该函数,并且context会设置为对应的数组item部分。
好的,那么如果path并非是个函数,而是个数组呢?
我们看到此时path就会作为一个contextPath保存,并去掉最后一个值,最后一个值保留起来作为属性名,用deepget先按路径取得context,最后调用这个context下的特定函数,并将该函数的context绑定为该context。
_.invoke本质上就是调用函数或者提供context来给被调用函数所使用,遇见null则返回null,若函数有return则在新的数组中展现。
_.pluck
_.pluck = function(obj, key) {
return _.map(obj, _.property(key));
};
_.property = function(path) {
if (!_.isArray(path)) {
return shallowProperty(path);
}
return function(obj) {
return deepGet(obj, path);
};
_.property是一个高层封装,如果path为字符串,则用shallowproperty处理,
数组则返回一个设定path为该数组的deepget类型返回函数
而pluck这个么。。。则是property的批量处理
_.where
_.where = function(obj, attrs) {
return _.filter(obj, _.matcher(attrs));
};
_.matcher = _.matches = function(attrs) {
attrs = _.extendOwn({}, attrs); //创造一个新对象
return function(obj) {
return _.isMatch(obj, attrs);
};
};
_.isMatch = function(object, attrs) {
var keys = _.keys(attrs), length = keys.length;
if (object == null) return !length;
var obj = Object(object);
for (var i = 0; i < length; i++) {
var key = keys[i];
if (attrs[key] !== obj[key] || !(key in obj)) return false;
}
return true;
}; //是否一一对应c
_.extendOwn = _.assign = createAssigner(_.keys);
var createAssigner = function(keysFunc, defaults) {
return function(obj) {
var length = arguments.length;
if (defaults) obj = Object(obj);
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 (!defaults || obj[key] === void 0) obj[key] = source[key]; ///唯一重要的一行,其余都是在加强程序健壮性
}
}
return obj;
};
};
部分重点通过注释标注,说明where是专门用于处理对象所用,去除obj中一切满足不了attrs属性的对象,是filter的一种更高层封装。
_.findWhere
_.findWhere = function(obj, attrs) {
return _.find(obj, _.matcher(attrs));
};
_.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];
};
不想吐槽,跟_.find基本一样,不过是对对象的处理,看attrs是不是一一对应,不过真的,underscore的健壮性真强,如此多的处理情况能连贯地考虑周全,可怕。
_.max
// Return the maximum element (or element-based computation).
_.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;
};
其实没什么好说的,选个最大值就完了,大不了用iteratee处理一下再选个最大值,只有一个点需要注意:
value != null && value > result
为什么value还需要 != null ?既然都大于result了?你试试看
console.log(null > -1000 )
就知道了,当然最好在nodejs或者chrome上试
_.min
// Return the minimum element (or element-based computation).
_.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](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
// If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
_.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);
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);
};
_.random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
};
没什么好说的,只有膜,代码太严谨了。。。
_.sample
*当只输入obj,或者有guard值时,返回当中任意一个值。
*当输入了obj,n且guard不能成立时,会返回一个打乱的obj内部,其中n是数组限定范围,若n大于传入obj的length值,则默认为obj的length
所以很明显_.shuffle传入infinity就是为了洗整个obj。
好吧,没了。