Underscore零碎笔记 · Lord Camelot

1
2
3
4
5
6
7
8
9
var _ = function(obj) {
// 若obj已经是_的一个实例,就直接返回
if (obj instanceof _) return obj;
// 若不是通过new调用的函数,就返回一个通过new创建的obj实例
if (!(this instanceof _)) return new _(obj);
// 通过new创建才会触发这一步,将obj赋给实例对象的wrapped属性
this.wrapped = obj;
};

这个函数创建一个包含有wrapped: obj属性的对象

如果通过new _(obj)来调用,此时this instanceof _true,直接为this添加属性
如果是以_(obj)的方式调用,返回new _(obj)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 返回一个this指向context的func函数
var optimizeCb = function(func, context, argCount) {
// 若没有给出context参数,直接返回func
if (context === void 0) return func;
// 根据参数数量决定使用call还是apply
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);
};
};

看起来很复杂的函数,但是本质上只有return func.apply(context, arguments);这一句

switch语句在这里起到一个优化的作用
.call() 的调用速度比.apply()要快一些,因此针对argCount<=4的情况专门做了个优化

为什么使用void 0
undefined并不是一个保留字,在低版本的浏览器中可能被重写;
void操作符会计算紧跟在后面的表达式并返回一个undefined


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 创建reduce函数
function (dir) {
// 执行迭代reduce的部分
// obj => 执行对象
// iteratee => reduce中处理转换数据的函数
// iteratee(memo, value, key, obj)
// memo => reduce初始值,reduce会在memo上面进行
// keys => 若obj不是一个类数组对象,keys应当是obj的键的数组
// index => 起始索引
// length => obj或obj的keys的长度
//
// 若obj是一个类数组对象,遍历obj;否则,遍历obj的key
// iteratee应当对将结果保存在第一个参数memo上
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转换为在context下执行的回调函数
iteratee = optimizeCb(iteratee, context, 4);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
// dir > 0,从左向右reduce,从0开始
// dir < 0, 从右向左reduce,从最后开始
index = dir > 0 ? 0 : length - 1;
// 若没有指定memo,使用index处的value值作为memo初始值,并更新index
if (arguments.length < 3) {
memo = object[keys ? keys[index] : index];
index += dir;
}
// 返回reduce结果
return iterator(obj, iteratee, memo, keys, index, length);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_.sortBy = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) {
// 遍历obj,返回{value, index, criteria}格式的对象数组
// 其中criteria是执行iteratee后,返回判断基准
// value属性方便进行pluck操作
return {
value: value,
index: index,
criteria: iteratee(value, inded, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
// undefined排到后面
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
});

本质上可以简化为_.pluck(obj.sort(), ‘value’)
先遍历obj,将obj转换为特定格式的对象组成的数组
然后对进行sort操作,最后取出对象的value属性


1
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

正常情况下诸如toString这样的属性是不可枚举的,在for key in obj这种循环中是不会出现的
但是如果重写了toString属性,比如{toString: function() {alert(1)}这样,toString会被覆盖为可枚举的
但是IE<9的浏览器中有一个bug,默认不可枚举的属性即使被同名可枚举的属性覆盖,也依然不可枚举
因此这一段代码就是用来判断是否有这个bug
http://stackoverflow.com/questions/7367519/ie8-property-enumeration-of-replaced-built-in-properties-e-g-tostring

obj.propertyIsEnumerable(key):判断obj的key是否可枚举,返回boolean
obj.hasOwnProperty(key):判断obj的key是来自自身还是来自其原型链
Object.getOwnPropertyDescriptor():获得obj自身属性的描述,也可以用来判断是否枚举。与propertyIsEnumeable()的区别是后者可以判断原型链上的属性。
https://www.zhihu.com/question/21907133

Object.keys()只返回对象自身可枚举的属性。
For...in返回对象所有可枚举的属性,包括原型链继承得到的