趣味解读,实现一个 LazyMan,可以链式调用,它有sleep(time)
方法,可以等待指定时间后,再继续执行后续的调用。
LazyMan 的分析
LazyMan('Jack').eat('lunch').sleepFirst(2).sleep(20).eat('dinner').sleep(40).eat('breakfast');
// (等待 2s) I'm sleep 2s
// Hi, I'm Jack
// I'm eating lunch
// (等待 20s) I'm sleep 20s
// I'm eating dinner
// (等待 40s) I'm sleep 40s
// I'm eating breakfast
看一下题目要求,可以很快知道,LazyMan 是一个对象,有 eat、sleep、sleepFirst 方法。其中链式调用,也就是在其方法最后,将对象 return 回来,于是就可以继续访问执行其方法了。
那么,这个 sleep 方法内部应该就是 setTimeout 的来实现。
可是,那个 sleepFirst 明显是要搞事情的,它不仅会延迟执行,还能跑到前面执行???
怎么办?
让我们回顾一下这个链式调用,离远点看,你看看它像不像一串糖葫芦,哦不,像不像是茶颜悦色前面苦苦排队的人。诶,排队,诶,队列?要悟了...
bingo,队列是什么?队列是一个先进先出(FIFO)的数据结构。
队列闪亮登场
OK,队列这种特性,刚好就符合我们对 LazyMan 的链式调用的编写。排在前面的方法,先执行;对于 sleepFirst 这种搞事情的方法,就好像是有(luan)特(cha)权(dui)的人,得特殊处理。
队列在代码中,可以用链表或者数组来实现,这里我们用数组来简单使用。
// 创建一个任务队列,用来存放链式调用的方法
this.taskQueue = [];
// next 方法呢,就是在任务队列中,取一个任务来执行
next() {
let fn = this.taskQueue.shift();
fn && fn();
}
// 声明 LazyMan 的方法,如 eat,在其函数体内,创建一个函数 fn,将要做的事情写到里面,随后将 fn 添加到任务队列中。
// 有两个一定要注意的点:1. 被添加到任务队列的函数中最后要执行 next(),也就是说,它执行完了,不能结束,还要去执行后面的任务。2. 返回 this,也就是 LazyMan 自身,不然怎么调用后面任务呢。
eat(food) {
let fn = () => {
console.log(`I'm eating ${food}`);
this.next(); // 执行完代码后,将控制权给后续任务
};
this.taskQueue.push(fn); // 添加到 taskQueue 队列中
// 返回 this,让函数可以继续链式调用。
return this;
};
至于 sleep 方法,和上述的差不多,就是在 setTimeout 中再调用 next()。而 sleepFirst 这种特殊的,在将其往任务队列tashQueue
中添加时,不再是 push 到最后,而是 unshift 到最前面.
完整代码
function LazyMan(name) {
class _LazyMan {
constructor(name) {
this.name = name;
this.taskQueue = [];
this.hi();
// 这里使用 setTimeout 来异步执行 next 函数,此时链式调用的各个方法已经在 taskQueue 队列中了。
setTimeout(() => {
console.log(this.taskQueue);
this.next();
}, 0);
}
hi() {
const fn = () => {
console.log(`Hi, I'm ${name}`);
this.next();
};
this.taskQueue.push(fn);
return this;
}
next() {
let fn = this.taskQueue.shift();
fn && fn();
}
eat(food) {
let fn = () => {
console.log(`I'm eating ${food}`);
this.next();
};
this.taskQueue.push(fn); // 添加到 taskQueue 队列中
// 返回 this,让函数可以继续链式调用。
return this;
}
sleep(time) {
return this.lazy(time);
}
sleepFirst(time) {
return this.lazy(time, true);
}
lazy(time, isFirst = false) {
const text = isFirst ? 'sleepFist' : 'sleep';
const fn = () => {
console.group(`I'm ${text} ...`);
setTimeout(() => {
console.group(`I'm ${text} ${time}s`);
console.groupEnd();
this.next();
}, time * 1000);
};
if (isFirst) {
this.taskQueue.unshift(fn);
} else {
this.taskQueue.push(fn);
}
return this;
}
}
return new _LazyMan(name);
}
LazyMan('Jack').eat('lunch').sleepFirst(2).sleep(4).eat('dinner').sleep(6).eat('breakfast');
结语
这是一道常见的面试题,有人可能选择背就完事了,不过我觉得吧,稍微想一想,其实也就是考察队列这种的数据结构,知道了这些后,哪怕题目再稍微变化一些,我们也是可以很快给出答案的吧。
死记硬背,终究不如深入理解的。