前几天在稀土上看到一篇面试的帖子,里面微信有一道题是lazyman的实现,具体要作的事情就是javascript
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
这道题考察的确定不是实现这个函数的能力问题,应该是流程控制的问题。解决思路应该是将全部的人如都存放到一个数组中,并在全部的方法执行完以后一次性的输出,实现的代码以下:java
function _LazyMan(name) { this.tasks = []; var self = this; var fn =(function(n){ var name = n; return function(){ console.log("Hi! This is " + name + "!"); self.next(); } })(name); this.tasks.push(fn); setTimeout(function(){ self.next(); }, 0); // 在下一个事件循环启动任务 } /* 事件调度函数 */ _LazyMan.prototype.next = function() { var fn = this.tasks.shift(); fn && fn(); } _LazyMan.prototype.eat = function(name) { var self = this; var fn =(function(name){ return function(){ console.log("Eat " + name + "~"); self.next() } })(name); this.tasks.push(fn); return this; // 实现链式调用 } _LazyMan.prototype.sleep = function(time) { var self = this; var fn = (function(time){ return function() { setTimeout(function(){ console.log("Wake up after " + time + "s!"); self.next(); }, time * 1000); } })(time); this.tasks.push(fn); return this; } _LazyMan.prototype.sleepFirst = function(time) { var self = this; var fn = (function(time) { return function() { setTimeout(function() { console.log("Wake up after " + time + "s!"); self.next(); }, time * 1000); } })(time); this.tasks.unshift(fn); return this; } /* 封装 */ function LazyMan(name){ return new _LazyMan(name); }
我本身在思考解决方法的时候最让我困惑的就是若是判断Lazyman对象的方法被调用结束了?我甚至为了这个问题坐过了站。。。后来当我看到这段代码的时候,发现一个简单的setTimeout
就解决了这个问题,为何呢???面试
这张图是MDN对Event Loop的解释,这张图上分为三个部分,分别是队列、栈、和堆。咱们在理解lazyman的过程当中须要知道的就是队列和栈。首先咱们来说这个栈:数组
在js中,每当有函数被执行的时候都会在当前的执行堆栈中建立一个新的堆栈帧,并放到栈顶。这个堆栈帧中包含当前执行的函数的参数和局部变量。(有没有感受很熟悉,没错,这就是咱们理解做用域链的时候的那个栈)而当咱们的函数执行完以后,这个堆栈帧就会从当前栈中移除。微信
队列就是JS中用来处理异步事件的队列,每当有新的异步事件发生,就会添加一个新的消息到队列的尾部。当以前提到的栈为空时,JS就会来处理队列中的消息。并发
举个例子来讲就是:异步
var a = function() { setTimeout(function(){console.log(1)},0) } var b = function(){ a() console.log(2) } // 2 // 1
这里须要注意的有函数
就算你不在函数中使用setTimeout
,而是在全局环境中使用,setTimeout
也是在正常的同步代码执行完以后执行,这是由于还有宿主环境在。oop
setTimeOut
是通过一段时间以后直接向队列中加入一个消息,而普通的http请求是等到有返回结果了才会将消息加入到队列中。this
普通的异步事件若是没有事件监听器的话是不会操做队列的,消息是直接被忽视掉。
Lazyman中的`setTimeout不是单单的在函数中执行,而是在对象链式调用中执行。由于是链式调用,因此代码一直在同一个做用于中执行,也就是说当前的堆栈帧一直没有被移出栈。上面的代码中就是利用了这个特色解决了如何判断对象调用结束的问题。