大家好,我是前端西瓜哥。今天我们来看一道JS编程题。问题实现一个LazyMan,可以按如下方式调用:LazyMan("Hank")输出:嗨!这是汉克!LazyMan("Hank").sleep(10).eat("dinner")输出嗨!这是汉克!//等待10秒..10Eatdinner后醒来LazyMan("Hank").eat("dinner").eat("supper")输出Hi!ThisisHank!EatdinnerEatsupperLazyMan("Hank").sleepFirst(5).eat("supper")输出//等5秒5Hi后醒来!这是汉克!吃夜宵等等。需要实现的功能我们先来分析一下需要实现的效果。第一个是Lazy('Hank'),它输出Hi!这是汉克。然后.sleep(10),会延时10秒,执行Wakeupafter10。之后会延时执行eat之类的。.eat('dinner'),就是直接输出Eatdinner。最后还有一个特殊的.sleepFirst(5),会放在最前面,提前执行,然后再执行其他的东西。思维题不简单,考的是多个知识点。第一种是它是以链式调用的形式使用的,即调用方法后对象会返回对象,然后可以继续调用对象的其他方法,形成链式调用.所以这个LazyMan('Hank')应该返回一个对象,而且这个对象还必须有sleep、eat、sleepFirst等方法。所以,我首先这样写:functionLazyMan(name){constsolver={sleep(second){returnsolver;},eat(something){returnsolver;},sleepFirst(second){返回求解器;},};returnsolver;}solver方法也可以返回这个。这里我没有把这个退回去,因为我担心这个指向丢失的问题。另一种方式是使用ES6类编写。functionLazyMan(name){returnnewMyLazyMan(name);}classMyLazyMan{}多了一层封装,但是可以更好的维护属性。不然像我这样写,需要用闭包来维护变量。回到主题。然后我们实现顺序输出的效果,因为里面的sleep是异步的,sleepFirst也会预输出,所以不能每执行一个方法就立即输出。相反,它需要先被缓存,然后在调用所有方法后执行。另外,由于所有的任务都是在执行开始前收集的,所以我们需要使用setTimeout构造一个异步宏任务,保证任务的执行在同步代码之后执行。functionLazyMan(name){setTimeout(()=>{//确保不要过早执行run();});functionrun(){//按顺序执行任务}}所以,我们需要一个队列来保存它们。队列是一个先进先出的线性列表。我们使用数组来实现它。理论上用链表比较好,但是自己实现起来很麻烦。通常,数据量不大,所以我们在开发中使用数组。一个重要的分歧点已经出现。这个队列存储什么?一种思路是在队列中存放一个对象,其中包含msg和t,记录输出内容,延迟执行时间。然后我们自己执行对象内部的业务逻辑。{msg:`Wakeupafter10`,t:10,}另一个想法是函数存储在队列中,它们应该顺序执行。实现类似于中间件,本质上是设计模式的责任链模式。执行当前函数后,我们调用next来执行下一个函数。如果你用过Express框架,可能会觉得很熟悉。run是一个递归函数,不断的执行自己,从队列中取出第一个任务,执行,然后执行run方法,直到队列为空。代码实现functionLazyMan(name){constqueue=[{msg:`Hi!这是${name}`,t:undefined,},];setTimeout(()=>{//确保在同步代码之后执行run();});functionrun(){//顺序执行任务if(queue.length===0)return;const{msg,t}=queue.shift();//不需要延迟的任务,我转为同步执行//也可以让它们都和异步执行保持一致if(t===undefined){console.log(msg);跑步();//执行}else{setTimeout(()=>{console.log(msg);run();},t*1000);}}constsolver={sleep(second){queue.push({msg:`Wakeupafter${second}`,t:second,});返回求解器;},eat(something){queue.push({msg:`Eat${something}`,t:undefined,});返回求解器;},sleepFirst(second){//比较特殊,需要放在队列的开头queue.unshift({msg:`Wakeupafter${second}`,t:second,});返回求解器;},};returnsolver;}对于睡眠方法,我只负责让它们被加入到队列中,我在运行中统一处理具体的执行。去网上看了别人的写法,发现Express的nextstyle比较多,所以我也写一个。functionLazyMan(name){returnnewMyLazyMan(name);}classMyLazyMan{constructor(name){this.queue=[];this.queue.push(()=>{setTimeout(()=>{console.log(`嗨!这是${name}`);})this.next();//不要忘记执行next})//这里还是要保证setTimeout(()=>{this在同步代码之后执行.next();})}next(){setTimeout(()=>{if(this.queue.length===0)return;consttask=this.queue.shift();task();})}eat(something){this.queue.push(()=>{console.log(`Eat${something}`);this.next();});归还这个;}睡眠(第二){这个。queue.push(()=>{setTimeout(()=>{console.log(`Wakeupafter${second}`);this.next();},second*1000);});归还这个;}sleepFirst(second){this.queue.unshift(()=>{setTimeout(()=>{console.log(`Wakeupafter${second}`);this.next();},second*1000)});归还这个;}}这里需要注意的是,setTimeout中不要忘记加上this.next,否则执行链会中途断掉。async/await,甚至使用rxjs,读者可以自行尝试。这道编程题最后考的东西很多,包括业务代码编写能力、队列、中间件思想(责任链模式)、异步代码等。
