JavaScript的event-loop
浏览器渲染
从耗时的角度,浏览器请求、加载、渲染一个页面,时间花在下面五件事情上:
1.DNS 查询
2.TCP 连接
3.HTTP 请求即响应
4.服务器响应
5.客户端渲染
这里重点讨论第五个部分,即浏览器对内容的渲染,这一部分(渲染树构建,布局和绘制),又可以分为下面的五个部分。
1.处理 HTML 标记并构建 DOM 树。
2.处理 CSS 标记并构建 CSSOM 树。
3.将 DOM 与 CSSOM 合并成一个渲染树。
4.根据渲染树来布局,以计算每个节点的几何信息。
5.将各个节点绘制到屏幕上。
这些并不是本文正文,只是说在完成以上过程之后,整个页面就已经出来了,这个时候浏览器是否就已经处于空闲状态了呢(不考虑动画、交互等处理)?接下来就是本文的重点了。
堆、栈、队列
了解重点之前我们先了解一点简单的基础知识,堆、栈、队列;
堆
对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域。
这是在程序运行时需要给对new操作产生的对象分配存储空间,是一个没有特别限制的存储空间。
栈
函数调用形成一个栈帧;
栈的特点:先进后出(First in, last out)函数执行栈过程;可以看成是每次函数first运行时,将函数入栈,此时函数中的其他运行函数(second函数)需要再次入栈,执行完该second函数之后,该second函数将会出栈,继而完成first的执行,执行完成后,first将会出栈;
队列
队列是一种和栈不一样的数据结构,类似管道,先进入的将会现出来,和栈是相反的。
event-loop
javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。
单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。
而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
单线程是必要的,也是javascript这门语言的基石,原因之一在其最初也是最主要的执行环境——浏览器中,我们需要进行各种各样的dom操作。试想一下 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。
当然,现如今人们也意识到,单线程在保证了执行顺序的同时也限制了javascript的效率,因此开发出了web worker技术。这项技术号称让javascript成为一门多线程语言。
然而,使用web worker技术开的多线程有着诸多限制,例如:所有新线程都受主线程的完全控制,不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。另外,这些子线程并没有执行I/O操作的权限,只能为主线程分担一些诸如计算等任务。所以严格来讲这些线程并没有完整的功能,也因此这项技术并非改变了javascript语言的单线程本质。
可以预见,未来的javascript也会一直是一门单线程的语言。
那么为了能够很好地提高的脚本的效率,故而设计的时候有一个非常有趣的特性是事件循环模型,与许多其他语言不同,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待IndexedDB查询返回或者一个 XHR 请求返回时,它仍然可以处理其它事情,如用户输入。
macro task与micro task
- 首先执行script,script被称为全局任务,也属于macrotask;
- 当macrotask执行完以下,执行所有的微任务;
- 微任务全部执行完,再取任务队列中的一个宏任务执行。
宏任务包括:script, setTimeout, setInterval, setImmediate, I/O,UI rendering,requestAnimationFrame
微任务包括:process.nextTick(node api), 原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver
执行顺序
所有的宏任务放在一个宏任务队列(即任务队列),处理完一个宏任务(从sccript开始),将微任务队列(包含当时所有的微任务)压入任务队列(宏任务队列)并执行,之后再取下一个任务队列(宏任务)中的宏任务。
https://juejin.im/post/5b6d58...
https://github.com/ccforward/... 【函数执行栈和事件队列】