从JS执行机制说起

浏览器(或者说JS引擎)执行JS的机制是基于事件循环。

由于JS是单线程,所以同一时间只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行。

为了避免因为某些长时间任务造成的无意义等待,JS引入了异步的概念,用另一个线程来管理异步任务。

同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程。等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如ajax请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环。

JS定时器

JS的定时器目前有三个:setTimeout、setInterval和setImmediate。

定时器也是一种异步任务,通常浏览器都有一个独立的定时器模块,定时器的延迟时间就由定时器模块来管理,当某个定时器到了可执行状态,就会被加入主线程队列。

JS定时器非常实用,做动画的肯定都用到过,也是最常用的异步模型之一。

有时候一些奇奇怪怪的问题,加一个setTimeout(fn, 0)(以下简写setTimeout(0))就解决了。不过,如果对定时器本身不熟悉,也会产生一些奇奇怪怪的问题。

setTimeout

setTimeout(fn, x)表示延迟x毫秒之后执行fn。

使用的时候千万不要太相信预期,延迟的时间严格来说总是大于x毫秒的,至于大多少就要看当时JS的执行情况了。

另外,多个定时器如不及时清除(clearTimeout),会存在干扰,使延迟时间更加捉摸不透。所以,不管定时器有没有执行完,及时清除已经不需要的定时器是个好习惯。

HTML5规范规定最小延迟时间不能小于4ms,即x如果小于4,会被当做4来处理。 不过不同浏览器的实现不一样,比如,Chrome可以设置1ms,IE11/Edge是4ms。

setTimeout注册的函数fn会交给浏览器的定时器模块来管理,延迟时间到了就将fn加入主进程执行队列,如果队列前面还有没有执行完的代码,则又需要花一点时间等待才能执行到fn,所以实际的延迟时间会比设置的长。如在fn之前正好有一个超级大循环,那延迟时间就不是一丁点了。

(function testSetTimeout() {

const label = 'setTimeout';

console.time(label);

setTimeout(() => {

console.timeEnd(label);

}, 10);

for(let i = 0; i < 100000000; i++) {}

})();

结果是:setTimeout: 335.187ms,远远不止10ms。

setInterval

setInterval的实现机制跟setTimeout类似,只不过setInterval是重复执行的。

对于setInterval(fn, 100)容易产生一个误区:并不是上一次fn执行完了之后再过100ms才开始执行下一次fn。 事实上,setInterval并不管上一次fn的执行结果,而是每隔100ms就将fn放入主线程队列,而两次fn之间具体间隔多久就不一定了,跟setTimeout实际延迟时间类似,和JS执行情况有关。

(function testSetInterval() {

let i = 0;

const start = Date.now();

const timer = setInterval(() => {

i += 1;

i === 5 && clearInterval(timer);

console.log(`第${i}次开始`, Date.now() - start);

for(let i = 0; i < 100000000; i++) {}

console.log(`第${i}次结束`, Date.now() - start);

}, 100);

})();

输出

第1次开始 100

第1次结束 1089

第2次开始 1091

第2次结束 1396

第3次开始 1396

第3次结束 1701

第4次开始 1701

第4次结束 2004

第5次开始 2004

第5次结束 2307

可见,虽然每次fn执行时间都很长,但下一次并不是等上一次执行完了再过100ms才开始执行的,实际上早就已经等在队列里了。

另外可以看出,当setInterval的回调函数执行时间超过了延迟时间,已经完全看不出有时间间隔了。

如果setTimeout和setInterval都在延迟100ms之后执行,那么谁先注册谁就先执行回调函数。

setImmediate

这算一个比较新的定时器,目前IE11/Edge支持、Nodejs支持,Chrome不支持,其他浏览器未测试。

从API名字来看很容易联想到setTimeout(0),不过setImmediate应该算是setTimeout(0)的替代版。

