我们都知道,JavaScript是单线程、基于异步的编程语言,执行代码时是非阻塞的。如果执行的是同步的代码,则按照顺序执行完毕;如果是异步的代码,则按照异步调用执行,并不会阻塞在某一个异步函数中。最近遇到了这样一个面试:LazyMan(懒汉):
const lazyMan = new LazyMan('张三')lazyMan.eat('dinner').sleep(10).eat('super').sleepFirst(5).sleep(5);
其实主要就是要实现可阻塞代码执行的sleep函数,当然还有链式调用。接下来就看看怎么具体实现吧
思路
不管是写什么代码,在此之前,都应该有一个大致的设计思路。回到面试题,主要由以下特点:
- 需要支持链式调用,那么
eat
、sleep
等方法内需要返回当前对象this - 需要支持sleep功能,那么函数就不能在直接调用时就触发。js是没有内置的sleep方法的,需要我们自己实现
- 初始化一个列表,将需要的函数都添加进去,然后依次执行
- 由于是链式调用,因此我们需要实现一个
next
的功能,在列表中的每一个函数都执行后能自动调用下一个任务来执行
思路看起来也不是很复杂,接下来就动手试试。
开始
简单版
先实现一个简单的LazyMan,为了便于理解,我们在最后一个链式调用中增加了done()
方法,用来打印当前的任务列表:
class LazyMan {
tasks = []; // 任务列表
name = ''; // 懒汉
constructor(name) {
this.name = name;
// 采用异步,确保所有的链式调用函数都添加到列表中后,自动执行第一个任务
setTimeout(() => {
this.next();
}, 0)
}
// 执行下一个任务,每次执行完都会从任务列表中删除
next() {
const task = this.tasks.shift(); // 从头部删除,会直接影响原数组
task && task();
}
// 阻塞,使得程序延后time执行
sleep(time) {
// 定义一个任务函数
const task = () => {
console.log(`sleep ${time}s 开始`)
setTimeout(() => {
console.log(`sleep ${time}s 结束`)
this.next(); // 执行完当前函数后,继续执行下一个任务,直到任务执行完毕
}, time * 1000); // 使用定时器来模拟实现阻塞功能
}
// 添加到任务列表中
this.tasks.push(task);
// 返回当前对象,保持链式调用
return this;
}
eat(food) {
// 定义一个任务函数
const task = () => {
console.log(`eat ${food}`)
this.next(); // 执行完当前函数后,继续执行下一个任务,直到任务执行完毕
}
// 添加到任务列表中
this.tasks.push(task);
// 返回当前对象,保持链式调用
return this;
}
done() {
const task = () => {
console.log(`当前待处理的任务:`)
console.log(`${this.tasks}`)
this.next();
}
this.tasks.unshift(task);
return this;
}
}
const lazyMan = new LazyMan('张三');
lazyMan.eat('dinner').sleep(10).eat('super').sleep(5).done();
实现效果:
任务列表中其实就是一个个可执行函数
实现效果基本上和LazyMan要求基本一致了。
看面试题中,除了sleep外,还有一个sleepFirst,这个其实就是说先执行sleep操作,即使这个函数调用并没有放在第一个。既然都已经实现了sleep了,要实现sleepFirst也并不复杂。
升级版
我们保持其他不变,仅来改动一下sleep函数:
class LazyMan {
...
_sleep(time, isFirst) {
const task = () => {
console.log(`sleep ${time}s 开始`)
setTimeout(() => {
console.log(`sleep ${time}s 结束`)
this.next(); // 执行完当前函数后,继续执行下一个任务,直到任务执行完毕
}, time * 1000); // 使用定时器来模拟实现阻塞功能
}
if (isFirst) {
this.tasks.unshift(task)
} else {
this.tasks.push(task)
}
}
sleep(time) {
this._sleep(time, false)
return this
}
sleepFirst(time) {
this._sleep(time, true)
return this
}
}
lazyMan.eat('dinner').sleep(10).eat('super').sleepFirst(5).sleep(5);
运行结果:
懒汉会先执行sleepFirst
总结
LazyMan就简简单单实现了,如果不了解这个可能在一开始会有点蒙,其实看下来并不复杂。总结一下核心知识点:
- 成员方法中返回this,可实现链式调用
- 将调用的函数注册到列表中
- 按照先进先出的原则,实现next方法,保证链式调用可以链式执行
- 使用setTimeout的延后执行,来实现sleep