求star

开源不易,喜欢请点个star吧

Ocean Han
1358 字
7 分钟
JavaScript异步机制
2023-04-17

JavaScript异步机制#

TIP

​ JavaScript是单线程语言,也就是说,一次只能完成一件任务,如果存在多个任务需要完成,那么后面的任务要等待前一个任务完成之后,才能执行,以此类推。这种模式的好处是执行环境相对单纯,实现起来简单,坏处是只要有一个任务耗时过长卡住,后面的任务都会一直等待,拖延整个程序的执行。常见的浏览器假死状态,通常就是某一段JS代码长时间运行比如死循环,导致其他任务无法执行

执行机制#

TIP

JS为了解决单线程带来的上述问题,把任务的执行方式分为两种: 同步(Synchronous)异步(Asynchronous)

同步#

TIP

​ 同步模式就是同步阻塞,后面的任务需要等待前一个任务执行完毕,才能开始执行。也就是说,程序的执行顺序,和任务的排列顺序是一致的,比较好理解

let i = 100;
whlie(--i) {console.log(i);}
console.log('while 循环执行完毕后才能执行');

异步#

TIP

​ 异步就是非阻塞执行,每个任务拥有一个或多个callback回调函数。前一个任务结束后,执行该任务的回调函数,而后一个任务则不等待前一个任务执行完毕就直接执行。所以程序的实际执行顺序与任务排列顺序是不一致的。

​ 我们知道,浏览器给每个Tab标签页分配了一个单独的JS线程,用来与用户交互或操作DOM等等,这也就是为什么它只能是单线程(如果JS不是单线程的,那么一个线程添加DOM,一个线程删除DOM,浏览器就无法确定以哪个线程的操作为准)

WARNING

W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。此外这与浏览器设定、主线程以及任务队列也有关系,执行时间可能大于4ms,例如老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动尤其是涉及页面重新渲染的部分,通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。

setTimeout(() => consolve.log("回调函数中的 后执行"),0)
console.log('先执行');

异步机制#

setTimeout(() => console.log('我得等while 执行完毕后执行'),0);
let i = 3000000000;
while(--i) {}
TIP

​ 上面的代码中,在主线程设置了一个非常大的循环来阻塞JS主线程(如果设置一个死循环,那么上面的setTimeout将永远不会有机会执行)。在浏览器中测试,大约30s后才执行setTimeout(因为主执行栈的任务并没有执行完毕,就不会去任务队列读取任务执行,即使这个任务早就在任务队列中)

​ 而且,浏览器渲染线程与JS引擎线程是互斥的,即在Js线程在处理任务时,渲染线程会被挂起,也就是浏览器暂停渲染,整个页面都被阻塞,无法刷新甚至无法关闭,只能通过使用任务管理器结束Tab进程的方式关闭页面。

JS实现异步是通过执行栈和任务队列来配合完成的,所有同步任务都在主线程执行,形成执行栈,任务队列中存放这各种事件回调,当执行栈任务执行完毕(执行栈为空)时,主线程就开始读取任务队列中的任务并执行,不断往复循环。

WARNING

​ 另外,关于setTimeout,定时计数器并不是由JavaScript来进行计数的,因为一旦JS线程发生阻塞就会影响计时器的准确,计数是由浏览器线程进行的,当计数完毕,就将它的回调加入任务队列。同样HTTP请求在浏览器中也存在单独的线程,也是执行完毕后将事件回调置入任务队列

Event Loop(事件循环):repeat:#

TIP

​ 主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event LoopEvent Loop是一个执行模型,在不同的地方有不同的实现。比如浏览器和NodeJS基于不同的技术实现了各自的Event Loop。浏览器的Event Loop是在HTML5的规范中明确定义,NodeJSEvent Loop是基于libuv实现的

​ 在浏览器中的Event Loop由执行栈Execution Stack、后台线程Background Threads、宏队列Macrotask Queue、微队列Microtask Queue组成。

  • 执行栈就是在主线程执行同步任务的数据结构,函数调用形成了一个由若干帧组成的栈。
  • 后台线程就是浏览器实现对于setTimeoutsetIntervalXMLHttpRequest等等的执行线程。
  • 宏队列,一些异步任务的回调会依次进入宏队列,等待后续被调用,包括setTimeoutsetIntervalsetImmediate(Node)requestAnimationFrameUI renderingI/O等操作
  • 微队列,另一些异步任务的回调会依次进入微队列,等待后续调用,包括Promiseprocess.nextTick(Node)Object.observeMutationObserver等操作

​ 为了节省篇幅,有关事件循环更详细的介绍请移步: JS中的事件循环机制

JavaScript异步机制
https://blog.oceanh.top/posts/frontend/javascript异步机制/
作者
Ocean Han
发布于
2023-04-17
许可协议
CC BY-NC-SA 4.0
最后修改时间
2024-08-10 10:08:49