最近在面试时遇到这样一道笔试题,觉得很有意思,拿出来分享一下。
看题目:
实现一个 LazyMan,按照以下方式调用时,得到相关输出:
LazyMan("Hank")
// Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")
// Hi! This is Hank!
// *等待 10 秒..
// Wake up after 10
// Eat dinner~
LazyMan("Hank").eat("dinner").eat("supper")
// Hi This is Hank!
// Eat dinner~
// Eat supper~
LazyMan("Hank").sleepFirst(5).eat("supper")
// *等待 5 秒
// Wake up after 5
// Hi This is Hank!
// Eat supper
刚拿到题目的时候还是比较懵的,无从下手,不过为了offer还是很快的静下心来去分析题目。
- 所有的调用都是基于 LazyMan 的链式调用,这样可以把 LazyMan 构造成一个构造函数,返回的实例具有一些方法;
- 实例方法都有链式调用的特征;
- eat 方法较为简单,直接输出参数即可
- sleep 方法会暂停链式调用,等待相应时间后继续,考虑使用 setTimeout ;
- sleepFirst 方法比较特殊,与 sleep 类似,但是优先级最高,无论在何处调用,都会从头开始暂时整个链式调用过程;
- 考虑到 sleep 和 sleepFirst 方法的特殊性,我决定使用任务队列来处理“暂停”操作。
接下来我尝试按照上面的思路去用代码实现。
class LazyManGenerator {
constructor(name) {
this.taskArray = []
const task = () => {
console.log(`Hi! This is ${name}`)
this.next()
}
this.taskArray.push(task);
// 防止立即执行,要去等待数组全部完成后再执行任务队列中的任务。
setTimeout(() => { this.next(); }, 0)
}
next() {
// 从头取出任务执行
const task = this.taskArray.shift()
task && task()
}
sleep(time) {
this.sleepTask(time, false)
// 支持链式调用
return this
}
sleepFirst(time) {
this.sleepTask(time, true)
return this
}
sleepTask(time, prior) {
const task = () => {
setTimeout(() => {
console.log(`Wake up after ${time}`)
this.next()
}, time * 1000)
}
// 如果是sleepFirst方法,从头部加入当前task,否则正常从当前尾部添加
if (prior) {
this.taskArray.unshift(task);
} else {
this.taskArray.push(task);
}
return this;
}
eat(name) {
const task = () => {
console.log(`Eat ${name}`)
this.next()
}
this.taskArray.push(task)
return this
}
}
function LazyMan(name) {
// 返回LazyManGenerator构造函数实例
return new LazyManGenerator(name)
}
代码其实并不难,就是上面分析的实现。中心思想就是方法不去立即执行,而是去按照特定的顺序添加到 taskArray 数组中,然后待链式调用完成后(任务全部添加到taskArray中),再去依次执行 taskArray 数组中的任务。
实现到这已经算是及格了,不过面试官还是继续追问是否有更好的实现,可能是由于 next 的原因,让我想到了 koa 的中间件的实现,简单的说了下思路,其实就是一个简单的中间件流程控制。有能力的童鞋可以自己尝试去实现以下。