在IE11/Edge中,setImmediate延迟可以在1ms以内,而setTimeout有最低4ms的延迟,所以setImmediate比setTimeout(0)更早执行回调函数。不过在Nodejs中,两者谁先执行都有可能,原因是Nodejs的事件循环和浏览器的略有差异。

(function testSetImmediate() {

const label = 'setImmediate';

console.time(label);

setImmediate(() => {

console.timeEnd(label);

});

})();

Edge输出:setImmediate: 0.555 毫秒

很明显,setImmediate设计来是为保证让代码在下一次事件循环执行,以前setTimeout(0)这种不可靠的方式可以丢掉了。

其他常用异步模型

requestAnimationFrame

requestAnimationFrame并不是定时器,但和setTimeout很相似,在没有requestAnimationFrame的浏览器一般都是用setTimeout模拟。

requestAnimationFrame跟屏幕刷新同步,大多数屏幕的刷新频率都是60Hz,对应的requestAnimationFrame大概每隔16.7ms触发一次,如果屏幕刷新频率更高,requestAnimationFrame也会更快触发。基于这点,在支持requestAnimationFrame的浏览器还使用setTimeout做动画显然是不明智的。

在不支持requestAnimationFrame的浏览器,如果使用setTimeout/setInterval来做动画,最佳延迟时间也是16.7ms。 如果太小,很可能连续两次或者多次修改dom才一次屏幕刷新,这样就会丢帧,动画就会卡;如果太大,显而易见也会有卡顿的感觉。

有趣的是,第一次触发requestAnimationFrame的时机在不同浏览器也存在差异,Edge中,大概16.7ms之后触发,而Chrome则立即触发,跟setImmediate差不多。按理说Edge的实现似乎更符合常理。

Edge输出:requestAnimationFrame: 16.66 毫秒

Chrome输出:requestAnimationFrame: 0.698ms

但相邻两次requestAnimationFrame的时间间隔大概都是16.7ms,这一点是一致的。当然也不是绝对的,如果页面本身性能就比较低,相隔的时间可能会变大,这就意味着页面达不到60fps。

Promise

Promise是很常用的一种异步模型,如果我们想让代码在下一个事件循环执行,可以选择使用setTimeout(0)、setImmediate、requestAnimationFrame(Chrome)和Promise。

而且Promise的延迟比setImmediate更低,意味着Promise比setImmediate先执行。

function testSetImmediate() {

const label = 'setImmediate';

console.time(label);

setImmediate(() => {

console.timeEnd(label);

});

}

function testPromise() {

const label = 'Promise';

console.time(label);

new Promise((resolve, reject) => {

resolve();

}).then(() => {

console.timeEnd(label);

});

}

testSetImmediate();

testPromise();

Edge输出:Promise: 0.33 毫秒 setImmediate: 1.66 毫秒

尽管setImmediate的回调函数比Promise先注册,但还是Promise先执行。

可以肯定的是,在各JS环境中,Promise都是最先执行的,setTimeout(0)、setImmediate和requestAnimationFrame顺序不确定。

process.nextTick

process.nextTick是Nodejs的API,比Promise更早执行。

事实上,process.nextTick是不会进入异步队列的,而是直接在主线程队列尾强插一个任务,虽然不会阻塞主线程,但是会阻塞异步任务的执行,如果有嵌套的process.nextTick,那异步任务就永远没机会被执行到了。

使用的时候要格外小心,除非你的代码明确要在本次事件循环结束之前执行,否则使用setImmediate或者Promise更保险。

