這週是 六角鼠年鐵人賽 第六週,上週剛好接觸到 Lodash,就簡單紀錄一下。
簡介
你很可能需要經常重複寫一些工具函式,尤其是處理字串與陣列或是物件。為了使開發專案更有效率且好維護,Lodash 可能是你好選擇。
Lodash 是一個一致性、模組化、高性能的 JavaScript 實用工具函式庫,包含對字串、陣列、物件等常見型別的處理函式。
其中部分是目前 ECMAScript 尚未制訂的規範,但同時被業界所認可的輔助函式。
目前在寫這篇文章時,Lodash 在 github 上星數高達 43.8k,版本為 4.17.15。
另外,在 npm 中依賴 Lodash 的 package 非常多:packages depending on lodash。
API
Lodash 提供了非常多的工具方法,包含
- Array:適用於 陣列,例如分割、合併、刪除、查詢、重組等操作。
- Collection:適用於 陣列 和 物件,部分可用於字串,例如遍歷、分組、查詢、過濾等操作。
- Date:只有一個
now
方法。
- Function:函式的操作。
- Lang:各種型別的轉換、判斷或是深拷貝。
- Math:基本的數學運算,例如數值的四捨五入、數組的最大、最小值等等。
- Number:取得隨機數、判斷數值區間、求中間值。
- Object:適用於 物件,例如物件的創建、擴展、轉換、搜索、集合等操作。
- Seq:常用於創建鏈式呼叫,提高執行性能(惰性計算)。
- String:適用於 字串 操作。
- Util:實用工具函式。
1. 為什麼不用 ES6 語法就好?
Lodash 有很多方法 ES6 已經封裝好了,而且部分方法也可以使用 ES6 語法替換。加上 ES6 越來越多主流瀏覽器支持,另外還可以使用 Babel 將 ES6 編譯成 ES5。那麼我們還需要使用 Lodash 嗎?
可以參考以下兩篇文章:
2. 惰性求值
Lodash 另一個優勢就是其優異的計算性能。很大部分就來源於其使用的算法「惰性求值」。
關於 惰性求值 可以參考以下文章:
3. SameValueZero 相等比
部分方法使用 SameValueZero 相等比,關於 SameValueZero 可以看這篇文章: ECMAScript 6相等演算 。
安裝
1. 瀏覽器
下載檔案,直接引入:
1
|
<script src="lodash.js"></script>
|
或是使用 CDN。
2. 在專案中使用 lodash
透過 npm 安裝:
npm i --save lodash
接著在你的程式碼中 import
即可:
1
|
import _ from 'lodash';
|
但這樣做會將整包 Lodash 打包進去。如果只使用 Lodash 其中一兩個方法,會造成了不必要的資源浪費。
解決辦法有三種:
- 單獨引入:lodash 每個函式都具有單獨的模組,可以只引入需要的模組。
1
2
3
4
5
|
import _map from 'lodash.map';
import _random from 'lodash.random';
// or
import _map from 'lodash/map';
import _random from 'lodash/random';
|
1
|
import { mapm , random } from 'lodash-es';
|
不過,是否需要對 Lodash 優化,可以參考這篇文章:lodash 在 webpack 中的各項優化的嘗試。
2. Vue Cli
Vue 除了上述方式,還可以使用 vue-lodash
套件:
1
2
3
4
5
|
// main.js
import Vue from 'vue';
import VueLodash from 'vue-lodash';
import lodash from 'lodash';
Vue.use(VueLodash, { name: 'custom', lodash: lodash });
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
export default {
methods: {
test() {
console.log( this.lodash.random(20) );
console.log( this._.random(20) );
console.log( this.custom.random(20) );
},
}
};
console.log( Vue.lodash.random(20) );
console.log( Vue._.random(20) );
console.log( Vue.custom.random(20) );
|
常用方法
由於 Lodash 中 API 太多了,這裡只會列幾個會常用到的方法並簡單說明。更詳細的用法、參數可以看官方 API 文件。
1. Array
_.union()
將陣列合併,並去除重複的元素(SameValueZero 相等比較),會回傳一個新陣列。
[arrays] (...Array)
: 陣列組
- 回傳值:回傳新的陣列
1
2
3
4
5
|
let arr1 = [1, 2, 3, 4];
let arr2 = [2, 4, 6, 8];
let arr3 = [3, 5, 7, 9];
console.log( _.union(arr1, arr2, arr3) );
|
如果元素是物件,可使用 _.unionBy
,另外還有 _.unionWith()
可以呼叫一個比較函式。
_.intersection()
取得陣列組的交集元素(使用 SameValueZero 相等比較)。
1
|
_.intersection([arrays])
|
[arrays] (...Array)
:陣列組
- 回傳值:交集元素陣列
1
2
3
4
5
6
7
|
let arr1 = [0, 1, 2, 3, 4];
let arr2 = [0, 2, 4, 6, 8];
let arr3 = [0, 2, 3, 5, 7, 9];
console.log( _.intersection(arr1, arr2, arr3) );
// [0, 2]
|
如果元素是物件,可使用 _.intersectionBy()
,另外還有 _.intersectionWith()
可以呼叫一個比較函式。
_.difference()
檢查一個陣列,並將指定的值排除(SameValueZero 相等比較),會回傳一個新陣列。
與 _.difference()
類似的方法還有 _.without()
與 _.pull()
。
1
|
_.difference(array, [values])
|
array (Array)
:需要被檢查的陣列
[values] (...Array)
:需要排除的值(放在陣列中)
- 回傳值:回傳新的陣列
1
2
3
|
let arr = [0, 1, 2, 3, 4];
console.log( _.difference(arr, [2, 4, 6, 8]) ); // [0, 1, 3]
|
如果元素是物件,可使用 _.differenceBy()
,另外還有 _.differenceWith()
可以呼叫一個比較函式。
_.uniq()
陣列重組,去除重複的元素(SameValueZero 相等比較),會回傳一個新陣列。
array (Array)
: 要檢查的陣列
- 回傳值:回傳新的陣列
1
2
|
console.log( _.uniq([1, 2, 2, 4, 1, 5]) );
// [1, 2, 4, 5]
|
如果元素是物件,可使用 _.uniqBy()
,另外還有 _.uniqWith()
可以呼叫一個比較函式。
_.indexOf()
搜尋陣列是否有值為 value
的元素(使用 SameValueZero 等值比較),並回傳第一個符合的索引值。
1
|
_.indexOf(array, value, [fromIndex=0])
|
array (Array)
:要查詢的陣列
value (*)
:條件值
[fromIndex=0] (number)
:初始位置
- 預設值為
0
也就是開頭
- 如果
fromIndex
為負值,則從陣列結尾數過來
- 回傳值:匹配值的索引值,否則回傳
-1
1
2
3
4
5
|
let arr = ['a', 'b', 'c', 'a', 'b', 'c'];
console.log( _.indexOf(arr, 'b') ); // 1
console.log( _.indexOf(arr, 'b', -1) ); // -1
console.log( _.indexOf(arr, 'b', -2) ); // 4
|
_.lastIndexOf()
這個方法類似 indexOf
,區別是它是從右到左遍歷陣列的元素,預設起始位置為結尾。
1
|
_.lastIndexOf(array, value, [fromIndex=array.length-1])
|
array (Array)
:要查詢的陣列
value (*)
:條件值
[fromIndex=array.length-1](number)
:初始位置
- 預設值為
array.length-1
也就是結尾
- 如果
fromIndex
為負值,則從陣列結尾數過來
- 回傳值:匹配值的索引值,否則回傳
-1
1
2
3
4
5
6
|
let arr = ['a', 'b', 'c', 'a', 'b', 'c'];
console.log( _.lastIndexOf(arr, 'b') ); // 4
console.log( _.lastIndexOf(arr, 'b', 0) ); // -1
console.log( _.lastIndexOf(arr, 'b', 2) ); // 1
console.log( _.lastIndexOf(arr, 'b', -2) ); // 4
|
2. Collection
Collection(集合)的方法 陣列 或 物件 都可以使用,部分方法字串也可以。
其中 map
及 filter
方法最常使用,因為原生 JavaScrtip 中是不支援物件的,因此當需要迭代物件時,會使用 for in
或 Object.keys
。
_.map()
迭代的集合的方法,回傳一個新陣列。
1
|
_.map(collection, [iteratee=_.identity])
|
collection (Array|Object)
:用來迭代的集合。
[iteratee=_.identity] (Array|Function|Object|string)
:每次迭代呼叫的函式。
- 參數:
(value, index|key, collection)
- 回傳值:新陣列
Lodash 中有許多方法是防止作為其他方法的迭代函式(註:即不能作為 iteratee
參數傳遞給其他方法),例如: _.every
、 _.filter
、 _.map
、 _.mapValues
、 _.reject
和 _.some
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
console.log( _.map([4, 8], (n) => n * n) );
// [16, 64]
const square = (n) => n * n;
let arr = [2, 4];
let obj = { a: 2, b: 4 };
console.log( _.map(arr, square) );
// [4, 16]
console.log( _.map(obj, square) );
// [4, 16]
let users = [{ user: 'barney' }, { user: 'fred' }];
console.log( _.map(users, 'user') );
// ['barney', 'fred']
|
另外可以參考這篇文章:map () 誰比較快和輕量? Lodash vs ES6。
_.filter()
根據條件過濾出符合條件的元素。
1
|
_.filter(collection, [predicate=_.identity])
|
collection (Array|Object)
:一個用來迭代的集合。
[predicate=_.identity] (Array|Function|Object|string)
:每次迭代呼叫的函式。
- 回傳值:回傳新的陣列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
let users = [
{ user: 'barney', age: 36, active: true },
{ user: 'fred', age: 40, active: false },
];
_.filter(users, function(o) {
return !o.active;
});
// => objects for ['fred']
// The `_.matches` iteratee shorthand.
_.filter(users, { age: 36, active: true });
// => objects for ['barney']
// The `_.matchesProperty` iteratee shorthand.
_.filter(users, ['active', false]);
// => objects for ['fred']
// The `_.property` iteratee shorthand.
_.filter(users, 'active');
// => objects for ['barney']
|
_.includes()
檢查 value
是否在 collection
中。
1
|
_.includes(collection, value, [fromIndex=0])
|
collection (Array|Object|string)
:要檢索的集合。
value (*)
:要檢索的值。
[fromIndex=0] (number)
:要檢索的索引位置。
如果 collection
是一串,value
就是字元,否則使用 SameValueZero 做等值比較。
1
2
3
4
5
6
7
8
9
10
11
|
_.includes([1, 2, 3], 1);
// => true
_.includes([1, 2, 3], 1, 2);
// => false
_.includes({ user: 'fred', age: 40 }, 'fred');
// => true
_.includes('pebbles', 'eb');
// => true
|
_.shuffle()
將集合打亂(使用 Fisher-Yates shuffle 洗牌算法)。
collection (Array|Object)
:要打亂的集合
- 回傳值:回傳隨機元素的陣列
1
2
3
4
5
6
7
8
9
|
let arr = [0, 1, 2, 3, 4];
console.log( _.shuffle(arr) ); // [ 3, 4, 1, 0, 2 ]
console.log( _.shuffle(arr) ); // [ 3, 0, 2, 4, 1 ]
let obj = { a: 1, b: 2, c: 3 };
console.log( _.shuffle(obj) ); // [ 2, 3, 1 ]
console.log( _.shuffle(obj) ); // [ 1, 3, 2 ]
|
_.sampleSize()
從集合中獲得 n
個隨機元素。
1
|
_.sampleSize(collection, [n=1])
|
collection (Array|Object)
:要取樣的集合
[n=1] (number)
:取樣的元素個數。
- 回傳值:回傳隨機元素的陣列
1
2
3
4
5
6
|
let arr = [0, 1, 2, 3, 4];
console.log( _.sampleSize(arr, 1) ); // [2]
console.log( _.sampleSize(arr, 1) ); // [0]
console.log( _.sampleSize(arr, 2) ); // [2, 1]
console.log( _.sampleSize(arr, 2) ); // [0, 4]
|
_.sortBy()
創建一個元素陣列。以 iteratee
處理的結果升序排序。這個方法執行穩定排序,也就是說相同元素會保持原始排序
1
|
_.sortBy(collection, [iteratees=[_.identity]])
|
collection (Array|Object)
:用來迭代的集合。
[iteratees=[_.identity]] (...* (Array|Array[]|Function|Function[]|Object|Object[]|string|string[]))
:這個函式決定排序。
- 回傳值:排序後的陣列
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
|
let arr = [1, 65, 44, 2, 3, 55];
console.log( _.sortBy(arr) );
let users = [
{ user: 'fred', age: 48 },
{ user: 'barney', age: 36 },
{ user: 'fred', age: 40 },
{ user: 'barney', age: 34 },
];
console.log(_.sortBy(users, (item) => item.user));
// [ { user: 'barney', age: 36 },
// { user: 'barney', age: 34 },
// { user: 'fred', age: 48 },
// { user: 'fred', age: 40 } ]
console.log(_.sortBy(users, 'age'));
// [ { user: 'barney', age: 34 },
// { user: 'barney', age: 36 },
// { user: 'fred', age: 40 },
// { user: 'fred', age: 48 } ]
console.log(_.sortBy(users, ['user', 'age']));
// [ { user: 'barney', age: 34 },
// { user: 'barney', age: 36 },
// { user: 'fred', age: 40 },
// { user: 'fred', age: 48 } ]
|
3. Object
_.get()
根據物件的路徑取值,沒有則回傳 undefined
,可以設定預設值。
1
|
_.get(object, path, [defaultValue])
|
object (Object)
:要檢索的物件。
path (Array|string)
:要獲取屬性的路徑,可以是字串或陣列。
[defaultValue] (*)
:預設值。
- 回傳值:解析的值
1
2
3
4
5
6
7
|
let obj = { id: '1234', list: ['a', 'b', 'c', 'd'] };
console.log( _.get(obj, 'id') ); // '1234'
console.log( _.get(obj, 'list[0]') ); // 'a'
console.log( _.get(obj, ['list', '3']) ); // 'd'
console.log( _.get(obj, 'name'); // undefined
console.log( _.get(obj, 'name', 'no-find') ); // 'no-find'
|
_.pick()
創建一個從 object 中選中的屬性的物件。
1
|
_.pick(object, [props])
|
object (Object)
:來源物件。
[props] (...(string|string[]))
:選中的屬性。(註:單獨指定或指定在陣列中。)
- 回傳值:回傳新物件
1
2
3
|
let object = { a: 1, b: '2', c: 3 };
console.log( _.pick(object, ['a', 'c']) ); // { a:1, c:3 }
|
_.omit
為反向版的 _.pick
。
_.pickBy()
創建一個物件,這個物件組成為從 object
中經 predicate
判斷為真值的屬性。
1
|
_.pickBy(object, [predicate=_.identity])
|
object (Object)
:來源物件。
[predicate=_.identity] (Function)
:呼叫每一個屬性的函式。
- 回傳值:回傳新物件
1
2
3
4
|
let obj = { a: 1, b: '2', c: 3 };
console.log( _.pickBy(obj, _.isNumber) );
// { 'a': 1, 'c': 3 }
|
_.omitBy
為反向版的 _.pickBy
。
_.defaults()
安全的合併物件,遇到重複的屬性則忽略。
1
|
_.defaults(object, [sources])
|
object (Object)
:目標物件。
[sources] (...Object)
:來源物件。
- 回傳值:這方法會改變目標物件,回傳目標物件
1
2
3
4
5
6
7
8
|
let obj1 = { foo: 1, boo: 2 };
let obj2 = { foo: 3, zoo: 4 };
let r = _.defaults(obj1, obj2);
console.log(r); // { foo: 1, boo: 2, zoo: 4 }
console.log(obj1); // { foo: 1, boo: 2, zoo: 4 }
console.log(obj1 === r); // true
|
4. Lang
_.cloneDeep()
資料的深拷貝。
value (*)
:要拷貝的資料。
- 回傳值:回傳拷貝的資料。
1
2
3
4
|
let obj = { a: { b: 1 } };
let newObj = _.cloneDeep(obj);
console.log( obj.a === newObj.a ); // false
|
另外,可以參考以下文章:
_.isEqual()
執行深比較來確定兩者的值是否相等。
1
|
_.isEqual(value, other)
|
value (*)
:用來比較的值。
other (*)
:另一個用來比較的值。
- 回傳值:布林值
如果是物件,則比較自身的屬性,不包括繼承的和可列舉的屬性。不支持函式和DOM 節點比較。
1
2
3
4
5
6
7
|
let object = { a: 1 };
let other = { a: 1 };
console.log(object === other);
// false
console.log(_.isEqual(object, other));
// true
|
5. Number
_.clamp()
設定數值上下限,超出範圍,回傳上限或下限,否則回傳數值本身。
1
|
_.clamp(number, [lower], upper)
|
number (number)
:被限制的值。
[lower] (number)
:可選,下限值。
upper (number)
:上限值。
- 回傳值:
number
大於 upper
回傳 lower
number
小於 lower
回傳 lower
- 在
upper
之間 lower
回傳 number
本身
1
2
3
4
5
6
|
console.log( _.clamp(5, 10) ); // 5
console.log( _.clamp(11, 10) ); // 10
console.log( _.clamp(11, -10, 10) ); // 10
console.log( _.clamp(-11, -10, 10) ); // -10
console.log( _.clamp(0, -10, 10) ); // 0
|
_.random()
隨機產生一個包括 lower
與 upper
之間的數,如果只設置一個參數則回傳 0
到該數之間的數值。
1
|
_.random([lower=0], [upper=1], [floating])
|
[lower=0] (number)
:下限。
[upper=1] (number)
:上限。
[floating] (boolean)
:指定是否回傳浮點數。
lower
與 upper
非浮點數預設為 false
1
2
3
4
5
6
7
8
9
10
|
_.random(5);
// between 0 and 5
_.random(-5);
// between -5 and 0
_.random(5, 10);
// between 5 and 10
_.random(5, true);
// a floating-point number between 0 and 5
_.random(1.2, 5.2);
// a floating-point number between 1.2 and 5.2
|
_.inRange()
檢查 number
是否在 start
與 end
之間(但不包括 end
)。
1
|
_.inRange(number, [start=0], end)
|
number (number)
:要檢查的值。
[start=0] (number)
:開始範圍。
end (number)
:結束範圍。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
_.inRange(3, 2, 4);
// 3 是否在 2~3 之間 => true
_.inRange(4, 8);
// 4 是否在 0~7 之間 => true
_.inRange(4, 2);
// 4 是否在 0~1 之間 => false
_.inRange(2, 2);
// 2 是否在 0~1 之間 => false
_.inRange(1.2, 2);
// 1.2 是否在 0~1 之間 => true
_.inRange(3.2, 4);
// 3.2 是否在 0~3 之間 => false
_.inRange(0, 4, 0);
// 0 是否在 0~3 之間 => true
_.inRange(-3, -2, -6);
// -3 是否在 -6~-2 之間 => true
|
6. Util
_.range()
建立一個指定範圍的數值陣列。
1
|
_.range([start=0], end, [step=1])
|
[start=0] (number
:開始的範圍。
end (number)
:結束的範圍(不包括自己)。
[step=1] (number)
:範圍的增量 或者 減量。
只有一個參數 _.range(n)
等同 _.range(0, n, 1)
,如果是負數 _.range(-n)
等同 _.range(0, n, -1)
。
end
小於 start
而且 step
非負數,會創建一個空陣列。
1
2
3
4
5
6
|
console.log( _.range(4) ); // [0, 1, 2, 3, 4]
console.log( _.range(-4) ); // [0, -1, -2, -3]
console.log( _.range(1, 4) ); // [1, 2, 3]
console.log( _.range(0, 10, 5) ); // [0, 5, 10]
console.log( _.range(0, -4, -1) ); // [0, -1, -2, -3]
console.log( _.range(1, 4, 0) ); // [1, 1, 1]
|
_.times()
呼叫 iteratee
函式 n
次,並將結果存到陣列中,並回傳該陣列。
1
|
_.times(n, [iteratee=_.identity])
|
n (number)
:呼叫 iteratee
的次數。
[iteratee=_.identity] (Function)
:每次迭代呼叫的函式,參數為 index
。
1
2
3
4
|
console.log(_.times(3, String));
// ['0', '1', '2']
console.log(_.times(3, (index) => index * index));
// [0, 1, 4]
|
_.uniqueId()
生成唯一 ID,可以添加前綴。
1
|
_.uniqueId([prefix=''])
|
[prefix=''] (string)
:要添加到 ID 前綴的值。
- 回傳值:回傳生成的唯一 ID 字串。
1
2
3
|
console.log( _.uniqueId() ); // '1'
console.log( _.uniqueId() ); // '2'
console.log( _.uniqueId('book') ); // 'book3'
|
7. Math
_.sum()
陣列的總和。
1
2
|
_.sum([4, 2, 8, 6]);
// => 20
|
如果元素是物件,可以使用 _.sumBy()
。
總結
這週先記錄到這邊,有空慢慢補上,下週見。