Array Functions First line: 470 1
2
3
4
5
6
7
8
9
_.first = _.head = _.take = function (array, n, guard ) {
if (array == null || array.length < 1 ) return void 0 ;
if (n == null || guard) return array[0 ];
return _.initial(array, array.length - n);
};
_.initial = function (array, n, guard ) {
return slice.call(array, 0 , Math .max(0 , array.length - (n == null || guard ? 1 : n)));
};
_.first可以回傳Array裡面的前n個Elements
若n沒有指定或者有guard 則回傳第一個Element
.first是透過 .initial來實作
那就來看一下_.initial做了什麼
_.initial裡面所呼叫的slice就是在underscore 一開始定義的
1
var slice = ArrayProto.slice
這邊的邏輯就很簡單
若有guard或者n=null就將傳入的Array去掉最後一個值並回傳
否則就是回傳0到n位置的Elements
line: 485 1
2
3
4
5
6
7
8
9
_.last = function (array, n, guard ) {
if (array == null || array.length < 1 ) return void 0 ;
if (n == null || guard) return array[array.length - 1 ];
return _.rest(array, Math .max(0 , array.length - n));
};
_.rest = _.tail = _.drop = function (array, n, guard ) {
return slice.call(array, n == null || guard ? 1 : n);
};
而last跟rest的邏輯就只是first跟initial反過來
Compact line: 499 1
2
3
_.compact = function (array ) {
return _.filter(array, Boolean );
};
透過_.filter把false的值濾掉
我還在想這個東西的應用情境為何
Flatten 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var flatten = function (input, shallow, strict, output ) {
output = output || [];
var idx = output.length;
for (var i = 0 , length = getLength(input); i < length; i++) {
var value = input[i];
if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
if (shallow) {
var j = 0 , len = value.length;
while (j < len) output[idx++] = value[j++];
} else {
flatten(value, shallow, strict, output);
idx = output.length;
}
} else if (!strict) {
output[idx++] = value;
}
}
return output;
};
_.flatten = function (array, shallow ) {
return flatten(array, shallow, false );
};
flatten主要的邏輯就是
遍歷最大的Array
如果Element是ArrayLike或者Array
shallow === true 就只Flatten一層Array
否則就透過Recursion將遍歷到的Array繼續做Flatten
line: 531 1
2
3
4
5
6
7
8
9
10
_.without = restArgs(function (array, otherArrays ) {
return _.difference(array, otherArrays);
});
_.difference = restArgs(function (array, rest ) {
rest = flatten(rest, true , true );
return _.filter(array, function (value ) {
return !_.contains(rest, value);
});
});
先看_.difference
主要是希望讓只出現在第一個Array的Element留下來
參數為第一個array和剩下的Array們
來看一個例子
1
2
_.difference([1 ,2 ,3 ,4 ,5 ],[1 ,2 ],[3 ,4 ]);
由於是透過restArgs來做實現
所以在這裡會讓接受到的參數變成
1
2
array = [1 , 2 , 3 , 4 , 5 ];
rest = [[1 , 2 ], [3 , 4 ]];
因此就可以在透過flatten來針對rest做處理
1
rest = flatten(rest, true , true );
最後一個true是希望讓傳入的參數是ArrayLike
否則就不flatten
因此
1
2
3
_.difference([1 ,2 ,3 ,4 ,5 ],[1 ,2 ],3 ,4 );
而_.without則是去除特定的值
不是ArrayLike的參數
Uniq line: 538 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
_.uniq = _.unique = function (array, isSorted, iteratee, context ) {
if (!_.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false ;
}
if (iteratee != null ) iteratee = cb(iteratee, context);
var result = [];
var seen = [];
for (var i = 0 , length = getLength(array); i < length; i++) {
var value = array[i],
computed = iteratee ? iteratee(value, i, array) : value;
if (isSorted) {
if (!i || seen !== computed) result.push(value);
seen = computed;
} else if (iteratee) {
if (!_.contains(seen, computed)) {
seen.push(computed);
result.push(value);
}
} else if (!_.contains(result, value)) {
result.push(value);
}
}
return result;
};
_.union = restArgs(function (arrays ) {
return _.uniq(flatten(arrays, true , true ));
});
_.uniq就透過遍歷整個array將不重複的值push到result
如果是排列過的就只要依序做比較就好
若沒排序過則會使用到_.contains 來做唯一性的比較
_.union 也是透過restArgs做實作
傳入的所有Arrays會變成Array of Array
透過flatten 再透過_.uniq將交集選出來
line: 573 1
2
3
4
5
6
7
8
9
10
11
12
13
14
_.intersection = function (array ) {
var result = [];
var argsLength = arguments .length;
for (var i = 0 , length = getLength(array); i < length; i++) {
var item = array[i];
if (_.contains(result, item)) continue ;
var j;
for (j = 1 ; j < argsLength; j++) {
if (!_.contains(arguments [j], item)) break ;
}
if (j === argsLength) result.push(item);
}
return result;
};
_.intersection這邊剛開始看的時候想說作者怎麼不用restArgs去實作
然後在參數的array 跟 arguments那邊卡了一下
原來如果在function內部的array只會refernce到輸入的第一個array
因此這裡主要的邏輯就是拿第一個Array的所有元素來和其他Array們比較
如果沒有包含就換下一個元素
因為是intersection
只要檢查完第一個Array就可以
因為之後就算其他Array有的元素 第一個Array也不會有
也試著用restArgs以同個邏輯實作看看
並且比較兩者的速度
intersection2 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
_.intersection2 = restArgs(function (arrays ) {
var result = [];
var argsLength = arrays.length;
var firstArray = arrays[0 ];
for (var i = 0 , length = getLength(firstArray); i < length; i++) {
var item = firstArray[i];
if (_.contains(result, item)) continue ;
var j;
for (j = 1 ; j < argsLength; j++) {
if (!_.contains(arrays[j], item)) break ;
}
if (j === argsLength) result.push(item);
}
return result;
});
(function ( ) {
var times = 100000 ;
console .time('original' )
while (times > 0 ){
_.intersection([1 ,2 ,3 ,4 ,5 ],[5 ,3 ,2 ,4 ],[2 ,3 ,4 ]);
times --;
}
console .timeEnd('original' )
times = 100000 ;
console .time('restArgs' )
while (times > 0 ){
_.intersection2([1 ,2 ,3 ,4 ,5 ],[5 ,3 ,2 ,4 ],[2 ,3 ,4 ]);
times --;
}
console .timeEnd('restArgs' )
}())
看到結果就笑了XD
restArgs的版本幾乎慢了五倍
雖然早就預期會比較慢
但沒想到會慢那麼多
所以像作者那樣的寫法
還真的是要很了解javascript對arguments的操作方式啊!
Zip line: 599 1
2
3
4
5
6
7
8
9
10
11
_.unzip = function (array ) {
var length = array && _.max(array, getLength).length || 0 ;
var result = Array (length);
for (var index = 0 ; index < length; index++) {
result[index] = _.pluck(array, index);
}
return result;
};
_.zip = restArgs(_.unzip);
這邊的解法真的很有趣
導致會有下面的結果產生
1
2
3
4
5
6
7
8
var ziped = _.zip(['moe' , 'larry' , 'curly' ], [30 , 40 , 50 ], [true , false , false ]);
var unziped = _.unzip(ziped);
var failziped = _.zip(unziped);
1
2
3
4
5
6
7
8
9
10
_.chunk = function (array, count ) {
if (count == null || count < 1 ) return [];
var result = [];
var i = 0 , length = array.length;
while (i < length) {
result.push(slice.call(array, i, i += count));
}
return result;
};
將Array切成每個chunck的element數量等於count的array
這邊透過slice並且用i += count來更新i的位置