属性值是变量时獲取json值為undefined_一篇文章看懂变量提升(hoisting)

f64525b670e764a8e208851865581e3b.png

前沿

大家对变量提升(hoisting)一定不陌生了,这篇文章希望可以建立一个系统的规则,以后碰到变量提升,可以重新看这篇文章,根据文中的规则找到答案。最终,我们可以记住这个规则,很自然的理解变量提升。


例子

相信大家对这个例子不陌生了:

a = 2;

var a;

console.log( a ); /// 2

为什么这段话a 在声明之前就可以赋值了呢?

再看下这个例子:

console.log( a );  //undefined

var a = 2;

为什么这个又是输出undefined呢?

别担心,看完整个文章你就会知道运用怎样的规则来推断结果了。


作用域浅析

在这里我们对作用域不做详细的讲解,作用域可以想象成可访问对象的集合

打个通俗的比方,现在有个公寓管理公司,底下有多个公寓。高级管理员A, 他有所有公寓所有房间的钥匙,所以他能打开所有公寓所有房间,并能拿出所有房间的物品。中级管理员B,只有某一栋公寓所有房间的钥匙,他能拿出所有此公寓所有房间的物品,但是不能拿出其他公寓的物品。原因就是管理员B在拿物品时(程序执行代码时), 并没用其他公寓的信息(其他作用域内信息)。

37fbbd904c8631c83f6cccee9ac57f46.png
为了简单,本篇文章只涉及同一个作用域下,不同作用域的交互之后几篇会讲到

大家可以想像成进入到全局作用域或者某个函数作用域时,引擎会产生一个json object 当作资料库, 之后代码执行的时候会从这个json 中找值。

var pseudoContext = {

}

JavaScript解析器

一般来说,大家可能觉得JavaScript解析器会在 run-time 运行时一行一行的来解析代码。

事实上当解释器到达一个作用域后,会先编译代码,然后再一行一行解析。

当JavaScript引擎运行到某个作用域后 ( 在第一个例子中,作用域是 global 全局作用域 )

它会有两个步骤

1. 初始化阶段 ( Creation Stage) [当进入一个作用域,逐行运行代码之前]

+ 创建 var 变量, function 函数和函数的arguments 参数

2. 代码执行阶段 (Activation/Code Execution Stage)

+ 给变量和函数赋值,以及执行代码


初始化阶段 ( Creation Stage)
在初始化阶段,进入一个作用域时会发生:
1. 如果作用域是函数内部,把函数参数放进前面的 context json
2. 扫描当前作用域寻找函数:
+ 每发现一个函数,就把名字和函数指针放进前面的json中
+ 如果函数名已经存在,覆盖之前的函数指针 3. 扫描当前作用域寻找变量:
+ 每发现一个变量 var,就把名字放进前面的 json中,并把值设>成 undefined
+ 如果变量名已经存在,不会覆盖,忽略然后继续扫描 代码执行阶段 (Activation/Code Execution Stage)
逐行执行代码,并且赋值之前为 undefined的变量 var

例子1

我们回到之前的例子:

a = 2;

var a;

console.log( a ); /// 2

我们把之前讲的规则拿来套用:

  1. 首先进入全局作用域,初始化一个空的模拟作用域 json
  2. 逐步执行代码之前,执行初始化阶段
    1. 作用域不是函数内部,没用函数参数,忽略
    2. 扫描未发现函数,忽略
    3. 扫描发现变量var a, 放进json里并设置成undefined
  3. 此时我们的 json
pseudoContext = {

      a = undefined
}
a = 2

4.扫描完成,逐行执行代码

a = 2

5.扫描我们的作用域,发现pseudoContext里存在a,赋值成2

pseudoContext = {
      a = 2
}

6.下一步:

console.log( a );

7.在作用域pseudoContext 中找到 a, 发现有值,输出2


例子2

console.log( a );  //undefined

var a = 2;
  1. 首先进入全局作用域,初始化一个空的模拟作用域json
  2. 逐步执行代码之前,执行初始化阶段
    1. 作用域不是函数内部,没用函数参数,忽略
    2. 扫描未发现函数,忽略
    3. 扫描发现变量var a, 放进json里并设置成undefined
  3. 此时我们的json
pseudoContext = {
   variables: {
      a = undefined
   }
}

4.扫描完成,逐行执行代码

console.log( a );

jsonaundefined, 所以输出undefined


更复杂的例子3

console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined

var foo = 'hello',
    bar = function() {
        return 'world';
    };

function foo() {
        return 'hello';
}

1.首先进入全局作用域,初始化一个空的模拟作用域json

2.逐步执行代码之前,执行初始化阶段

3.作用域不是函数内部,没用函数参数,忽略

4.扫描发现函数foo(第9行), 声明并赋值

5.此时的 json:

pseudoContext = {
    foo = function pointer
} 

6.扫描发现变量var foo, 因为foo名字已经存在了,依照之前规则 "如果变量名已经存在,不会覆盖,忽略然后继续扫描", 忽略

7.扫描发现变量 var bar, 赋值成undefined引擎会先函数扫描,再变量扫描

8.此时我们的json

pseudoContext = {
    foo = function pointer,
    bar = undefined
}

扫描完成,逐行执行代码

console.log(typeof foo);

jsonfoofunction pointer,返回function

下一步:

console.log(typeof bar);

jsonbarundefined,输出undefined


letconst

希望以上讲的大家都能理解,再来说说letconst,这两个和var不同 他们在所谓的 时间静止区 temporal dead zone (TDZ)(不知道谁取的这么中二的名字)。

  • 当进入一个作用域时,我们不会把它加在我们json
  • 一开始get或者set的时候就会报错 ReferenceError
  • 逐行执行时,如果有let const声明,就会在作用域json里创建,如果赋值了,就会在作用域json里赋值
b   // Uncaught ReferenceError: b is not defined
let b =2

总结

希望大家看了这篇文章对变量提升有更深的理解,变量提升只是表象,只是一个js解析器和作用域共同产生的一个结果。之后会更加详细的讲解一下作用域


参考:

What is the Execution Context & Stack in JavaScript? by David Shariff

You-Dont-Know-JS/ch4.md at master · getify/You-Dont-Know-JS · GitHub

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值