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

1.问题的引发

最近在学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中执行类似的代码会打印出什么

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与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)),是不能够访问的,所以就会报错!
以上就是本篇文章的所有分享内容了,存在疑问或者其他声音的想法欢迎在评论区讨论😁

相关推荐

  1. 一个SSE(流式)接口引发问题

    2024-07-20 19:14:05       50 阅读
  2. 【C++】循环语句引起循环引用问题

    2024-07-20 19:14:05       32 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-20 19:14:05       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-20 19:14:05       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-20 19:14:05       45 阅读
  4. Python语言-面向对象

    2024-07-20 19:14:05       55 阅读

热门阅读

  1. Python __init__与__new__的区别

    2024-07-20 19:14:05       13 阅读
  2. 深入探索Perl中的函数定义与调用机制

    2024-07-20 19:14:05       19 阅读
  3. lua语法思维导图

    2024-07-20 19:14:05       11 阅读
  4. Perl脚本的魔法:打造自定义文件系统视图

    2024-07-20 19:14:05       19 阅读
  5. nginx的docker-compose文件

    2024-07-20 19:14:05       15 阅读
  6. 蒙皮(Skinning)

    2024-07-20 19:14:05       17 阅读
  7. PS小技巧:在有褶皱的地方贴图 (300字畅享版)

    2024-07-20 19:14:05       15 阅读
  8. 恶补,正态分布

    2024-07-20 19:14:05       22 阅读