今天来分析下setTimeout这个函数。
语法
首先是setTimeout方法的api:
setTimeout(code,millisec);
参数 | 描述 |
---|---|
code | 必需。要调用的函数后要执行的 JavaScript 代码串。 |
millisec | 必需。在执行代码前需等待的毫秒数。 |
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,其实是在指定毫秒后将调用函数或计算表达式的任务加入到任务队列。
setTimeout原理
JavaScript 是单线程执行的,也就是无法同时执行多段代码,当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列,一旦当前任务执行完毕,再从队列中取出下一个任务。这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是 Ajax 请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript 进程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
关于任务队列与事件循环
setTimeout的作用是延迟将任务加入到任务队列,那任务队列又是什么呢?
首先,JavaScript是单线程语言,即一次只能执行一个任务,也就是说所有的任务需要排队,当前一个任务结束之后后一个任务才能执行,如果前一个任务耗时很长,则后一个任务将一直阻塞。如果当前执行的任务是IO操作,则CPU此时空闲,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,任务被分成了两种:
一种是同步任务(synchronous):在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
另一种是异步任务(asynchronous):不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
注意只有当主线程空了的时候才会去读任务队列中的任务。
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。
关于setTimeout(fn,0)
setTimeout(fn,0)与fn有什么区别呢?
setTimeout(fn,0)表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。
本文总结自 http://www.ruanyifeng.com/blog/2014/10/event-loop.html