JavaScript 本质上是一门单线程语言
1.JavaScript的执行上下文
当一段JavaScript代码在运行的时候,它实际上是运行在执行上下文中。
以下3中类型会创建一个新的执行上下文:
(1)全局上下文是为运行代码主体而创建的执行上下文,为那些存在于JavaScript函数之外的任何代码而创建的。
(2)每个函数会在执行的时候创建自己的执行上下文。通常说的“本地上下文”。
(3)每个eval()
函数也会创建一个新的执行上下文。
每个执行上下文在本质上是一种作用域层级。每个代码段开始执行的时候都会创建一个新的上下文来运行它,并在代码退出的时候销毁掉。
每个上下文创建的时候会被推入执行上下文栈。当退出的时候,它会从上下文栈中移除
关于递归函数——即多次调用自身的函数,需要特别注意:每次递归调用自身都会创建一个新的上下文。这使得 JavaScript 运行时能够追踪递归的层级以及从递归中得到的返回值,但这也意味着每次递归都会消耗内存来创建新的上下文
2.JavaScript运行时
在执行 JavaScript 代码的时候,JavaScript 运行时实际上维护了一组用于执行 JavaScript 代码的代理。
每个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可能创建用于执行 worker 的额外的线程集合、一个任务队列以及一个微任务队列构成。
除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其它组成部分对该代理都是唯一的。
(1)事件循环
每个代理都是由事件循环驱动的,事件循环负责收集用事件(包括用户事件以及其他非用户事件等)、对任务进行排队以便在合适的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务(宏任务),然后是微任务,然后在开始下一次循环之前执行一些必要的渲染和绘制操作。
网页或者 app 的代码和浏览器本身的用户界面程序运行在相同的 线程中, 共享相同的 事件循环。 该线程就是 主线程,它除了运行网页本身的代码之外,还负责收集和派发用户和其它事件,以及渲染和绘制网页内容等。
事件循环分类:Window 事件循环、Worker 事件循环、Worklet 事件循环
1)window 事件循环驱动所有同源的窗口
2)worker 事件循环顾名思义就是驱动 worker 的事件循环。这包括了所有种类的 worker:最基本的 web worker 以及 shared worker 和 service worker。 Worker 被放在一个或多个独立于 “主代码” 的代理中。浏览器可能会用单个或多个事件循环来处理给定类型的所有 workder。
3)worklet 事件循环用于驱动运行 worklet 的代理。这包含了 Worklet
(en-US)、AudioWorklet
(en-US) 以及 PaintWorklet
(en-US)。
多个同源(译者注:此处同源的源应该不是指同源策略中的源,而是指由同一个窗口打开的多个子窗口或同一个窗口中的多个 iframe 等,意味着起源的意思,下一段内容就会对这里进行说明)窗口可能运行在相同的事件循环中,每个队列任务进入到事件循环中以便处理器能够轮流对它们进行处理。记住这里的网络术语 “window” 实际上指的用于运行网页内容的浏览器级容器,包括实际的 window,一个 tab 标签或者一个 frame。
在特定情况下,同源窗口之间共享事件循环,例如:
- 如果一个窗口打开了另一个窗口,它们可能会共享一个事件循环。
- 如果窗口是包含在
<iframe>
中,则它可能会和包含它的窗口共享一个事件循环。 - 在多进程浏览器中多个窗口碰巧共享了同一个进程。
3.任务VS微任务
一个任务就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。 除了使用事件,你还可以使用setTimeout()
或者setInterval()
来添加任务
任务队列和微任务队列的区别很简单,但却很重要:
当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行.
每次当一个任务退出且执行上下文为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在下一个任务开始执行之前且当前事件循环结束之前执行完所有的微任务。
问题:
由于你的代码和浏览器的用户界面运行在同一个线程中,共享同一个事件循环,假如你的代码阻塞了或者进入了无限循环,则浏览器将会卡死。
解决:使用 web workers 可以让主线程另起新的线程来运行脚本,这能够缓解上面的情况。一个设计良好的网站或应用会把一些复杂的或者耗时的操作交给 worker 去做,这样可以让主线程除了更新、布局和渲染网页之外,尽可能少的去做其他事情。
通过使用像 promises 这样的 异步JavaScript 技术可以使得主线程在等待请求返回结果的同时继续往下执行,这能够更进一步减轻上面提到的情况。
微任务是另一种解决该问题的方案,通过将代码安排在下一次事件循环开始之前运行而不是必须要等到下一次开始之后才执行,这样可以提供一个更好的访问级别。
JavaScript 执行上下文栈中没有执行上下文剩余时也可以将代码安排在一个安全的时间运行。
所有浏览器以及运行时中,一个标准的微任务队列机都制意味着这些微任务可以非常可靠的以相同的顺序执行,从而避免一些潜在的难以发现的错误。
备注:本文摘抄自MDN文档,仅作为个人笔记或者知识分享,勿作他用!
网友评论