人们因能感知太多信息而感到心神不宁,或因产生过多欲望而痛苦不堪,又或因担忧能力不足而滋生焦虑,无论顺境或是逆境都不得安生。就像今天的我们,虽衣食无忧,却总是苦于无法摆脱手机的干扰,无法获取让人羡慕的技能,无法拥有想要的生活,等等。低层次的动物是没有这种烦恼的,它们的心灵只容纳环境中确实存在的、与它们切身相关的、靠直觉判断的信息——饥饿的狮子只注意能帮助它猎到羚羊的信息,吃饱的狮子的注意力则集中在温暖的阳光上...... ——《认知觉醒》
前言
“旧游如梦,重逢却是初见”,这句话很好的描述了再看见这个面试题的感觉,最开始知道这道题的时候,它只是道题,再看见时,它好像变成了很多知识点,这也是一种进步的吧。
题目
可能有些人不知道这道题目,所以有必要描述一下题目,这是一道经典题目
js
复制代码
> LazyMan('Hank')
你好,我是 Hank
> LazyMan('Hank').sleep(10).eat('lunch')
你好,我是 Hank
(等10秒...)
我醒了,我刚睡了 10 秒
吃午餐
> LazyMan('Hank').eat('lunch').eat('supper')
你好,我是 Hank
吃午餐
吃晚餐
> LazyMan('Hank').sleepFirst(5).eat('supper')
(沉默5秒)
我醒了,我刚刚睡了 5 秒
你好,我是 Hank
吃晚餐
- 需要实现 LazayMan 函数,函数接收一个参数
- 实现链式调用
- 实现
sleep, eat, sleepFirst
方法,且执行时机要与上述匹配
知道上述情况后,大致可以奠定LazyMan
的基调
js
复制代码
const LazyMan = (name) => {
return {
sleep(){},
eat(){},
sleepFirst(){}
}
}
LazyMan("Hank")
清楚题目后,我们开始来实现吧,如果可以,我建议你可以自己先想一想!
实现 LazyMan('Hank')
从这里第一个打印来看,非常简单,只需要将这个参数值在控制台打印出即可
代码实现
js
复制代码
const LazyMan = (name) => {
console.log(`你好,我是${name}`)
return {
sleep(){},
eat(){},
sleepFirst(){}
}
}
LazyMan("Hank")
这样就可以得到第一个打印结果:
第一步实现非常简单,对吧,接下来继续实现下一个
实现sleep(10)和eat('lunch')
首先看到LazyMan('Hank').sleep(10).eat('lunch')
打印结果
js
复制代码
你好,我是 Hank
(等10秒...)
我醒了,我刚睡了 10 秒
吃午餐
- sleep函数的执行时机是按照链式调用顺序执行,且sleep的作用是等待n秒后,再执行后续调用
- eat函数就是根据参数打印是吃午餐,还是吃晚餐还是... 清楚这些后,我们开始在原来的代码上添加修改代码,以达到目的,既然 sleep 是等待n秒后执行其他函数,那我直接死循环等待即可
代码实现:
js
复制代码
const LazyMan = (name) => {
console.log(`你好,我是${name}`)
const lazyMan = {
sleep(second){
let startTime = Date.now()
while (Date.now() - startTime < 10000) { } // 空等 second 秒
console.log(`我醒了,刚刚睡了${second}秒`)
return lazyMan // 链式调用
},
eat(type){
console.log("吃午餐")
},
sleepFirst(){}
}
return lazyMan
}
LazyMan("Hank").sleep(10).eat("lunch")
完工,这样我们也达到了这个调用的打印顺序要求了,查看结果:
接下来继续实现下一个
实现eat函数
我们先看到调用方式LazyMan('Hank').eat('lunch').eat('supper')
,在看打印结果
js
复制代码
你好,我是 Hank
吃午餐
吃晚餐
- eat链式调用
- eat根据参数不同,打印出不同文字 很巧,链式调用就是直接返回一个对象即可,我们已经完成了,剩下就是根据不同参数,打印不同结果了
代码实现:
js
复制代码
const LazyMan = (name) => {
console.log(`你好,我是${name}`)
const eatInfoMap = {
'lunch': "吃午餐",
'supper': "吃晚餐"
}
const lazyMan = {
sleep(second){
let startTime = Date.now()
while (Date.now() - startTime < 10000) { } // 空等 second 秒
console.log(`我醒了,刚刚睡了${second}秒`)
return lazyMan // 链式调用
},
eat(type){
console.log(eatInfoMap[type])
return lazyMan
},
sleepFirst(){}
}
return lazyMan
}
LazyMan('Hank').eat('lunch').eat('supper')
这样似乎就满足了当前需求,查看打印结果:
实现了这个以后,我们继续实现
实现sleepFirst函数
看到调用方式LazyMan('Hank').sleepFirst(5).eat('supper')
,再看打印结果
js
复制代码
(沉默5秒)
我醒了,我刚刚睡了 5 秒
你好,我是 Hank
吃晚餐
这时候发现,如果继续采用之前的方式,好像永远也无法实现这个了,有点难受,这说明前面的想法都是错误的了 再分析一下,sleepFirst可以改变链式调用的执行顺序,所以代码执行的顺序,需要我们进行控制,关于控制顺序的方式,刚好有执行队列呀,我们只需要把需要先执行的推送到执行队列最前方不就好了,然后考虑到有等待,可以让每一个执行队列的第一个任务执行完成后,再提醒后续执行,这样不就解决了嘛,那就试试吧!
删除之前代码,改变思路,使用一个队列来控制执行顺序
代码实现:
js
复制代码
const LazyMan = (name) => {
const eatInfoMap = {
'lunch': "吃午餐",
'supper': "吃晚餐"
}
const queue = [] // 任务队列
const helloTask = () => {
console.log(`你好,我是${name}`);
next()
}
queue.push(helloTask) // 入任务队列,默认是第一个打印,出了特殊情况
// 提醒需要执行后续函数,所以每个方法里执行了自己后,都需要向后传递,让后面执行
const next = () => {
const firstFn = queue.shift();
firstFn?.()
}
const lazyMan = {
sleep(s){
const task = () => {
setTimeout(() => {
console.log(`我醒了,我刚睡了 ${s} 秒`)
next()
}, s * 1000)
}
queue.push(task)
return lazyMan
},
eat(type){
const task = () => {
console.log(eatInfoMap[type])
next()
}
queue.push(task)
return lazyMan
},
sleepFirst(s){
// 这个任务比较特殊,需要在最前方等待,所以直接推送到队列第一个
const task = () => {
setTimeout(() => {
console.log(`我醒了,我刚刚睡了 ${s} 秒`)
next()
}, s * 1000)
}
queue.unshift(task)
return lazyMan
}
}
// 启动执行,但不能直接执行,如果直接执行的话,任务没有收集完,但是收集任务是同步的,所以要异步调用,所以就是手机完成后执行
setTimeout(() => next())
return lazyMan
}
LazyMan('Hank').sleepFirst(5).eat('supper')
- 使用了任务队列,让执行顺序变成可控的
- 所有的链式调用,先收集任务,按照函数效果插队,到对应位置
- 使用 next 作为媒介,当自己执行完成,让后续继续执行
查看结果:
至此,就完成了这道题目,我真的觉得相出这道题目的人是天才,哈哈哈,考点挺多的,如果之前没接触,还是很难写出来。
源文:经典回溯,从一个老面试题(LazyMan)中学到的知识
如有侵权请联系站点删除!
技术合作服务热线,欢迎来电咨询!