实现链式调用的 find 函数
实现链式调用的 find 函数

实现链式调用的 find 函数

描述
面试题复盘
标签
JavaScript
日期
Apr 4, 2026
题目描述:
var data = [{ price: 13, name: 'A-apple' }, { price: 3, name: 'B-apple' }, { price: 16, name: 'A-orange' }, { price: 10, name: 'B-orange' }, { price: 20, name: null }]; var find = function (origin) { // your code are here... // 查找 data 中,符合条件的数据,并进行排序 }; var result = find(data).where({ 'name': /^A/ }).orderBy('price', "desc"); console.log(result); // [ { price: 16, name: 'A-orange' }, { price: 13, name: 'A-apple' } ]
这个题目设计到了 JS 链式调用函数的设计。

链式调用函数

示例1

先来看一个简单的场景:
function arrange(name) { // your code here } arrange("William").execute(); // > William is notified arrange("William").do('commit').execute(); // > William is notified // > Start to commit arrange("William").wait(5).do('commit').execute(); // > William is norified // 等待 5s // > Start to commit arrange("William").waitFirst(5).do('push').execute(); // 等待 5s // > William is notified // > Start to push
这个题目需要你设计一个 arrange 函数,支持链式调用,并且在最后调用 execute 方法时执行之前链式调用的任务。
我们可以把每次链式调用都想成是一个任务,将所有链式调用产生的任务都添加到队列中,在调用 execute 方法时,从队列中依次取出任务执行。
每个任务的执行都会返回一个带有所有链式调用方法的 object,以便下一次进行链式调用。
function arrange(name) { const tasks = []; tasks.push(() => { console.log(`${name} is notified`); }) function doSomething(action) { tasks.push(() => { console.log(`Start to ${action}`) }) return this; } function wait(sec) { tasks.push(() => new Promise((resolve) => { setTimeout(resolve, sec * 1000); })) return this; } async function execute() { for (const t of tasks) { await t(); } return this; } function waitFirst(sec) { tasks.unshift(() => new Promise((resolve) => { setTimeout(resolve, sec * 1000); })) return this; } return { do: doSomething, wait, execute, waitFirst } }
  • waitFirst 需要在所有任务之前执行,所以使用 unshift 方法在队头插入一个新的任务。
  • 所有任务返回 this 即可,以为函数都是通过对象的方法进行调用而且是非箭头函数,在调用时 this 绑定到调用其的对象上,所以直接返回 this 即可。
  • execute 执行的是异步任务,结合 async await 确保任务执行顺序。

示例2

上述事例通过显示调用 execute 方法来执行调用链收集的任务,但是如果题目要求在所有链式调用后自动执行呢?
事例代码:
HardMan("jack") 输出: I am jack HardMan("jack").rest(10).learn("computer") 输出 I am jack //等待10秒 Start learning after 10 seconds Learning computer HardMan("jack").restFirst(5).learn("chinese") 输出 //等待5秒 Start learning after 5 seconds I am jack Learning chinese
这个问题中,restrestFirst 的思路和上一题的 waitwaitFirst 是一样的,区别就在于,HardMan 在链式调用结束后,应立即执行链式调用任务。
这里可以通过 Promise + JS 事件循环来实现。我们把执行作为一个任务通过 Promise 添加到微队列,当主进程执行完所有的同步链式调用时(任务收集),从微队列取出执行任务。
function HardMan(name) { const tasks = []; tasks.push(() => { console.log(`I am ${name}`); }) function reset(sec) { tasks.push(() => new Promise((resolve) => setTimeout(resolve, sec * 1000))); return this; } function resetFirst(sec) { tasks.unshift(() => new Promise((resolve) => setTimeout(resolve, sec * 1000))) return this; } function learn(name) { tasks.push(() => { console.log(`Learning ${name}`); }) return this; } Promise.resolve().then(async () => { for (const t of tasks) { await t(); } }) return { reset, resetFirst, learn } }
其余逻辑与上一题类似。

find 函数实现

通过上述两个示例,我们可以发现,链式调用函数的本质是,每次进行链式调用时,都进行任务收集,并且返回一个带有链式调用方法的对象,在链式调用的末尾(或显示调用执行函数时)去执行之前收集的任务。
但是题目中的 find 方法在链式调用的结尾返回的是一个数组,可就是说,通过以上两种办法,都不能直接在最后得到题目要求。如果按照上述两种思路去做,那么你最后 log 出来的结果大概率是:
{ where: [Function: where], orderBy: [Function: orderBy] }
[ { price: 16, name: 'A-orange' }, { price: 13, name: 'A-apple' }, where: [Function (anonymous)], orderBy: [Function (anonymous)] ]
也就是说,我们不能直接返回链式调用的方法组成的对象,也不能直接对 Array.prototype 添加链式调用方法。这种情况可以使用 Proxy 去实现。
通过把原始 data 封装成一个代理对象数组(proxy data),通过 get 访问器去操作 proxy data。
function find(data) { function createProxy(arr) { return new Proxy(arr, { get(target, prop) { if (prop === 'where') { return function (condition) { const key = Object.keys(condition)[0]; const value = condition[key]; const filterdArr = target.filter(item => item[key] && item[key].toString().match(value)); return createProxy(filterdArr); } } if (prop === 'orderBy') { return function (key, dir) { const sorted = [...target].sort((a, b) => { if (dir === 'desc') return b[key] - a[key]; else return a[key] - b[key]; }) return createProxy(sorted) } } return Reflect.get(target, prop); } }) } return createProxy(data); }
通过上述使用 proxy 的方式,就能够在最终 log 时打印经过 find 方法处理后的一个纯净的数组。 但是这样实现还有一些潜在的问题:

1. 抽象性

代码中对于 whereorderBy 的实现逻辑,只是针对题目中给定输入的特殊性去考虑的,如果 where 条件支持多个属性,或者 orderBy 的条件支持回调函数,那么上述代码可以封装成更为通用的部分。
其实 where 的本质就是进行过滤,而 orderBy 的本质是排序,所以可以针对这两个分支进行优化:
get(_, prop) { if (prop === "where") { return (condition) => { const entries = Object.entries(condition); const result = arr.filter(item => entries.every(([key, value]) => { const v = item[key]; if (value instanceof RegExp) return value.test(v); if (typeof value === "function") return value(v); return v === value; }) ); return create(result); } } if (prop === "orderBy") { return (key, dir = "asc") => { return [...arr].sort((a, b) => { if (a[key] === b[key]) return 0; if (dir === "desc") return a[key] < b[key] ? 1 : -1; return a[key] > b[key] ? 1 : -1; }); } } }

2. 性能

因为每次调用链式函数,都会返回一个新的 proxy 对象,如果调用链很长,每次都需要创建新的 Proxy 对象,会带来较大的性能问题,但是根据题目描述,也无法使用其他的方法,因为每次链式调用之后,我都需要返回处理后的数组,每次调用数组的状态都会发生变化。
但是如果题目中最后通过调用某个方法(如 execute)或使用某个属性(.value)去进行最终的获取,那么就可以使用“示例1”的方法去做,链式调用的过程中,不改变数组的状态,只是收集需要执行的任务,在调用 execute 方法时,顺序执行收集的任务,这样就可以只创建一次 proxy 来优化性能问题。