Collection Functions Each line: 165 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_.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;
};
先透過optimizeCb將傳入的迭代器作優化
再來透過 isArrayLike判斷物件為Array或者是Object
再分別用不同的方式迭代
Array 可以直接透過index, Object則要先取得各個key value pair的key去做迭代
並套用先前優化過的迭代器
Map line: 182 1
2
3
4
5
6
7
8
9
10
11
_.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;
};
這段程式碼一開始是先透過cb取得相對應的callback function
透過cb,optimizeCb這兩個function
iteratee 應該會變成這樣接受3個參數的function
1
iteratee(value, index, collection)
接下來是要遍歷整個Object或者Array並且透過iteratee來做迭代
並將迭代的值傳入results
1
2
3
4
5
6
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
results = Array (length);
createReduce line 195 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var createReduce = function (dir ) {
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);
};
};
createReduce這個function執行後會回傳一個匿名function
可以接受4個參數
這個function在arguments小於三的時候要進行initialize
而reducer的部分
一開始是和_.map一樣取得length
這裡不一樣的是reduce有方向
若是dir = 1則是從左邊開始 此時index = 0
dir = -1則是從右邊開始 此時 index = length - 1
memo是先暫存當前index的值也就是 obj[0 || length-1]
再來就是把index指向memo的下一個位置 不論是向左reduce或者向右reduce
接者進到for迴圈當中進行reduce
for迴圈內部的iteratee 是經過optimizeCb所優化的callback function
1
function iteratee (accumulator, value, index, collection );
而accumulator就是我們的memo當前的值
透過這樣的方式就可以建立一個reduce function
Reduce line: 221 1
2
3
_.reduce = _.foldl = _.inject = createReduce(1 );
_.reduceRight = _.foldr = createReduce(-1 );
所有的reduce function都可以透過createReduce來產生
Find 要使用_.find 之前要先看 createPredicateIndexFinder
line: 629 1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 ;
};
};
_.findIndex = createPredicateIndexFinder(1 );
_.findLastIndex = createPredicateIndexFinder(-1 );
透過createPredicateIndexFinder來產生 .findIndex, .findLastIndex這兩個函式
predicate(判斷函示)其實也就是個回傳true,false的function
createPredicateIndexFinder會先將predicate丟到cb裡
由於predicate是個函式
所以會透過optimizeCb去做優化
再來就是遍歷整個Array來找到回傳true的值時的index
並回傳出去
結束整個find的過程
再來則是針對Object的findKey
line: 1070 1
2
3
4
5
6
7
8
_.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;
}
};
大致上的邏輯和先前許多例子差不多
line: 227 1
2
3
4
5
_.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
之後大概就可以知道find就是將Array的find 跟 Object的 findKey集合在一起的函式
並透過obj[key]來得到value
Filter line: 235 1
2
3
4
5
6
7
8
9
10
11
12
13
_.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;
};
_.reject = function (obj, predicate, context ) {
return _.filter(obj, _.negate(cb(predicate)), context);
};
filter將傳入的obj透過_.each遍歷一遍
並將predicate函式執行為true的值push到results內
最後回傳results
而reject則是透過_.negate取得相反的結果
line: 839 1
2
3
4
5
_.negate = function (predicate ) {
return function ( ) {
return !predicate.apply(this , arguments );
};
};
Every 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_.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 ;
};
_.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 ;
};
這幾個函式分別是檢測Obj能否通過predicate判斷
包含全部都通過 .every = .all
部分通過 .some = .any
Contains line:277 1
2
3
4
5
_.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 ;
};
單純就是檢驗Array,Object內有沒有某個item
第一個if是將Object的所有value變成一個ArrayLikeObj
Invoke line: 284 1
2
3
4
5
6
7
_.invoke = restArgs(function (obj, method, args ) {
var isFunc = _.isFunction(method);
return _.map(obj, function (value ) {
var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args);
});
});
_.invoke是透過restArgs來呼叫
這邊就可以透過很好的例子來理解restArgs到底是做什麼用的
先透過_.isFunction來判斷method是否是一個function
再來透過_.map去遍歷obj中所有的物件
接著若isFunc是真就代表method本身就是個function
否則就去試著用value[mehtod]取得遍歷的物件的method
若都沒有則回傳null
最後舉個例子
1
2
3
var Arr = [[1,2,3,4,5],[2,3,4,1,5,6],[1,5,3,3,6,3,2]];
_.invoke(Arr,'slice',0,2); //[[1, 2], [2, 3], [1, 5]]
由於這幾行程式碼的關係
restArgs 1
2
3
4
5
6
7
8
9
startIndex = startIndex == null ? func.length - 1 : +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);
}
invoke 1
_.invoke = restArgs(function (obj, method, args ) {
這裡的func.length-1等於2
所以restArgs會將 最後的兩的args 0,2變成Array
所以map出來的function會變成func.apply(value, [0,2])
讓剩餘的函數可以做更好的操作
Convenience version of XXX line: 293 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_.pluck = function (obj, key ) {
return _.map(obj, _.property(key));
};
_.where = function (obj, attrs ) {
return _.filter(obj, _.matcher(attrs));
};
_.findWhere = function (obj, attrs ) {
return _.find(obj, _.matcher(attrs));
};
Max & Min line: 310 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
38
39
40
41
42
43
44
45
46
47
_.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;
};
_.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;
};
.max 跟 .min可以單純比較數值的大小
也能透過iteratee去迭代後比較大小
並且回傳原來的值
sample & shuffle line: 360 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_.shuffle = function (obj ) {
return _.sample(obj, Infinity );
};
_.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);
};
Sample 是透過 Fisher-Yates shuffle 實作
從obj中取出隨機的n個值
_.sample 接受三個參數 obj, n, guard
若n === null 或者有gurad 則回傳obj中隨機的一個值
首先 sample 先複製一份一模一樣的值存在Array當中
n 為指定的smaple數量 若大於obj長度 則以obj長度為n
for迴圈中跑的就是Fisher-Yates shffle
最後return 時透過slice達到sample的效果
因此_.shuffle的實作就非常簡單
只要將n指定為Infinity就可以了
sortBy line: 387 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_.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' );
};
這段程式碼呼叫了兩個underscore的function
和一個javascript內建的sort function
先從map來看
將Obj內的element先map到一個新的object
1
2
3
4
5
{
value : value,
indes: index++,
criteria: iteratee(value, key, list)
}
Object中的iteratee也是透過 cb 來回傳的iteratee
因此可以是一個function,object或者是一個字串
以字串為例子
1
2
iteratee = cb('name' , context);
所以criteria存的值就會是value的name property
在map完之後透過sort去自行定義一個compareFunction
並去比較criteria
藉此完成sorting
但此時sorting完的Array我們mapping完的格式
因此可以透過_.pluck的方式將value property的值取出來
得到我們要的結果
Group line: 408 1
2
3
4
5
6
7
8
9
10
11
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;
};
};
先看group function
這是個underscore 內部使用的function
用來產生.groupBy, .indexBy, _.countBy 等function
group接受兩個參數 behavior, partition
並且回傳一個function
而behavior簡單來說就是 依照相對應的value跟key
我要怎麼去產生result
所以group的邏輯就是
回傳一個function可以接受三個參數 obj, iteratee, context
跟之前一樣透過 cb 產生相對應的 iteratee
透過_.each去遍歷obj中的每一個物件
利用iteratee去產生key
透過behavior去產生不同的result
接下來看不同的behavior可以產生什麼樣的result
line: 422 1
2
3
_.groupBy = group(function (result, value, key ) {
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
});
第一個_.groupBy 的behavior很簡單
透過 _.has來檢驗result有沒有相對應的key
若有則直接push value到相對應key底下的array
若沒有則產生一個新的key 並將 [value] 指定在該key底下
1
2
3
_.indexBy = group(function (result, value, key ) {
result[key] = value;
});
再來 _.indexBy
直接產生一個新的key 並將 [value] 指定在該key底下
因此要先確定key都是唯一的
1
2
3
_.countBy = group(function (result, value, key ) {
if (_.has(result, key)) result[key]++; else result[key] = 1 ;
});
第三個 _.countBy
透過 _.has來檢驗result有沒有相對應的key
若有直接將相對應的key的value + 1
若沒有則產生一個新的key 並將 value 初始成1
1
2
3
_.partition = group(function (result, value, pass ) {
result[pass ? 0 : 1 ].push(value);
}, true );
最後一個 _.partition (二分法)
在前面介紹underscore group function的時候跳過了一段
就是
1
var result = partition ? [[], []] : {};
在大部分情況下我們做Gourping都會回傳一個Obj
而_.partition 就是一個例外的情況
在這個情況下我們的結果會是一個Array 裡面有兩的Array 的Element
透過iteratee去判斷輸入obj是否通過
藉此將value分別push到不同的Array當中