Josh Hsu

如何鏈式調用函式?

> 最近看到這個面試題,發現我原來不會

如何鏈式調用函式?

提供了一個數組結構的 data,要求實現一個 query 方法,返回一個新的數組,query 方法內部有 過濾、排序、分組 等操作,並且支持鏈式調用,調用最終的 execute 方法返回結果:

const result = query(list) .where((item) => item.age > 18) .sortBy('id') .groupBy('name') .execute() console.log(result)

我們可以在 jQuery 中常看到這樣的用法

jQuery

$('p1').css('color', 'red').slideUp(2000).slideDown(2000)

在操作 Array 的時候我們也很自然地使用

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 的時候

fetch(url) .then(res => res.json()) .then(result => {......}) .then(result => {.....})

或者一些第三方的 Library 中也實現了 chain function ,如 underscore.js

_.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 本身

#鏈式調用的優缺點

優點

缺點

#回到題目

來分析一下各個 function 如何實現

function 的寫法

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 的寫法

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 ,也應該說從來沒有寫過,藉由這次的面試考題順便整理一下研究的思路。

#Ref

大专前端,三轮面试,终与阿里无缘

学习 underscore 源码整体架构,打造属于自己的函数式编程类库

See all posts