請啟用 Javascript 查看內容

Lodash - JS 實用工具函式庫

 ·  ☕ 11 分鐘

這週是 六角鼠年鐵人賽 第六週,上週剛好接觸到 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 相等比較),會回傳一個新陣列。

1
_.union([arrays])
  • [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 相等比較),會回傳一個新陣列。

1
_.uniq(array)
  • 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(集合)的方法 陣列物件 都可以使用,部分方法字串也可以。

其中 mapfilter 方法最常使用,因為原生 JavaScrtip 中是不支援物件的,因此當需要迭代物件時,會使用 for inObject.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 洗牌算法)。

1
_.shuffle(collection)
  • 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):呼叫每一個屬性的函式。
    • 有 2個參數:(value, key)
  • 回傳值:回傳新物件
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()

資料的深拷貝。

1
_.cloneDeep(value)
  • 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()

隨機產生一個包括 lowerupper 之間的數,如果只設置一個參數則回傳 0 到該數之間的數值。

1
_.random([lower=0], [upper=1], [floating])
  • [lower=0] (number):下限。
  • [upper=1] (number):上限。
  • [floating] (boolean):指定是否回傳浮點數。
    • lowerupper 非浮點數預設為 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 是否在 startend 之間(但不包括 end)。

1
_.inRange(number, [start=0], end)
  • number (number):要檢查的值。
  • [start=0] (number):開始範圍。
    • 可選,沒設置預設值為 0
  • end (number):結束範圍。
    • 如果第二個參數大於第三個參數,大的為 end
 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
_.sum(array)
1
2
_.sum([4, 2, 8, 6]);
// => 20

如果元素是物件,可以使用 _.sumBy()

總結

這週先記錄到這邊,有空慢慢補上,下週見。


竹白
作者
竹白
前端筆記

文章目錄