js事件循环原理

2022-10-19 12:35:34

一、执行栈

JS运行的环境称之为宿主环境。
执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。(单线程不容易冲突,但是效率低)
在这里插入图片描述
如下函数:

functiona(){
    console.log("a")b();}functionb(){
    console.log("b");c();}functionc(){
    console.log("c")}
console.log("global");a();
  1. 函数a执行,将a放入执行期上下文
  2. log(‘a’)执行,将log(‘a’)放入
  3. log(‘a’)执行完毕,将log(‘a’)移除
  4. 函数b执行,将b放入执行期上下文

    n-1. 函数b执行完毕,将b移除
    n. 函数a执行完毕,将a移除

在这里插入图片描述
对于递归函数:

functiongetFeibo(n){if(n===1|| n===2){return1;}returngetFeibo(n-1)+getFeibo(n-2);};
console.log(getFeibo(4));
  1. log(getFeibo(4))执行,将log(getFeibo(4))放入
  2. getFeibo(4)执行,将getFeibo(3)+getFeibo(2)放入
  3. getFeibo(3)执行,将getFeibo(2)+getFeibo(1)放入
  4. getFeibo(2)+getFeibo(1)执行完毕,将getFeibo(3)移除
  5. getFeibo(2)执行,将getFeibo(2)放入
  6. getFeibo(2)执行完毕,将getFeibo(2)移除
  7. getFeibo(4)执行完毕,将getFeibo(4)移除
  8. log(getFeibo(4))执行完毕,将log(getFeibo(4))移除

在这里插入图片描述
JS引擎永远执行的是执行栈的最顶部。

二、异步函数

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

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

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程:负责渲染页面 (js线程与GUI线程会相互等待,一者工作一者等待)
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信
    当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。
    JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

例1:

<div><button id="btn">点击</button></div><script>
    document.getElementById("btn").onclick=functionA(){//这里A只是后面好讲解
        console.log("按钮被点击了");};</script>
  1. 全局上下文
  2. getElementById执行,getElementById放入
  3. getElementById执行完毕,getElementById移除
    a.浏览器宿主 发现点击事件,将 点击函数A 放入事件队列
  4. 全局执行完毕,执行栈中已经没有了任何内容后
    b. 将 点击函数A 加入到执行栈中执行

例2:

console.log("a")setTimeout(()=>{
    console.log("b")},0);for(let i=0; i<1000; i++){
    console.log("c")};
  1. 全局上下文
  2. log(‘a’)执行,放入
  3. log(‘a’)完毕,移除
    a.浏览器宿主 发现计时线程,将 setTimeout 放入事件队列
  4. for循环事件执行,放入
  5. for循环事件完毕,移除
  6. 全局执行完毕,执行栈中已经没有了任何内容后
    b. 将 计时线程 加入到执行栈中执行

在这里插入图片描述

  1. 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
  2. 微任务(队列):MutationObserver,Promise产生的回调进入微队列
    当执行栈执行完毕后,会先在微队列中找需要执行的事件
let count=1;const ul= document.getElementById("container");
document.getElementById("btn").onclick=functionA(){setTimeout(functionC(){//宏队列
        console.log("添加了一个li")},0);var li= document.createElement("li")
    li.innerText= count++;
    ul.appendChild(li);};//监听ul,该函数就会进入微队列const observer=newMutationObserver(()=>{//当监听的dom元素发生变化时运行的回调函数
    console.log("ul元素发生了变化")});//监听ul
observer.observe(ul,{
    attributes:true,//监听属性的变化
    childList:true,//监听子元素的变化
    subtree:true//监听子树的变化(子元素的后代)});//不想监听后可以取消监听// observer.disconnect();

在这里插入图片描述

let count=1;const ul= document.getElementById("container");
document.getElementById("btn").onclick=functionA(){var li= document.createElement("li")
    li.innerText= count++;
    ul.appendChild(li);
    console.log("添加了一个li");};//监听ul,该函数就会进入微队列const observer=newMutationObserver(()=>{//当监听的dom元素发生变化时运行的回调函数
    console.log("ul元素发生了变化")});//监听ul
observer.observe(ul,{
    attributes:true,//监听属性的变化
    childList:true,//监听子元素的变化
    subtree:true//监听子树的变化(子元素的后代)});//不想监听后可以取消监听// observer.disconnect();

在这里插入图片描述

  • 作者:飞羽逐星
  • 原文链接:https://blog.csdn.net/xun__xing/article/details/107898428
    更新时间:2022-10-19 12:35:34