• 技术文章 >web前端 >js教程

    深入浅析JavaScript中的执行上下文和执行机制

    青灯夜游青灯夜游2022-03-30 20:48:27转载83
    本篇文章给大家介绍一下线程和进程,了解一下JavaScript中的执行上下文和执行机制,希望对大家有所帮助!

    关于js中的执行上下文、执行栈、执行机制(同步任务、异步任务、微任务、宏任务、事件循环)在面试中是一个高频考点,有些小伙伴被问到时可能会一脸茫然不知所措,所以笔者今天就来总结下,希望可以对屏幕前的你有所帮助。【相关推荐:javascript学习教程

    线程和进程

    js中的执行上下文和js执行机制之前我们来说说线程和进程

    什么是线程

    用官方的话术来说 线程CPU调度的最小单位。

    什么是进程

    用官方的话术来说 进程CPU资源分配的最小单位。

    线程和进程的关系

    线程是建立在进程的基础上的一次程序运行单位,通俗点解释线程就是程序中的一个执行流,一个进程可以有一个或多个线程

    一个进程中只有一个执行流称作单线程,即程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。

    一个进程中有多个执行流称作多线程,即在一个程序中可以同时运行多个不同的线程来执行不同的任务, 也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

    下面笔者举一个简单的例子,比如我们打开qq音乐听歌,qq音乐就可以理解为一个进程,在qq音乐中我们可以边听歌边下载这里就是多线程,听歌是一个线程,下载是一个线程。如果我们再打开vscode来写代码这就是另外一个进程了。

    进程之间相互独立,但同一进程下的各个线程间有些资源是共享的。

    线程的生命周期

    线程的生命周期会经历五个阶段。

    1.png

    js是单线程还是多线程呢

    JS是单线程。JS 作为浏览器脚本语言其主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    执行上下文和执行栈

    什么是执行上下文

    JS 引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 “准备工作” ,就叫做 "执行上下文(execution context 简称 EC)" 或者也可以叫做执行环境

    执行上下文分类

    javascript 中有三种执行上下文类型,分别是:

    什么是执行栈?

    前面我们说到js在运行的时候会创建执行上下文,但是执行上下文是需要存储的,那用什么来存储呢?就需要用到栈数据结构了。

    栈是一种先进后出的数据结构。

    2.png

    所以总结来说用来存储代码运行时创建的执行上下文就是执行栈

    js执行流程

    在执行一段代码时,JS 引擎会首先创建一个执行栈,用来存放执行上下文。

    然后 JS 引擎会创建一个全局执行上下文,并 push 到执行栈中, 这个过程 JS 引擎会为这段代码中所有变量分配内存并赋一个初始值(undefined),在创建完成后,JS 引擎会进入执行阶段,这个过程 JS 引擎会逐行的执行代码,即为之前分配好内存的变量逐个赋值(真实值)。

    如果这段代码中存在 function 的调用,那么 JS 引擎会创建一个函数执行上下文,并 push 到执行栈中,其创建和执行过程跟全局执行上下文一样。

    当一个执行栈执行完毕后该执行上下文就会从栈中弹出,接下来会进入下一个执行上下文。

    下面笔者来举个例子,假如在我们的程序中有如下代码

    console.log("Global Execution Context start");
    
    function first() {
      console.log("first function");
      second();
      console.log("Again first function");
    }
    
    function second() {
      console.log("second function");
    }
    
    first();
    console.log("Global Execution Context end");

    上面的例子我们简单来分析下

    我们用一张图来总结

    3.png

    好了。说完执行上下文和执行栈我们再来说说js的执行机制

    执行机制

    说到js的执行机制,我们就需要了解js中同步任务和异步任务、宏任务和微任务了。

    同步任务和异步任务

    js中,任务分为同步任务和异步任务,那什么是同步任务什么是异步任务呢?

    同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

    异步任务指的是,不进入主线程、而进入"任务队列"的任务(任务队列中的任务与主线程并列执行),只有当主线程空闲了并且"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。由于是队列存储所以满足先进先出规则。常见的异步任务有我们的setIntervalsetTimeoutpromise.then等。

    事件循环

    前面介绍了同步任务和异步任务,下面我们来说说事件循环。

    上述过程会不断重复,也就是常说的 Event Loop(事件循环)

    我们用一张图来总结下

    4.png

    下面笔者简单来介绍个例子

    function test1() {
      console.log("log1");
    
      setTimeout(() => {
        console.log("setTimeout 1000");
      }, 1000);
    
      setTimeout(() => {
        console.log("setTimeout 100");
      }, 100);
    
      console.log("log2");
    }
    
    test1(); // log1、log2、setTimeout 100、setTimeout 1000

    上面的例子比较简单,相信只要你看懂了上面笔者说的同步异步任务做出来是没什么问题的。那下面笔者再举一个例子小伙伴们看看会输出啥呢?

    function test2() {
      console.log("log1");
    
      setTimeout(() => {
        console.log("setTimeout 1000");
      }, 1000);
    
      setTimeout(() => {
        console.log("setTimeout 100");
      }, 100);
    
      new Promise((resolve, reject) => {
        console.log("new promise");
        resolve();
      }).then(() => {
        console.log("promise.then");
      });
    
      console.log("log2");
    }
    
    test2();

    要解决上面的问题光知道同步和异步任务是不够的,我们还得知道宏任务和微任务。

    宏任务和微任务

    js中,任务被分为两种,一种叫宏任务MacroTask,一种叫微任务MicroTask

    常见的宏任务MacroTask

    常见的微任务MicroTask

    所以在上面的例子中就涉及到宏任务和微任务了,那宏任务微任务的执行顺序是怎么样的呢?

    我们用一张图来总结下

    5.png

    读懂了异步里面的宏任务和微任务上面的例子我们就可以轻易的得到答案了。

    所以test2方法执行后会依次输出log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000

    关于js执行到底是先宏任务再微任务还是先微任务再宏任务网上的文章各有说辞。笔者的理解是如果把整个js代码块当做宏任务的时候我们的js执行顺序是先宏任务后微任务的。

    正所谓百看不如一练,下面笔者举两个例子如果你都能做对那你算是掌握了js执行机制这一块的知识了。

    例子1

    function test3() {
      console.log(1);
    
      setTimeout(function () {
        console.log(2);
        new Promise(function (resolve) {
          console.log(3);
          resolve();
        }).then(function () {
          console.log(4);
        });
        console.log(5);
      }, 1000);
    
      new Promise(function (resolve) {
        console.log(6);
        resolve();
      }).then(function () {
        console.log(7);
        setTimeout(function () {
          console.log(8);
        });
      });
    
      setTimeout(function () {
        console.log(9);
        new Promise(function (resolve) {
          console.log(10);
          resolve();
        }).then(function () {
          console.log(11);
        });
      }, 100);
    
      console.log(12);
    }
    
    test3();

    我们来具体分析下

    所以上面代码例子会依次输出1、6、12、7、8、9、10、11、2、3、5、4,小伙伴们是否做对了呢?

    例子2

    我们把上面的例子1稍作修改,引入asyncawait

    async function test4() {
      console.log(1);
    
      setTimeout(function () {
        console.log(2);
        new Promise(function (resolve) {
          console.log(3);
          resolve();
        }).then(function () {
          console.log(4);
        });
        console.log(5);
      }, 1000);
    
      new Promise(function (resolve) {
        console.log(6);
        resolve();
      }).then(function () {
        console.log(7);
        setTimeout(function () {
          console.log(8);
        });
      });
    
      const result = await async1();
      console.log(result);
    
      setTimeout(function () {
        console.log(9);
        new Promise(function (resolve) {
          console.log(10);
          resolve();
        }).then(function () {
          console.log(11);
        });
      }, 100);
    
      console.log(12);
    }
    
    async function async1() {
      console.log(13)
      return Promise.resolve("Promise.resolve");
    }
    
    test4();

    上面这里例子会输出什么呢?这里我们弄懂asyncawait题目就迎刃而解了。

    我们知道asyncawait其实是Promise的语法糖,这里我们只需要知道await后面就相当于Promise.then。所以上面的例子我们可以理解成如下代码

    function test4() {
      console.log(1);
    
      setTimeout(function () {
        console.log(2);
        new Promise(function (resolve) {
          console.log(3);
          resolve();
        }).then(function () {
          console.log(4);
        });
        console.log(5);
      }, 1000);
    
      new Promise(function (resolve) {
        console.log(6);
        resolve();
      }).then(function () {
        console.log(7);
        setTimeout(function () {
          console.log(8);
        });
      });
    
      new Promise(function (resolve) {
        console.log(13);
        return resolve("Promise.resolve");
      }).then((result) => {
        console.log(result);
    
        setTimeout(function () {
          console.log(9);
          new Promise(function (resolve) {
            console.log(10);
            resolve();
          }).then(function () {
            console.log(11);
          });
        }, 100);
    
        console.log(12);
      });
    }
    
    test4();

    看到上面的代码是不是就能轻易得出结果呢?

    所以上面代码例子会依次输出1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4,小伙伴们是否做对了呢?

    扩展

    setTimeout(fn, 0)

    关于setTimeout(fn)可能很多小伙伴还是不太理解,这不明明没设置延迟时间吗,不应该立即就执行吗?

    setTimeout(fn)我们可以理解成setTimeout(fn,0),其实是同一个意思。

    我们知道js分同步任务和异步任务,setTimeout(fn)就是属于异步任务,所以这里就算你没设置延迟时间,他也会进入异步队列,需要等到主线程空闲的时候才会执行。

    笔者这里再提一嘴,你觉得我们在setTimeout后面设置的延迟时间,js就一定会按我们的延迟时间执行吗,我觉得并不见得。我们设置的时间只是该回调函数可以被执行了,但是主线程有没有空还是另外一回事,我们可以举个简单的例子。

    function test5() {
      setTimeout(function () {
        console.log("setTimeout");
      }, 100);
    
      let i = 0;
      while (true) {
        i++;
      }
    }
    
    test5();

    上面的例子一定会在100毫秒后输出setTimeout吗,并不会,因为我们的主线程进入了死循环,并没有空去执行异步队列的任务。

    GUI渲染

    GUI渲染在这里说有些小伙伴可能不太理解,后面笔者会出关于浏览器的文章会再详细介绍,这里只是简单了解下即可。

    由于JS引擎线程GUI渲染线程是互斥的关系,浏览器为了能够使宏任务DOM任务有序的进行,会在一个宏任务执行结果后,在下一个宏任务执行前,GUI渲染线程开始工作,对页面进行渲染。

    所以宏任务、微任务、GUI渲染之间的关系如下

    宏任务 -> 微任务 -> GUI渲染 -> 宏任务 -> ...

    【相关视频教程推荐:web前端

    以上就是深入浅析JavaScript中的执行上下文和执行机制的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    上一篇:聊聊怎么利用Memoization提高React性能 下一篇:JavaScript实例详解之HTML元素操作
    PHP编程就业班

    相关文章推荐

    • 值得了解的几个实用JavaScript优化小技巧• JavaScript常见数组方法以及教你如何转置矩阵• javascript删除水平线的代码是什么• javascript怎么保留两位小数(不四舍五入)• 深入了解JavaScript引擎如何执行JS代码

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网