08月08, 2022

JS进阶(10)--Promise(1)--执行栈与事件循环初识

执行栈与事件循环初识

执行栈

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

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

看一下下面简单代码的执行过程:

function b(){
}
function a(){
  b();
}
a();

首先,全局执行上下文Global Execution Context会被建立,这时候会一并建立thisglobal object (window),在函数开始执行的过程中,function afunction b由于JS提升机制的缘故会先被建立在内存中,接着才会开始逐行执行函数。

接着,代码会执行到a()这个部分,这时候,会建立a的执行上下文(execution context),并且被放置到执行栈(execution stack)中。在这个execution stack中,最上面的execution context会是正在被执行的a()。如下图:

function a()execution context建立后,便会开始执行function a中的内容。由于在function a() 里面有去执行function b() ,因此,在这个execution stack中,接下来最上面会变成function b()execution context。如下图:

function b() 执行完之后,会从execution stack中离开,继续逐行执行function a。当function a 执行完之后,一样会从execution stack中抽离,再回到Global Execution Context逐行执行。如下图:

function b() 执行完之后,会从execution stack中离开

继续逐行执行function a()。当function a()执行完之后,一样会从execution stack中抽离,再回到Global Execution Context逐行执行。如下图:

异步函数

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

下面代码的执行结果是什么?

console.log("a")

setTimeout(() => {
    console.log("b")
}, 0);

for (let i = 0; i < 1000; i++) {
    console.log("c")
}

浏览器内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS )、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript 引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步 http 请求线程(网络线程)

GUI 渲染线程

  • 主要负责页面的渲染,解析 HTMLCSS,构建 DOM 树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
  • 该线程与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,主线程才会去执行 GUI 渲染。

JavaScript 引擎线程

  • 该线程当然是主要负责处理 JavaScript 脚本,执行代码。
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行。
  • 当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞。

定时触发器线程

  • 负责执行异步定时器一类的函数的线程,如:setTimeout、setInterval
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行。

事件触发线程

  • 主要负责将准备好的事件交给 JS 引擎线程执行。

比如 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行。

异步 http 请求线程

  • 负责执行异步请求一类的函数的线程,如:Promise、fetch、ajax 等。
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。

事件循环(轮询)

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

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

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

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

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

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

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <ul id="container"></ul>
    <button id="btn">点击</button>
    <script>
        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(function B() {
            //当监听的dom元素发生变化时运行的回调函数
            console.log("ul元素发生了变化")
        })
        //监听ul
        observer.observe(ul, {
            attributes: true, //监听属性的变化
            childList: true, //监听子元素的变化
            subtree: true //监听子树的变化
        })
        //取消监听
        // observer.disconnect();
    </script>
</body>

</html>

本文链接:http://www.yanhongzhi.com/post/js_ap_22.html

-- EOF --

Comments