题目要求
实现一个类 LazyMan,要求:
new LazyMan('煎饼狗子').drink('水') 输出
- 我是煎饼狗子
喝喝喝 水new LazyMan('煎饼狗子').drink('水').sleep(3000).eat('憨包') 输出
- 我是煎饼狗子
喝喝喝 水
// ......在这里等了3000ms
睡了3000ms,起床
吃吃吃 憨包new LazyMan('煎饼狗子').drink('可乐').sleepFirst(1000) 输出
- // ......在这里等了1000ms
睡了1000ms,起床
我是煎饼狗子
喝喝喝 可乐
分析
这是一道很经典的面试题,这题不能用传统的想法来做,因为在使用new LazyMan('xxx').drink('x').sleep(3000).eat('x')
这样的点语法的时候,是同步任务,不可能等待睡觉几秒后才执行下一个。
可以用这样的思路,把 new LazyMan('xxx').drink('x').sleep(3000).eat('x')
看做注册任务
, 放入一个数组中,等注册完了,再统一去执行,这样才方便我们控制
那么我们要怎么知道用户是否注册完任务了,再去统一执行?在这里我们可以利用事件队列 的特性,由于注册懒汉要做的事
是同步任务(宏任务),我们只要用微任务即可,它自然就会等待所有宏任务执行完,自动去执行我们想要的操作。
代码实现
写法一:
ts
type Task = (...params: any) => void | Promise<void>
class LazyMan {
/**任务队列 */
private queue: Task[] = []
constructor(name: string) {
this.add(() => console.log('我是 ' + name))
setTimeout(() => { //利用宏任务的特性,确保同步任务执行完之后才来到这 (这里的同步任务指的是 lazyMan.drink('xxx').sleep(3000).eat('xx') 这样的事件注册)
this.next()
});
}
eat(food: string) {
this.add(() => console.log('吃吃吃 ' + food))
return this
}
sleep(time: number) {
this.add(async () => {
await new Promise<void>(r => setTimeout(() => {
r()
console.log(`睡了${time}ms,起床`);
}, time))
})
return this
}
drink(thing: string) {
this.add(() => console.log('喝喝喝 ' + thing))
return this
}
sleepFirst(time: number) {
this.addFirst(async () => {
await new Promise<void>(r => setTimeout(() => {
r()
console.log(`睡了${time}ms,起床`);
}, time))
})
return this
}
/**添加任务到末尾 */
private add(task: Task) {
this.queue.push(async () => {
await task() //等待当前任务执行完后,接着执行下一个任务
this.next()
})
}
/**添加任务到开头 */
private addFirst(task: Task) {
this.queue.unshift(async () => {
await task() //等待当前任务执行完后,接着执行下一个任务
this.next()
})
}
/**执行下一个任务 */
private next() {
this.queue.shift()?.()
}
}
第二种写法: (区别在于添加任务的时候,不用手动调用next了,而是在runTask函数里去递归调用)
ts
class LazyMan2 {
private queue: Task[] = []
constructor(name: string) {
this.addTask(() => console.log('名字是: ' + name))
setTimeout(() => {
this.runTask()
}, 0);
}
drink(name: string) {
this.addTask(() => console.log('喝: ' + name))
return this
}
eat(name: string) {
this.addTask(() => console.log('吃: ' + name))
return this
}
sleep(time: number) {
this.addTask(() => {
return new Promise(async (resolve, reject) => {
setTimeout(() => {
console.log(`睡了 ${time} 秒`);
resolve()
}, time * 1000)
})
})
return this
}
private addTask(task: Task) {
this.queue.push(task)
}
private async runTask() {
const task = this.queue.shift()
if (task) {
await task()
this.runTask()
}
}
}