一个打印问题引发的思考(js中变量创建的流程以及引发的错误)

1.问题的引发

最近在学python的时候发现了一个打印问题

python 复制代码
x = 1
def fun1():
    def fun2():
        print(x)
    fun2()
    x = 2
fun1()

问: 以上代码中输出什么?

你是不是以为会打印1? 结果是什么都不会打印,而是会报错

2. 原因

后来我翻阅了一些资料以及一些大佬的讲解,明白了问题所在

  1. 这里的fun1函数在定义的时候,内部就已经判断有一个x变量了,所以就不会引用全局作用域中的x变量,这里注意是fun1函数在定义时判断的内部存在x变量,说明函数进行初始化的时候就进行了此操作
  2. 然后执行fun1函数,继续执行fun2函数,等到执行fun2函数的时候,打印当前局部作用域的x,上面说过,在fun1函数初始化的时候,判断内部有个x变量,所以默认局部作用域有x,所以fun2的x不会去全局作用域获取,而是从局部作用域获取,此时问题就出现了,因为x仅仅只是在初始化的时候认为存在,但是当fun2函数执行时,并没有被赋值,所以就会产生报错

3. 引发思考

python中存在这个问题得到的答案,然后我就想去试试javascript中执行类似的代码会打印出什么

js 复制代码
let x = 1
function a() {
  function b() {
    console.log(x)
  }
  b()
  let x = 2
}
a()

结果也是相同的,也是直接报错,起初我以为是let不存在变量提升的问题,于是换了var声明变量,但是结果仍旧是一样的

4. 继续查找原因

我在博客论坛中找到一些相关文章,整理后发现,很多文章都在讲述一个点,就是let与const真的时严格意义上的不存在变量提升吗?

大概的意思就是,js的变量声明都会经历三个步骤,分别是

  1. 创建变量
  2. 初始化变量
  3. 赋值变量

正常的变量提升会将第一步与第二步提升到当前作用域的顶层,默认会优先执行,而let与const虽然没有变量提升,但是依旧会将第一步的创建变量提升上去,虽然看起来像是变量提升,但是严格意义上来说,这与python类似,在函数定义的时候进行的初始化操作

有了以上知识做铺垫,我又去ECMAScript官网查阅了一下文档,找到了这个问题出现的根本原因,具体可见此链接

在官网的14.3.1节讲述了let与const声明变量的流程,以下是原文

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

翻译过来就是:

let和const声明定义了在运行执行上下文的LexicalEnvironment范围内的变量.变量是在实例化其包含的词法环境时创建的,但在评估变量的词法绑定之前,不能以任何方式访问这些变量.当计算LexicalBinding时,而不是在创建变量时,为由具有初始化器的LexicalBinding定义的变量分配其初始化器AssignmentExpression的值.如果let声明中的LexicalBinding没有初始化器,则在计算LexicalBinding时,该变量将被赋予未定义的值。

5. 结论

  1. 根据以上的解释,我们大概知道了,var会将步骤1:创建变量与步骤2:初始化变量提升到当前作用域的最上方,而let与const会因为语法的特性,不存在变量提升,但是会优先定义了在执行上下文中的变量(也就是优先执行了步骤1:创建变量与步骤)
  2. 根据以上所有的解释,最终可以得到,虽然在a函数内部中声明x变量在调用b变量之后,但是x变量的创建会被js初始化函数的时候提升到当前作用域的最上方,虽然不会像var一样提升创建变量与初始化变量两个步骤,但是会因为函数初始化而执行步骤1:创建变量与步骤
  3. 所以b函数执行的时候,获取到当前局部作用域中存在变量x,就不会去全局作用域中查找,但是这个x在在评估变量的词法绑定之前,不能以任何形式访问变量,也就是说x变量在剩下的两个步骤(初始化变量与赋值变量)完成之前(在完成之前这个变量也被称为 暂时性死区(TDZ)),是不能够访问的,所以就会报错!

以上就是本篇文章的所有分享内容了,存在疑问或者其他声音的想法欢迎在评论区讨论😁

相关推荐
_柳青杨3 小时前
深入理解 JavaScript 事件循环
前端·javascript
大家的林语冰8 小时前
ES5 凉凉,Babel 8 正式发布,默认不再编译为 ES5 和 CJS......
前端·javascript·前端工程化
weedsfly10 小时前
异步编程全景与事件循环——彻底搞懂 JS 执行机制
前端·javascript
用户17335980753710 小时前
纯前端 PDF 数字签名实战:Vue 3 + pdf-lib 在浏览器里完成签名嵌入
前端·javascript
JieE21221 小时前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE21221 小时前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
kyriewen1 天前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
Larcher1 天前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式
默_笙1 天前
🃏 JS 只有 8 种数据类型,但我花了 2 天才搞懂 null 和 undefined 的区别
javascript
jump_jump1 天前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化