LazyMan问题

131 阅读6分钟
分享一道面试题:


实现一个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流程控制
JS事件循环机制Promise
。。。

方法1:
问题的关键是如何实现任务的顺序执行。
参考node.js,在Express有一个类似的东西叫中间件,这个中间件和我们这里的吃饭、睡觉等任务很类似。
每一个中间件执行完成后会调用next()函数,这个函数用来调用下一个中间件。
对于这个问题,我们也可以利用相似的思路来解决。
首先创建一个任务队列,然后利用next()函数来控制任务的顺序执行:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function LazyMan(name) {
// 创建一个任务队列
let taskList = [];
// 创建lazyman对象
let _lazyMan = {
// 执行下一步 的方法
next() {
// 抽取任务队列中的第一个任务
let task = taskList.shift();
// 如果存在该任务,就调用该任务
task && task()
},
// sayHi(打招呼) 方法
sayHi(name) {
// 在任务队列 最后面 追加任务
taskList.push(() => {
// 打招呼
console.log(`Hi! This is ${name}!`);
// 该任务完成之后,调用下一步的方法
this.next()
})
// return this 为了实现链式编程
return this
},
// sleep(睡觉) 方法
sleep(time) {
// 在任务队列 最后面 追加任务
taskList.push(() => {
// 开启定时器
setTimeout(() => {
// 输入 多少秒之后醒来
console.log(`Wake up after ${time}`);
// 该任务完成之后,调用下一步的方法
this.next()
}, time * 1000);
})
// return this 为了实现链式编程
return this
},
eat(food) {
taskList.push(() => {
console.log(`Eat ${food}~`);
this.next()
})
return this
},
// sleepFirst(先睡) 方法
sleepFirst(time) {
// 在任务队列 最前面 添加任务
taskList.unshift(() => {
// 开启定时器
setTimeout(() => {
console.log(`Wake up after ${time}`);
// 该任务完成之后,调用下一步的方法
this.next()
}, time * 1000);
})
// return this 为了实现链式编程
return this
}
}
// 手动调用 sayHi方法
_lazyMan.sayHi(name)
// 使用定时器,让任务队列在同步线程完成之后再执行
setTimeout(() => {
_lazyMan.next()
}, 0);
// 暴露 lazyman对象
return _lazyMan
}
// LazyMan("Hank")
// LazyMan("Hank").sleep(5).eat("dinner")
// LazyMan("Hank").eat("dinner").eat("supper")
LazyMan("Hank").sleepFirst(5).eat("supper")


方法2:
1. 看题目输出示例,可以确定这是拟人化的输出,也就是说:应该编写一个类来定义一类人,叫做LazyMan。可以输出名字、吃饭、睡觉等行为。
 2. 从输出的句子可以看出,sleepFrist的优先级是最高的,其他行为的优先级一致。
 3. 从三个例子来看,都得先调用LazyMan来初始化一个人,才能继续后续行为,所以LazyMan是一个接口。
 4. 句子是按调用方法的次序进行顺序执行的,是一个队列。

[JavaScript]
纯文本查看
复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 采用模块模式来编写代码
(function (window, undefined) {
// 创建一个任务队列
var taskList = [];
// {
// 'msg': 'LazyMan', // 消息名
// 'args': 'Hank' // 参数列表
// }
// 订阅
function subscribe() {
var args = Array.prototype.slice.call(arguments);
if (args.length < 1) {
throw new Error("subscribe 参数不能为空!");
}
// 创建任务
var task = {
msg: args[0], // 消息名
args: args.slice(1) // 参数列表
}
// 除非是 sleepFirst 向前添加,否则向后追加
if (task.msg == "sleepFirst") {
taskList.unshift(task);
} else {
taskList.push(task);
}
}
// 发布
function publish() {
if (taskList.length > 0) {
// 调用 run(执行)方法
run(taskList.shift());
}
}
// 类
function LazyMan() {};
LazyMan.prototype.eat = function (str) {
subscribe("eat", str);
return this;
};
LazyMan.prototype.sleep = function (num) {
subscribe("sleep", num);
return this;
};
LazyMan.prototype.sleepFirst = function (num) {
subscribe("sleepFirst", num);
return this;
};
// 输出文字
function lazyManLog(str) {
console.log(str);
}
// 具体方法
// 打招呼
function lazyMan(str) {
lazyManLog("Hi!This is " + str + "!");
publish();
}
// 吃
function eat(str) {
lazyManLog("Eat " + str + "~");
publish();
}
// 睡
function sleep(num) {
setTimeout(function () {
lazyManLog("Wake up after " + num);
publish();
}, num * 1000);
}
// 先睡
function sleepFirst(num) {
setTimeout(function () {
lazyManLog("Wake up after " + num);
publish();
}, num * 1000);
}
// run(执行)方法:
function run(option) {
var msg = option.msg,
args = option.args;
switch (msg) {
case "lazyMan":
lazyMan.apply(null, args);
break;
case "eat":
eat.apply(null, args);
break;
case "sleep":
sleep.apply(null, args);
break;
case "sleepFirst":
sleepFirst.apply(null, args);
break;
default:
;
}
}
// 暴露接口
window.LazyMan = function (str) {
subscribe("lazyMan", str);
setTimeout(function () {
publish();
}, 0);
return new LazyMan();
};
})(window);
// LazyMan("Hank")
// LazyMan("Hank").sleep(5).eat("dinner")
// LazyMan("Hank").eat("dinner").eat("supper")
LazyMan("Hank").sleepFirst(5).eat("supper")



Promise版:

[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
function _LazyMan(name) {
this.promiseGetters = [];
var makePromise = function () {
var promiseObj = new Promise(function(resolve, reject){
console.log("Hi! This is " + name + "!");
resolve();
})
return promiseObj;
}
this.promiseGetters.push(makePromise);
// 在各个Promise的then函数中,将任务序列穿起来
var self = this;
var sequence = Promise.resolve();
// Promise.resolve 等价于
// var sequence = new Promise(function (resolve, reject) {
// resolve();
// })
setTimeout(function(){
for (var i = 0; i < self.promiseGetters.length; i++) {
var nowPromiseGetter = self.promiseGetters[i];
var thenFunc = (function (nowPromiseGetter) {
return function () {
return nowPromiseGetter()
}
})(nowPromiseGetter);
sequence = sequence.then(thenFunc);
};
}, 0); // 在下一个事件循环启动任务
}
_LazyMan.prototype.eat = function(name) {
var makePromise = function () {
var promiseObj = new Promise(function(resolve, reject){
console.log("Eat " + name + "~");
resolve();
})
return promiseObj;
}
this.promiseGetters.push(makePromise);
return this; // 实现链式调用
}
_LazyMan.prototype.sleep = function(time) {
var makePromise = function () {
var promiseObj = new Promise(function(resolve, reject){
setTimeout(function(){
console.log("Wake up after " + time + "s!");
resolve();
}, time * 1000);
})
return promiseObj;
}
this.promiseGetters.push(makePromise);
return this;
}
/* 封装 */
function LazyMan(name){
return new _LazyMan(name);
}
LazyMan("Hank").sleep(1).eat("dinner")