不知道大家对于最近一堆面经是怎么样的看法,我不喜欢看的是一堆相似题目比如作用域,闭包或者原型链的,这都已经烂大街了的东西,说再多也没什么特别多的干货。但是对一些新颖的题目还是很有好感的。
最近看见了几个挺喜欢的题目来和大家分享一下,我觉得我写的答案没人家写的好,所以就不献丑了。第一个题是在我挺佩服的前端@serialcoder的博客中看见的,话不多说,看题:

@serialcoder给出的答案是:
const limitConcurrency = (fn, max) => {
const pendingTasks = new Set();
return async function(...args) {
while (pendingTasks.size >= max) {
await Promise.race(pendingTasks);
}
const promise = fn.apply(this, args);
const res = promise.catch(() => {});
pendingTasks.add(res);
await res;
pendingTasks.delete(res);
return promise;
};
};
async function sendRequest(urls, max, callback) {
const limitFetch = limitConcurrency(fetch, max);
await Promise.all(urls.map(limitFetch));
callback();
}作者将最大并发数的限制放在了limitConcurrency中的async 函数中,将所有请求结束的回调函数执行放在了sendRequest函数中,挺喜欢作者用函数柯里化的思想,加上用map函数自动的将urls中每一项的值传进limitFetch中,最后用Promise.all来确定请求集体结束。
在看这段代码的时候,我起初在纳闷为什么要赋值这一段。
const res = promise.catch(() => {});看到了Promise.all醒悟到了:万一fetch请求中发生错误,Promise.all就直接结束了,那就意味着回调函数会比预想的早触发。
还有一位是幻☆精灵写的思想和我很相近,但是实现起来比我简洁干脆。都是利用循环加上条件,直接用fetch函数来进行请求,因为fetch是源自promise,也就是说循环的主线程会比promise的微任务先执行,执行完了max数目的循环,才开始进行fetch请求。但是用了finally这个方法让人眼前一亮,然后在返回的结果中相对应的进行操作,这段代码就比较通俗易懂:
function sendRequest(urls, max, callback) {
const len = urls.length;
let idx = 0;
let counter = 0;
function _request() {
while (idx < len && max > 0) {
fetch(urls[idx++]).finally(() => {
max++;
counter++;
if (counter === len) {
return callback();
} else {
_request();
}
});
}
}
_request();
}第二个题目是来自尹光耀面经中的题:
function machine() {
}
machine('ygy').execute()
// start ygy
machine('ygy').do('eat').execute();
// start ygy
// ygy eat
machine('ygy').wait(5).do('eat').execute();
// start ygy
// wait 5s(这里等待了5s)
// ygy eat
machine('ygy').waitFirst(5).do('eat').execute();
// wait 5s
// start ygy
// ygy eat这题目粗略想想大概能知道是用数组将一个个任务塞进去,然后任务从前往后执行,类似于队列。但是不同之处在于wait和waitFirst两个区别,wait是按照正常的push进去,所以他执行在start ygy之前,waitFirst塞入时间比start ygy晚,但却执行于start yay之前,这就想到了unshift方法。等待时间可以用setTimeout来控制,一个接着一个执行是不是很容易想到promise的链式用法,再扩展一步就是加以运用 async和await来进行。
下面给出原作者的答案:
function machine(name) {
return new Action(name)
}
const defer = (time, callback) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(callback())
}, time * 1000)
})
}
class QueueItem {
constructor(defer, callback) {
this.defer = defer;
this.callback = callback;
}
}
class Action {
queue = []
constructor(name) {
this.name = name;
this.queue.push(new QueueItem(0, () => console.log(`start ${this.name}`)))
}
do(eat) {
this.queue.push(new QueueItem(0, () => console.log(`${this.name} ${eat}`)))
return this;
}
wait(time) {
this.queue.push(new QueueItem(time, () => console.log(`wait ${time}s`)))
return this;
}
waitFirst(time) {
this.queue.unshift(new QueueItem(time, () => console.log(`wait ${time}s`)))
return this;
}
async execute() {
while(this.queue.length > 0) {
const curItem = this.queue.shift();
if (!curItem.defer) {
curItem.callback();
continue;
}
await defer(curItem.defer, curItem.callback)
}
}
}
在评论中发现serialcoder也给出了不同的答案,思想是差不多的,就是又包了一层异步,他这asyncPipe函数和接下讲的题目解法很类似于是就拿出来先看看了:
const defer = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));
const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);
function Machine(name) {
const tasks = [];
let needToWaitFirst = false;
function _do(str) {
const task = () => {
console.log(`${name} ${str}`);
};
tasks.push(task);
return this;
}
function wait(sec) {
const task = async () => {
console.log(`wait ${sec}s`);
await defer(sec);
};
tasks.push(task);
return this;
}
function waitFirst(sec) {
needToWaitFirst = true;
const task = async () => {
console.log(`wait ${sec}s`);
await defer(sec);
};
tasks.unshift(task);
return this;
}
function execute() {
const task = () => {
console.log(`start ${name}`);
};
if (needToWaitFirst) {
tasks.splice(1, 0, task);
} else {
tasks.unshift(task);
}
asyncPipe(...tasks)();
}
return {
do: _do,
wait,
waitFirst,
execute
};
} 第三个题目来自蒙面大虾的面经,实现一个compose。话不多话直接看答案,答案参考更详细的解析:
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}很简单明了的答案,将函数数组进行遍历,当reduce函数没传入默认参数a时,a会被认为是数组的第一项,b则是数组第二项。举例来说,
funcs = [fn1, fn2, fn3];假设传入的...args为[1,2],那在第一次reduce的时候,a为fn1,b为fn2。因为此时函数并没有给与a默认值,所以第一次reduce相当于 返回 (...args) => fn1( fn2(...args))。
第二次reduce的时候,这时候的a就为 (...args) => fn1( fn2(...args)),它是一个函数。而b就是fn3,此时的a函数中的...args === fn3([1,2])。
类似的pipe就很简单可以得出了,执行顺序是从fn1 -> fn2 -> fn3,与compose相反。
总结语:
感谢三位提供的学习材料,学习始终是自己的事情,也许现在懂得不会很多,但是一步步积累去探索终究还是会有收获的。