Consolidar la base, parte 1: mecanismo gráfico de ejecución de JavaScript

¡Acostúmbrate a escribir juntos! Este es el tercer día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento

prefacio

No es fácil hablar de los conceptos básicos. Este artículo espera aprender o revisar el mecanismo de ejecución de JavaScript con usted a través de 9 demostraciones y 18 imágenes. El esquema de este artículo:

  1. que es izar
  2. Cómo se ejecuta una pieza de código JavaScript
  3. ¿Qué es la pila de llamadas?

Hay un resumen al final del artículo .

Si tiene alguna pregunta sobre este artículo o encuentra algún error u omisión, puede dejar un mensaje en el área de comentarios ~

Este artículo es la primera parte de la serie de bases sólidas, preste atención a la siguiente parte ~

Si te ayuda, espero Sanlian~

que es izar

Comencemos con una imagen de resumen y sorpréndase ~

imagen.png

El texto comienza ~

Pregunta: ¿Qué imprime el siguiente código? ¿Por qué?

    showSinger()
    console.log('第1次打印:', singer)
    var singer = 'Jaychou'
    console.log('第2次打印:', singer)
    function showSinger() { 
      console.log('showSinger函数')
    }
复制代码

la respuesta es:

imagen.pngLa función showSinger se ejecuta normalmente, la primera impresión de la variable cantante no está definida, y la segunda impresión de la variable cantante es Jaychou, parece que la variable var cantante y la declaración de la función showSinger se promueven, como la siguiente simulación:

    // 函数声明被提升了
    function showSinger() { 
      console.log('showSinger函数')
    }
    // var 变量被提升了
    var singer = undefined

    showSinger()
    console.log('第1次打印:', singer) // undefined
    singer = 'Jaychou'
    console.log('第2次打印:', singer) // Jaychou
复制代码

En JavaScript, este fenómeno se denomina elevación : las variables declaradas por var se elevan y las declaraciones de función se elevan, y se agregan a la parte superior del contexto de ejecución antes de ejecutar el código.

Detalles sobre el impulso

  1. Las variables let y const no se elevan y solo se pueden usar después de declarar la variable. Antes de la declaración, se denomina "zona muerta temporal". El siguiente código informará un error:
    console.log('打印:', singer)
    let singer = 'Jaychou'
复制代码

imagen.png2. La variable var declarada en el contexto de ejecución global se convertirá en propiedad del objeto ventana, y la variable let y la variable const no.

    var singer = 'Jaychou'
    console.log(window.singer) // Jaychou

    let age = 40
    console.log(window.age) // undefined
复制代码
  1. Las declaraciones var tienen un alcance de función, las declaraciones let y las declaraciones const tienen un alcance de bloque
    if (true) {
      var singer = 'Jaychou'
      console.log(singer) // Jaychou
    }
    console.log(singer) // Jaychou

    if (true) {
      let age = 40
      console.log(age) // 40
    }
    // 报错:Uncaught ReferenceError: age is not defined
    console.log(age);
复制代码
  1. let no permite declaraciones redundantes en el mismo ámbito de bloque e informará un error, mientras que las declaraciones var permiten declaraciones duplicadas
    let age;
    // Uncaught SyntaxError: Identifier 'age' has already been declared
    var age;
    
    var age = 10
    var age = 20
    var age = 30
    console.log(age) // 正常打印 30
复制代码
  1. Las declaraciones de funciones se elevan, las expresiones de funciones no (las dos sintaxis son equivalentes, excepto por la diferencia cuando la función está realmente definida)
    // 没有问题,因为 sum 函数有被提升
    console.log(sum(10, 10)); 
    // 函数声明
    function sum(num1, num2) {
      return num1 + num2;
    } 

    // 会报错: Uncaught TypeError: sum1 is not a function
    // 因为 sum1 函数不会被提升
    console.log(sum1(10, 10));
    // 函数表达式
    var sum1 = function (num1, num2) {
      return num1 + num2;
    };
复制代码

¿Cuándo ocurrió el ascensor?

都在说提升,那这个步骤是发生在什么时候?执行代码之前吗?

这就引出了下面这个问题:一段 JavaScript 代码是怎样被执行的?

一段 JavaScript 代码是怎样被执行的

提问环节:下面的 html 里的 JavaScript 代码是怎样被执行的?

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    showSinger()
    var singer = 'Jaychou'
    console.log(singer)

    function showSinger() {
      console.log('showSinger函数')
    }
  </script>
</body>
</html>
复制代码

简述:html 和 css 部分会被浏览器的渲染引擎拿来渲染,进行计算dom树、计算style、计算布局、计算分层、计算绘制等等一系列的渲染操作,而 JavaScript 代码的执行由 JavaScript 引擎负责。

