如何鏈式調用函式?
> 最近看到這個面試題,發現我原來不會
提供了一個數組結構的 data,要求實現一個 query 方法,返回一個新的數組,query 方法內部有 過濾、排序、分組 等操作,並且支持鏈式調用,調用最終的 execute 方法返回結果:
Copy!const result = query(list) .where((item) => item.age > 18) .sortBy('id') .groupBy('name') .execute() console.log(result)
我們可以在 jQuery 中常看到這樣的用法
jQuery
Copy!$('p1').css('color', 'red').slideUp(2000).slideDown(2000)
在操作 Array 的時候我們也很自然地使用
Copy!const res = [1, 2, 3, 4].filter((num) => num > 1).map((num) => ({ key: num })) console.log('res', res) // [({ key: 2 }, { key: 3 }, { key: 4 })]
或是在用 Promise 的時候
Copy!fetch(url) .then(res => res.json()) .then(result => {......}) .then(result => {.....})
或者一些第三方的 Library 中也實現了 chain function ,如 underscore.js
Copy!_.chain = function (obj) { var instance = _(obj) instance._chain = true return instance } // Helper function to continue chaining intermediate results. var chainResult = function (instance, obj) { // 如果實例中有_chain 為 true 這個屬性,則返回實例 支持鏈式調用的實例對像 { _chain: true, this._wrapped: [3, 2, 1] },否則直接返回這個對像[3, 2, 1]。 return instance._chain ? _(obj).chain() : obj }
在實際業務上我們很少或者根本沒什麼在使用,甚至不了解如何寫鏈式調用的函式,
其實核心就是return this,返回 Instance 本身
#鏈式調用的優缺點
優點
-
能一直 chain 下去,一直用一直爽
-
程式變得比較簡潔好讀,看命名就知道(抽象化命名很重要)
-
可以將單個 function 的職責切分清楚,用 chain 就能輕易達到復用
缺點
- 因為是鏈式的所以如果 Chain 好幾個 function 只看輸出的話會不好排查問題
#回到題目
來分析一下各個 function 如何實現
-
where - 就是 filter 功能
-
sortBy - 用 key 當排序的的索引
-
groupBy - 對數組中的物件進行分組,原生也有group可以使用,但是現在支援度非常的差,目前只有 Safari 16.4+ 才有支援
-
execute - 返回結果
function 的寫法
Copy!const query = (list) => { if (!Array.isArray(list)) throw new Error('The parameter passed is not an Array') return { _list: list, where(callback) { this._list = this._list.filter(callback) return this }, sortBy(key) { if (!this._list[0].hasOwnProperty(key)) throw new Error("can't find the key in sortBy function") this._list.sort((a, b) => a[key] - b[key]) return this }, groupBy(key) { if (!this._list[0].hasOwnProperty(key)) throw new Error("Can't find the key in groupBy") this._list = this._list.reduce((acc, curr) => { const groupKey = curr[key] acc[groupKey] = acc[groupKey] ?? [] acc[groupKey].push(curr) return acc }, {}) return this }, execute() { return this._list }, } } const data = [ { id: 4, name: 'Max', age: 31 }, { id: 2, name: 'Bob', age: 11 }, { id: 1, name: 'Joy', age: 20 }, { id: 5, name: 'Alex', age: 6 }, { id: 3, name: 'Bob', age: 24 }, { id: 7, name: 'John', age: 56 }, { id: 8, name: 'Bob', age: 29 }, ] const result = query(data) .where((item) => item.age > 18) .sortBy('id') .groupBy('name') .execute() console.log(result) // { // Joy: [ { id: 1, name: 'Joy', age: 20 } ], // Bob: [ { id: 3, name: 'Bob', age: 24 }, { id: 8, name: 'Bob', age: 29 } ], // Max: [ { id: 4, name: 'Max', age: 31 } ], // John: [ { id: 7, name: 'John', age: 56 } ] // }
class 的寫法
Copy!class Chain { constructor(list) { if (!Array.isArray(list)) throw new Error('The parameter passed is not an Array') this._list = list } query(list) { this._list = list return this } where(callback) { this._list = this._list.filter(callback) return this } sortBy(key) { if (!this._list[0].hasOwnProperty(key)) throw new Error("can't find the key in sortBy function") this._list.sort((a, b) => a[key] - b[key]) return this } groupBy(key) { if (!this._list[0].hasOwnProperty(key)) throw new Error("Can't find the key in groupBy") this._list = this._list.reduce((acc, curr) => { const groupKey = curr[key] acc[groupKey] = acc[groupKey] ?? [] acc[groupKey].push(curr) return acc }, {}) return this } execute() { return this._list } } const data = [ { id: 4, name: 'Max', age: 31 }, { id: 2, name: 'Bob', age: 11 }, { id: 1, name: 'Joy', age: 20 }, { id: 5, name: 'Alex', age: 6 }, { id: 3, name: 'Bob', age: 24 }, { id: 7, name: 'John', age: 56 }, { id: 8, name: 'Bob', age: 29 }, ] const query = new Chain(data) const result = query .where((item) => item.age > 18) .sortBy('id') .groupBy('name') .execute() console.log(result) // { // Joy: [ { id: 1, name: 'Joy', age: 20 } ], // Bob: [ { id: 3, name: 'Bob', age: 24 }, { id: 8, name: 'Bob', age: 29 } ], // Max: [ { id: 4, name: 'Max', age: 31 } ], // John: [ { id: 7, name: 'John', age: 56 } ] // }
#結語
其實整個概念就像 FP 常用的 Pipe 一樣,藉由串接單一職責的 function 減少可能的 side effect。
而在理解這次的主題之前,我發現原來我本來完全不會寫 chain function ,也應該說從來沒有寫過,藉由這次的面試考題順便整理一下研究的思路。