underscore探究

优化的思想贯穿始终

  • 为了便于代码压缩,整体函数闭包

    1
    2
    3
    (function(){
    // ...
    }.call(this));
  • 为原型赋值也有利于压缩

    1
    var ArrayProto = Array.prototype,ObjProto = Object.prototype;
  • isElement isArray isArguments isNaN isBoolean isUndefined等元素的判断实现可参考源码,通俗易懂

  • js内建基本类型: 值类型 undefined number boolean string 引用类型 object function
  • 原生对象: Null Number Boolean String Object Function Array RegExp Error Date Math JSON Global Arguments 更多参考
  • Functions 具有length属性表示能够接受的参数个数
  • 1
    2
    3
    4
    原理是根据链式作用域(chain scope)的原则,上级变量对下级可见。
    如果在对象或者函数内部再定义函数,而内部的函数使用了上级的变量,
    当将这个函数被作为返回值时,返回的函数就成为闭包,
    而上级的变量因为仍旧被使用因此会一直保存在内存中。

部分源码注释

each

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//_.each是underscore.js的基础方法,基于集合的方法很多有需要用到each
//别名是forEach,context是可选参数,如果要修改iterator的调用对象为context,即函数中this为context,就传递这个参数,否则context为undefined
_.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return obj;
//判断是否是数组或字符串,实际上function类型的变量也有length属性,但是function的[i]返回undefined,所以后面相当于对undefined进行变换
if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
//breaker是undercore定义的一个空对象,用于跳出循环,注意{}==={}返回false
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else { //其余类型,包括很多标准的内置对象
//只有Object才有key-value对,才进行处理,其他类型的对象不处理
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
return obj; //返回obj自身,未经任何处理
};

map

1
2
3
4
5
6
7
8
9
10
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
//这里函数的三个传入参数分别是obj的一个成员的值,key或者index,和obj自己,由each函数决定
_.each(obj, function(value, index, list) {

results.push(iterator.call(context, value, index, list)); //保存处理结果
});
return results;
};

some

1
2
3
4
5
6
7
8
9
10
11
12
//如果obj是Object类型的话,会判断其每一个属性的值是否满足prediacte,这是由each方法决定的
_.some = _.any = function(obj, predicate, context) {
//如果predicate为空,则将predicate赋值为空函数[Function]
predicate || (predicate = _.identity);
var result = false;
if (obj == null) return result;
_.each(obj, function(value, index, list) {
//这里发现一个问题,就是each循环无法break,必须将全部的对象都遍历一遍,应该是一个可改进的地方
if (result || (result = predicate.call(context, value, index, list))) return breaker;
});
return !!result; //两次取反,保证返回的是boolean类型,0/null/undefined进行两次取反都会返回false
};

every

1
2
3
4
5
6
7
8
9
10
11
_.every = _.all = function(obj, predicate, context) {
predicate || (predicate = _.identity);
var result = true;
if (obj == null) return result;
_.each(obj, function(value, index, list) {
//开始没看明白,“result=result”是什么意思?后来想到运算符优先级才看明白,“=”的优先级比“&&”要低,
//javascript为了减小文件大小,多余的运算符一概不要,能不能在开发版不要这么省啊
if (!(result = result && predicate.call(context, value, index, list))) return breaker;
});
return !!result;
};

indexOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i = 0, length = array.length;
if (isSorted) {
//如果是数字,则从数字开始,逐个元素与item对比,直到找到
if (typeof isSorted == 'number') {
i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
} else { //如果isSorted是true,使用二分查找,由sortedIndex实现。实际上没有对isSorted进行过多校验,由开发者自己保证正确性
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
}
for (; i < length; i++) if (array[i] === item) return i;
return -1;
};

invoke

1
2
3
4
5
6
7
8
9
_.invoke = function(obj, method) {
//invoke方法接受多于两个参数作为函数参数,从第3个参数开始将作为被调用函数的参数
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
//method可以是对象的属性名,这种情况就是调用对象自己属性名为method的方法
return (isFunc ? method : value[method]).apply(value, args);
});
};

toArray

1
2
3
4
5
6
7
8
_.toArray = function(obj) {
if (!obj) return [];
//slice.call会将Array-like对象转化为Array,难道isArray判断有问题?
if (_.isArray(obj)) return slice.call(obj);
//其他有length属性的对象,_identity返回参数自己,组成新数组。但是不清楚什么类型的对象会走到这里?
if (obj.length === +obj.length) return _.map(obj, _.identity);
return _.values(obj); //Object,返回属性的value组成数组
};

memoize

1
2
3
4
5
6
7
8
9
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
//利用闭包来保存结果,好处在大量运算时避免了对func函数的相同参数情况下的重复调用,只要执行过一次,以后直接取结果
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};

isType

1
2
3
4
5
6
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {    
//定义了一批isType函数,each没有返回值,但是相当于循环展开
_['is' + name] = function(obj) {
return toString.call(obj) == '[object ' + name + ']';
};
});