Consolidate the Foundation Part 1 - Graphical JavaScript Execution Mechanism

Get into the habit of writing together! This is the third day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event

foreword

It is not easy to talk about the basics. This article hopes to learn or review the JavaScript execution mechanism with you through 9 demos and 18 pictures. The outline of this article:

  1. what is hoisting
  2. How a piece of JavaScript code is executed
  3. what is the call stack

There is a summary at the end of the article .

If you have any questions about this article or find any mistakes or omissions, you can leave a message in the comment area~

This article is the first part of the solid foundation series, please pay attention to the next part~

If it helps you, I hope Sanlian~

what is hoisting

Let's start with a summary picture and be shocked~

image.png

The text begins~

Question: What does the following code print? Why?

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

the answer is:

image.pngThe showSinger function executes normally, the first print of the singer variable is undefined, and the second print of the singer variable is Jaychou. It seems that the var variable singer and the function declaration showSinger are promoted, like the following simulation:

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

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

In JavaScript, this phenomenon is called hoisting : var-declared variables are hoisted and function declarations are hoisted, and are added to the top of the execution context before executing the code.

Details about the boost

  1. let variables and const variables are not hoisted and can only be used after the variable is declared. Before the declaration, it is called a "temporary dead zone". The following code will report an error:
    console.log('打印:', singer)
    let singer = 'Jaychou'
复制代码

image.png2. The var variable declared in the global execution context will become the property of the window object, and the let variable and const variable will not.

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

    let age = 40
    console.log(window.age) // undefined
复制代码
  1. var declarations are function scoped, let declarations and const declarations are block scoped
    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 does not allow redundant declarations in the same block scope and will report an error, while var declarations allow duplicate declarations
    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. Function declarations are hoisted, function expressions are not (the two syntaxes are equivalent, except for the difference when the function is actually defined)
    // 没有问题,因为 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;
    };
复制代码

When did the lift happen

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

这就引出了下面这个问题:一段 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,翻译成机器能够理解的机器语言,大致流程是:

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

编译阶段

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

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

image.png

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

image.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
    }
复制代码

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

image.png

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

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

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

image.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)
复制代码

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

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

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

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

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

image.pngWhen the add function returns, the execution context of the function is popped from the top of the stack, and the value of result is set to the return value of the add function, which is 9

image.pngAfter addAll performs the last addition operation and returns, the execution context of addAll is also popped from the top of the stack, and only the global context is left in the call stack.

image.pngThis is the process that the call stack goes through~

In the usual development process, you can see the Call Stack call stack by breaking the debugging of the breakpoint, such as a breakpoint in the add function just now:

image.png

Summarize

image.pngThis article connects declaration promotion, JavaScript compilation and execution, and call stack to describe the JavaScript execution mechanism, hoping to help everyone~

This article is the first part of the solid foundation series, and previews the middle part of the codeword: scope chain + closure + this.

Guess you like

Origin juejin.im/post/7084508382337433637