前言

讲基础不容易,本文希望通过 9 个 demo 和 18 张图,和大家一起学习或温故 JavaScript 执行机制,本文大纲:

  1. hoisting 是什么
  2. 一段 JavaScript 代码是怎样被执行的
  3. 调用栈是什么

文末有总结大图

如果对本文有什么疑问或发现什么错漏的地方,可在评论区留言~

本文是夯实基础系列的上篇,请关注接下来的中篇~

如果对你有帮助,希望三连~

hoisting 是什么

先来个总结图压压惊~

正文开始~

提问环节:下面这段代码打印什么?为什么?

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

答案是:


showSinger 函数正常执行,第 1 次打印 singer 变量是 undefined,第 2 次打印 singer 变量是Jaychou,看上去 var 变量 singer 和函数声明 showSinger 被提升了,像是下面的模拟:

    // 函数声明被提升了
function showSinger() {
console.log('showSinger函数')
}
// var 变量被提升了
var singer = undefined showSinger()
console.log('第1次打印:', singer) // undefined
singer = 'Jaychou'
console.log('第2次打印:', singer) // Jaychou

在 JavaScript 里,这种现象被称为 hoisting 提升:var 声明的变量会提升和函数声明会提升,在执行代码之前会先被添加到执行上下文的顶部。

关于提升的细节

  1. let 变量和 const 变量不会被提升,只能在声明变量之后才能使用,声明之前被称为“暂时性死区”,以下代码会报错:
    console.log('打印:', singer)
let singer = 'Jaychou'


2. 在全局执行上下文声明的 var 变量会成为 window 对象的属性,let 变量和 const 变量不会

    var singer = 'Jaychou'
console.log(window.singer) // Jaychou let age = 40
console.log(window.age) // undefined
  1. var 声明是函数作用域,let 声明和 const 声明是块作用域
    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 不允许同一个块作用域中出现冗余声明,会报错,var 声明则允许有重复声明
    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. 函数声明会被提升,函数表达式不会(除了函数什么时候真正有定义这个区别之外,这两种语法是等价的)
    // 没有问题,因为 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;
};

提升发生在什么时候

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

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


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

编译阶段

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


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

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

执行上下文案例分析

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

    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
}

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

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

接下来是执行代码,执行代码到块{}里面时,a 已被设置成 1,b 已被设置成 2,块作用域里的 b 和 d 作为新的一层放在词法环境里

词法环境内部的小型栈结构,栈底是函数最外层的变量,进入一个块作用域后,就把该块作用域内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出。

继续执行,执行到console.log(a);console.log(b);时,进入变量查找过程:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找,所以块作用域里面的 b 会找到 3:

当块作用域执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出,最终执行上下文如下:

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

调用栈是什么

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

而调用栈就是用来管理函数调用关系的一种数据结构,在执行上下文创建好后,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)

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


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

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


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

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


当 add 函数返回时,该函数的执行上下文就会从栈顶弹出,并将 result 的值设置为 add 函数的返回值,也就是 9


addAll 执行最后一个相加操作后并返回,addAll 的执行上下文也会从栈顶部弹出,此时调用栈中就只剩下全局上下文


这就是调用栈经历的过程~

而平时开发过程中,打断点调试就可以看到 Call Stack 调用栈了,比如刚才的 add 函数里打个断点:

总结


本文串联了声明提升、JavaScript 编译和执行、调用栈,来讲述 JavaScript 执行机制,希望有帮助到大家~

本文是夯实基础系列的上篇,预告正在码字的中篇:作用域链 + 闭包 + this。

