经典回溯,从一个老面试题(LazyMan)中学到的知识

经典回溯,从一个老面试题(LazyMan)中学到的知识

技术博客 admin 499 浏览

人们因能感知太多信息而感到心神不宁,或因产生过多欲望而痛苦不堪,又或因担忧能力不足而滋生焦虑,无论顺境或是逆境都不得安生。就像今天的我们,虽衣食无忧,却总是苦于无法摆脱手机的干扰,无法获取让人羡慕的技能,无法拥有想要的生活,等等。低层次的动物是没有这种烦恼的,它们的心灵只容纳环境中确实存在的、与它们切身相关的、靠直觉判断的信息——饥饿的狮子只注意能帮助它猎到羚羊的信息,吃饱的狮子的注意力则集中在温暖的阳光上...... ——《认知觉醒》

前言

“旧游如梦,重逢却是初见”,这句话很好的描述了再看见这个面试题的感觉,最开始知道这道题的时候,它只是道题,再看见时,它好像变成了很多知识点,这也是一种进步的吧。

题目

可能有些人不知道这道题目,所以有必要描述一下题目,这是一道经典题目

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)中学到的知识

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!