市面上的 JavaScript 引擎有很多,例如 SpiderMonkey、V8、JavaScriptCore 等,可以简单理解成 JavaScript 引擎将人类能够理解的编程语言 JavaScript,翻译成机器能够理解的机器语言,大致流程是:

imagen.png 是的,在执行之前,会有编译阶段,而不是直接就执行了。

编译阶段

输入一段代码,经过编译后,会生成两部分内容:执行上下文和可执行代码,执行上下文就是刚才提的那个执行上下文,它是执行一段 JavaScript 代码时的运行环境。

imagen.png 执行上下文具体的分类和对应的创建时机如下:

imagen.png

而执行上下文具体包括什么内容,怎样存放刚才提的 var 声明变量、函数声明、以及 let 和 const 声明变量请看:

imagen.png

执行上下文案例分析

结合下面的案例来具体分析:

    var a = 1 // var 声明
    let b = 2 // let 声明
    { 
      let b = 3 // let 声明
      var c = 4 // var 声明
      let d = 5 // let 声明
      console.log(a)
      console.log(b) 
    } 
    console.log(b) 
    console.log(c)
    // 函数声明
    function add(num1, num2){
      return num1 + num2
    }
复制代码

第一步是编译上面的全局代码,并创建全局执行上下文:

imagen.png

  • var 声明的变量在编译阶段放到了变量环境,例如 a 和 c;
  • 函数声明在编译阶段放到了变量环境,例如 add 函数;
  • let 声明的变量在编译阶段放到了词法环境,例如 b(不包括其内部的块作用域)
  • 内部的块作用域的 let 声明还没做处理

接下来是执行代码,执行代码到块{}里面时,a 已被设置成 1,b 已被设置成 2,块作用域里的 b 和 d 作为新的一层放在词法环境里 imagen.png 词法环境内部的小型栈结构,栈底是函数最外层的变量,进入一个块作用域后,就把该块作用域内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出。

继续执行,执行到console.log(a);console.log(b);时,进入变量查找过程:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找,所以块作用域里面的 b 会找到 3: imagen.png 当块作用域执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出,最终执行上下文如下:

imagen.png

这个过程不清楚的同学可以多看几次案例,有不明白的可以在评论区讨论~

调用栈是什么

刚才聊到,函数执行上下文的创建时机在函数被调用时,它的过程是取出函数体的代码 》对这段代码进行编译 》创建该函数的执行上下文和可执行代码 》执行代码输出结果,其中编译和创建执行上下文的过程和刚才演示的对全局代码的处理类似。

而调用栈就是用来管理函数调用关系的一种数据结构,在执行上下文创建好后,JavaScript 引擎会将执行上下文压入栈中。

调用栈案例分析

    var a = 2
    function add(b, c) {
      return b + c
    }
    function addAll(b, c) {
      var d = 10
      var result = add(b, c)
      return a + result + d
    }
    addAll(3, 6)
复制代码

第一步,创建全局上下文,并将其压入栈底

imagen.png 接下来执行代码,a = 2 把 a 从 undefined 设为 2

第二步,调用 addAll 函数,会编译该函数,并为其创建一个执行上下文,将该函数的执行上下文压入栈中

imagen.png 接下来执行 addAll 函数的代码,把 d 置为 10,然后执行 add 函数

第三步,调用 add 函数,为其创建执行上下文,并压入栈中

imagen.pngCuando la función de agregar regresa, el contexto de ejecución de la función se extrae de la parte superior de la pila y el valor de result se establece en el valor de retorno de la función de agregar, que es 9

imagen.pngDespués de que addAll realiza la última operación de suma y regresa, el contexto de ejecución de addAll también se extrae de la parte superior de la pila y solo queda el contexto global en la pila de llamadas.

imagen.pngEste es el proceso por el que pasa la pila de llamadas~

En el proceso de desarrollo habitual, puede ver la pila de llamadas de la pila de llamadas interrumpiendo la depuración del punto de interrupción, como un punto de interrupción en la función de agregar en este momento:

imagen.png

Resumir

imagen.pngEste artículo conecta la promoción de declaraciones, la compilación y ejecución de JavaScript y la pila de llamadas para describir el mecanismo de ejecución de JavaScript, con la esperanza de ayudar a todos ~

Este artículo es la primera parte de la serie de fundamentos sólidos y presenta una vista previa de la parte central de la palabra clave: cadena de alcance + cierre + esto.

Supongo que te gusta

Origin juejin.im/post/7084508382337433637
Recomendado
Clasificación