夯实基础上篇-图解 JavaScript 执行机制的更多相关文章

  1. JavaScript 执行机制

    一.宏任务与微任务 macro-task(宏任务):包括整体代码script,setTimeout,setInterval micro-task(微任务):Promise,process.nextTi ...

  2. 转载---JavaScript执行机制

    很好的一篇文章,原地址 JavaScript执行机制 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不 ...

  3. 【THE LAST TIME】彻底吃透 JavaScript 执行机制

    前言 The last time, I have learned [THE LAST TIME]一直是我想写的一个系列,旨在厚积薄发,重温前端. 也是给自己的查缺补漏和技术分享. 欢迎大家多多评论指点 ...

  4. 探索JavaScript执行机制

    前言 不论是工作还是面试,我们可能都经常会碰到需要知道代码的执行顺序的场景,所以打算花点时间彻底搞懂JavaScript的执行机制. 如果这篇文章有帮助到你,️关注+点赞️鼓励一下作者,文章公众号首发 ...

  5. javascript执行机制

    文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的 ...

  6. 彻底弄懂 JavaScript 执行机制

    本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定 ...

  7. 【js】javaScript 执行机制

    javascript 是一门单线程语言(按照语句一行一行的执行) let a = '1'; console.log(a); let b = '2'; console.log(b); 这样子正常执行是没 ...

  8. 这一次,彻底弄懂 JavaScript 执行机制

    本文转自https://juejin.im/post/59e85eebf265da430d571f89#heading-4 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还 ...

  9. 0182 JavaScript执行机制:单线程,同步任务和异步任务,执行栈,消息队列,事件循环

    以下代码执行的结果是什么? [结果是1 2 3 ] console.log(1); setTimeout(function () { console.log(3); }, 1000); console ...

随机推荐

  1. Docker容器和虚拟机区别

    Docker .虚拟机之间区别 虚拟机技术的缺点: 1.资源占用太多 2.冗余步骤多 3.启动很慢 容器化技术 1.服务器资源利用率高 2.比较轻量化 3.打包镜像测试,一键运行 比较Docker和虚 ...

  2. 单链表上的一系列操作(基于c语言)

    单链表的实现分为两种单链表(其实差别并不是很大):带头结点和不带头结点,分别对应下面图中的上下两种. 链表的每一个结点是由两个域组成:数据域和指针域,分别存放所含数据和下一个结点的地址(这都是很明白的 ...

  3. RESTful API设计规范总结

    RESTful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计. 它的大原则容易把握,但是细节不容易做对.本文总结 RESTful 的设计细节,介绍如何设计出易于理解和使用的 API. ...

  4. C++设计模式 - 状态模式(State)

    状态变化模式 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?"状态变化"模式为这一问题提供了一种解决方案. 典型模式 Sta ...

  5. Sobel算子 Scharr算子 Laplacian算子

    图像梯度处理 Sobel算子 水平方向: 对于线条A和线条B,右侧像素值与左侧像素值的差值不为零,因此是边界 上下像素值差值为0,左右素值的差值不为零,分布为正负, 离的近的为2,离的远的为1 P5= ...

  6. Ubuntu下使用C语言连接Mysql 8.0客户端教程

    Ubuntu下如何C语言程序连接MYSQL 8.0(全教程) 1. 安装GCC(略) 2. 安装mysql(本人使用的是最新MySQL 8.0版本) sudo apt install mysql-cl ...

  7. 使用VS Code编译Marlin固件

    参考:https://marlinfw.org/docs/basics/install_platformio_vscode.html 前言 在阅读本文之前,您应该已经阅读了使用 PlatformIO ...

  8. url斜杠问题——重定向

    path('hello',hello), path('hello/',hello), 有什么区别? 没有斜杠:只能访问hello 有斜杠:可以访问hello和hello/ 分析有斜杠的: hello- ...

  9. 还在担心CC攻击? 让我们来了解它, 并尽可能将其拒之服务之外.

    还在担心CC攻击? 让我们来了解它, 并尽可能将其拒之服务之外. CC攻击是什么? 基本原理 CC原名为ChallengeCollapsar, 这种攻击通常是攻击者通过大量的代理机或者肉鸡给目标服务器 ...

  10. InnoDB中加锁?

    InnoDB 实现了两种类型的行锁,共享锁(S)与排他锁(X).然后由于 InnoDB引擎又支持表级锁,所以它内部又有意向共享锁(IS)与意向排他锁(IX).这两种表锁,都是InnoDB内部自动处理, ...