js事件循环原理

一、执行栈

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

function a() {
    console.log("a")
    b();
}
function b() {
    console.log("b");
    c();
}
function c() {
    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移除

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

function getFeibo(n) {
    if (n === 1 || n === 2) {
        return 1;
    }
    return getFeibo(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 = function A() {//这里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 = function A() {
    setTimeout(function C() {//宏队列
        console.log("添加了一个li")
    }, 0);
    var li = document.createElement("li")
    li.innerText = count++;
    ul.appendChild(li);
};
//监听ul,该函数就会进入微队列
const observer = new MutationObserver(()=> {
    //当监听的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 = function A() {
    var li = document.createElement("li")
    li.innerText = count++;
    ul.appendChild(li);
    console.log("添加了一个li");
};
//监听ul,该函数就会进入微队列
const observer = new MutationObserver(()=> {
    //当监听的dom元素发生变化时运行的回调函数
    console.log("ul元素发生了变化")
});
//监听ul
observer.observe(ul, {
    attributes: true, //监听属性的变化
    childList: true, //监听子元素的变化
    subtree: true //监听子树的变化(子元素的后代)
});
//不想监听后可以取消监听
// observer.disconnect();

在这里插入图片描述

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页