JS运行的环境称之为宿主环境。

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

JS引擎永远执行的是执行栈的最顶部。

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
    2. GUI线程:负责渲染页面
    3. 事件监听线程:负责监听各种事件
    4. 计时线程:负责计时
    5. 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

-** 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
-** 微任务(队列):MutationObserver,Promise产生的回调进入微队列

MutationObserver用于监听某个DOM对象的变化

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务**。**

案例1

事件循环 - 执行上下文

  1. function a() {
  2. console.log("a")
  3. b();
  4. }
  5. function b() {
  6. console.log("b");
  7. c();
  8. }
  9. function c() {
  10. console.log("c")
  11. }
  12. console.log("global");
  13. a();

此时的事件循环为
image.png
当 console.log(“global”); 执行完后出栈加载新的上下文 新的上下文为函数a
image.png
函数a中 拥有来个函数 一个为console.log(“a”) 一个为b(); 先执行console.log(“a”) 当其console.log(“a”) 的执行上下文出栈后才会执行 b()如下图

image.png
此时执行call stack 中的b函数 函数b中也有两个函数
image.png
执行栈顶的上下文后
image.png
开始执行函数c的上下文
image.png
执行完函数c中的函数后
image.png
此时开函数c中没有可执行的上下文啦 及出栈
image.png
函数b与函数a 与函数c同样的道理,当其函数b与函数c出栈后,全局没有可执行的上下文后全局上下文出栈
image.png

案例2

事件对列

注: 当其call stack 为空时才会执行事件里的函数,当call stack不为空时不会去执行事件对列中的函数 ,故一次执行一个事件对列里的函数

  1. document.getElementById("btn").onclick = function A() {
  2. console.log("按钮被点击了");
  3. }

如下图
image.png
call stack 里面是引擎, wed api 宿主环境是其余四个线程
代码中btn 单击事件会触发A() 那吗事件监听线程会监听btn是否被单击 当btn单击是执行函数A 不会直接放入call stack 而是放入事件队列如下图
image.png
当call stack 为空时才会执行事件队列里的函数 , 事件队列的执行顺序为, 谁先被添加进事件对列谁就被calls tack 执行,

当 call stack 为空时
image.png
函数A被放入callstack

案例3

深入事件对列

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务

  1. <ul id="container">
  2. </ul>
  3. <button id="btn">点击</button>
  4. <script>
  5. let count = 1;
  6. const ul = document.getElementById("container");
  7. document.getElementById("btn").onclick = function A() {
  8. setTimeout(function C() {
  9. console.log("添加了一个li")
  10. }, 0);
  11. var li = document.createElement("li")
  12. li.innerText = count++;
  13. ul.appendChild(li);
  14. }
  15. //监听ul
  16. const observer = new MutationObserver(function B() {
  17. //当监听的dom元素发生变化时运行的回调函数
  18. console.log("ul元素发生了变化")
  19. })
  20. //监听ul
  21. observer.observe(ul, {
  22. attributes: true, //监听属性的变化
  23. childList: true, //监听子元素的变化
  24. subtree: true //监听子树的变化
  25. })
  26. //取消监听
  27. // observer.disconnect();
  28. </script>

如图
image.png
当btn的单击事件出后如图

image.png
由于此时 callstack为空 所及call stack会去执行事件对列,但是事件对列的微队列为空 而去执行宏对列
如下图
image.png
函数A中 拥有函数C 函数C 是延迟执行 会被时间线程监听
如图
image.png
但是函数C会延迟0秒执行 故别放入宏对列,但此时函数A()的执行上下文还没被销毁,同时函数A()会ul填加li元素
ul元素有监听函数 B() 当其ul元素发生变化时,函数B()被放入微队列
如图
image.png
此时函数A()出栈 ,函数A()出栈后call stack 为空 会取事件对列 中优先执行微队列
如图
image.png
当其函数B()执行完后出栈 ,当call stack 为空时会去执行事件对列里的函数,此时微队列为空 就会执行 函数C
如图
image.png