JavaScript定时器与执行机制解析的更多相关文章

  1. JavaScript定时器与执行机制

    JavaScript动画中是必须使用到定时器的,这里做一个总结. var label = 'someLable'; console.time(label); console.timeEnd(label ...

  2. 十分钟理解JavaScript引擎的执行机制

    关注专栏写文章 十分钟理解JavaScript引擎的执行机制 方伟景 千锋前端开发推动市场提升的学习研究者. 4 人赞同了该文章 首先,请牢记2点: JS是单线程语言 JS的Event Loop是JS ...

  3. 关于javascript的事件执行机制理解

    理解javascript事件执行机制 众所周知,js是一个单线程的语言,这意味着同一时间只能做一件事,但是我们又说js是异步的.首先,单线程并不是没有优点.作为浏览器脚本语言,JavaScript 的 ...

  4. javascript执行机制

    文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的 ...

  5. JavaScript 执行机制

    一.宏任务与微任务 macro-task(宏任务):包括整体代码script,setTimeout,setInterval micro-task(微任务):Promise,process.nextTi ...

  6. 转载---JavaScript执行机制

    很好的一篇文章,原地址 JavaScript执行机制 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不 ...

  7. 【js】javaScript 执行机制

    javascript 是一门单线程语言(按照语句一行一行的执行) let a = '1'; console.log(a); let b = '2'; console.log(b); 这样子正常执行是没 ...

  8. 【THE LAST TIME】彻底吃透 JavaScript 执行机制

    前言 The last time, I have learned [THE LAST TIME]一直是我想写的一个系列,旨在厚积薄发,重温前端. 也是给自己的查缺补漏和技术分享. 欢迎大家多多评论指点 ...

  9. 这一次,彻底弄懂 JavaScript 执行机制

    本文转自https://juejin.im/post/59e85eebf265da430d571f89#heading-4 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还 ...

随机推荐

  1. Java:Collection集合类

    特点:只能用于存储对象,集合长度时可变的,集合可以存储不同类型的对象. 数组和集合类同时容器,不同的是: 1.数组虽然也可以存储对象,但是长度是固定的:集合长度时可变的. 2.数组中可以存储基本数据类 ...

  2. Android AlarmManager(全局定时器/闹钟)指定时长或以周期形式执行某项操作

    AlarmManager的使用机制有的称呼为全局定时器,有的称呼为闹钟.通过对它的使用,个人觉得叫全局定时器比较合适,其实它的作用和Timer有点相似.都有两种相似的用法:(1)在指定时长后执行某项操 ...

  3. 怎么批量修改Word表格的宽度

    怎么批量修改Word表格的宽度 怎么批量修改Word表格的宽度呢.Word表格可根据窗口自动调整表格宽度,使得所有的表格宽度和页面宽度一样.当页面设置了新的页边距后,所有的表格都需要调整新的宽度.或者 ...

  4. 在MAC中安装XMPP服务器

    一.安装MySQL 1.下载安装包

  5. Ubuntu下安装Git以及Git帮助手册【转】

    转自:http://milkythinking.com/blog/2011/04/17/install_git_and_manual/ Git简介 Git是一个分布式版本控制系统,对应的是SVN.CV ...

  6. 利用SOLR搭建企业搜索平台 之——配置文件

    运行solr是个很简单的事,如何让solr高效运行你的项目,这个就不容易了.要考虑的因素太多.这里很重要一个就是对solr的配置要了解.懂得配置文件每个配置项的含义,这样操作起来就会如鱼得水! 在so ...

  7. spring.net异常处理

    接下来我们看一下SpringNetAop层的内容:此层主要是实现一些AOP的代码,以及特性和一个统一调用spring.net的类:记录日志我们使用Log4Net来实现: 1:Aspects 文件夹里存 ...

  8. js和 jquery对象

    核心提示:jquery选择器得到的jquery对象和标准的 javascript中的document.getElementById()取得的dom对象是两种不同的对象类型,一般情况下,如S(’#id’ ...

  9. 网络攻击之二:XSS(之一是SQL注入,前面有文章)

    学习了 http://www.oschina.net/question/565065_57506 (这里做了转载 http://blog.csdn.net/stilling2006/article/d ...

  10. How to install ruby on mac/ change ruby source in china

    his one is tailor made for the Basix users among you. If you've been itching to try out Ruby and/or ...