stephen's blog

[object Object] object(s)
 

LazyMan的几种解法

描述

LazyMan是一道很经典的前端面试题,题目非常有意思,完整描述是:

实现一个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

以此类推。

这道题主要考察的是JS里面的流程控制,如何让任务顺序实现以及如何正确链式调用是解决这道题的关键,网上大部分解法是借鉴express中间件原理,每一个行为执行完成后调用next()方法,然后使用setTimeout来执行下一个任务,如果你对js中的EventLoop、任务队列不了解的话,可以看我之前的这篇文章Understanding Event Loop in Javascript。下面主要讲解通过next方法、promise方法两种方法来实现。

Next()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class LazyManClass {
constructor(name) {
this.tasks = [];
let fn = () => {
console.log('Hi, this is ' + name)
this.next()
}
this.tasks.push(fn)
setTimeout(this.next.bind(this), 0)
}
next() {
let fn = this.tasks.shift()
fn && fn.call(this)
}
sleep(time) {
let fn = () => {
setTimeout(() => {
console.log('Wake up after ' + time)
this.next()
}, time * 1000)
}
this.tasks.push(fn)
return this
}
sleepFirst(time) {
let fn = () => {
setTimeout(() => {
console.log('Wake up after ' + time)
this.next()
}, time * 1000)
}
this.tasks.unshift(fn)
return this
}
eat(name) {
let fn = () => {
console.log('Eat ' + name)
this.next()
}
this.tasks.push(fn)
return this
}
}
const LazyMan = (name) => new LazyManClass(name)
LazyMan('stephen').sleep(5).eat('lunch')
// "Hi, this is stephen"
// 等待5s..
// "Wake up after 1"
// "Eat lunch"

点击查看代码
其中setTimeout模拟事件循环,使用this.tasks当作任务处理队列,每个事件内部有一个next方法当作事件调度函数。注意每个事件需要返回this来支持链式调用,下面看看promise的实现方法。

Promise

这里我们可以使用Promise来实现,关于promise可以看这张图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class LazyManClass {
constructor(name) {
this.promiseTasks = []
this.sequence = Promise.resolve()
let promiseObj = () => {
return new Promise((resolve, reject) => {
console.log('Hi, this is ' + name)
resolve()
})
}
this.promiseTasks.push(promiseObj)
setTimeout(() => {
this.promiseTasks.forEach((promiseObj) => {
let thenFunc = () => promiseObj()
this.sequence = this.sequence.then(thenFunc)
})
}, 0)
}
sleep(time) {
let promiseObj = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Wake up after ' + time)
resolve()
}, time * 1000)
})
}
this.promiseTasks.push(promiseObj)
return this
}
sleepFirst(time) {
let promiseObj = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Wake up after ' + time)
resolve()
}, time * 1000)
})
}
this.promiseTasks.unshift(promiseObj)
return this
}
eat(name) {
let promiseObj = () => {
return new Promise((resolve, reject) => {
console.log('Eat ' + name)
resolve()
})
}
this.promiseTasks.push(promiseObj)
return this
}
}
const LazyMan = (name) => new LazyManClass(name)
LazyMan('stephen').eat('lunch').sleep(2).eat('dinner')

点击查看代码
可以说使用promise控制流程非常方便,不过注意的是then接受的函数必须是已经确定状态的(resovle或者reject)。

(完)