调用栈是解释器追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。
- 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
- 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
- 当分配的调用栈空间被占满时,会引发『堆栈溢出』。
并发模型与事件循环
运行时概念
栈
函数调用形成了一个栈帧。
堆
对象被分配在一个堆中,即用以表示一大块非结构化的内存区域。
队列
一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都关联着一个用以处理这个消息的函数。
在事件循环期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并作为输入参数调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。
函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。
事件循环
❗️必读 event-loop
“执行至完成”
每一个消息完整地执行后,其它消息才会被执行。
这个模型的一个缺点在于当一个消息需要太长时间才能处理完毕时,Web 应用就无法处理用户的交互,例如点击或滚动。浏览器用『程序需要过长时间运行』的对话框来缓解这个问题。一个很好的做法是缩短消息处理,并在可能的情况下将一个消息裁剪成多个消息。
添加消息
在浏览器里,当一个事件发生且有一个事件监听器绑定在该事件上时,消息会被随时添加进队列。如果没有事件监听器,事件会丢失。所以点击一个附带点击事件处理函数的元素会添加一个消息,其它事件类似。
函数 setTimeout
接受两个参数:待加入队列的消息和一个延迟(可选,默认为 0
)。这个延迟代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,setTimeout
消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。
零延迟
零延迟并不意味着回调会立即执行。以 0
为第二参数调用 setTimeout
并不表示在 0 毫秒后就立即调用回调函数。
多个运行时互相通信
一个 web worker 或者一个跨域的 iframe 都有自己的栈,堆和消息队列。两个不同的运行时只能通过 postMessage
方法进行通信。如果另一个运行时侦听 message
事件,则此方法会向该运行时添加消息。
永不阻塞
JavaScript 永不阻塞。 处理 I/O 通常通过事件和回调来执行。
但也存在例外,如 alert
或者同步 XHR
。
微任务与宏任务
https://juejin.im/post/5b498d245188251b193d4059