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))) {
// Flatten current level of array or arguments object.
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]);
// output: [5]

由於是透過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);
// output: [3,4,5]
// not: [5]

而_.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;
});
/* compare */
(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')
}())
// original: 77.72ms
// restArgs: 330.04ms

看到結果就笑了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]);
//ziped: [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
var unziped = _.unzip(ziped);
//unziped: [['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]]
var failziped = _.zip(unziped);
//failziped: [[["moe", "larry", "curly"]], [[30, 40, 50]], [[true, false, false]]]
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的位置