hoisting怎么读(hoisted怎么读)

前沿

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

例子

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

a = 2;var a;console.log( a ); /// 2

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

再看下这个例子:

console.log( a ); //undefinedvar a = 2;

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

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

作用域浅析

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

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

hoisting怎么读(hoisted怎么读)

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

大家可以想像成进入到全局作用域或者某个函数作用域时。引擎会产生一个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

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

首先进入全局作用域。初始化一个空的模拟作用域 json逐步执行代码之前。执行初始化阶段作用域不是函数内部。没用函数参数。忽略扫描未发现函数。忽略扫描发现变量var a。 放进json里并设置成undefined此时我们的 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 ); //undefinedvar a = 2;

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

pseudoContext = { variables: { a = undefined }}

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

console.log( a );

json里a是undefined。 所以输出undefined

更复杂的例子3

console.log(typeof foo); // function pointerconsole.log(typeof bar); // undefinedvar 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);

json里foo 是function pointer。返回function

下一步:

console.log(typeof bar);

json里bar是undefined。输出undefined

let 和 const

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

当进入一个作用域时。我们不会把它加在我们json里一开始get或者set的时候就会报错 ReferenceError逐行执行时。如果有letconst声明。就会在作用域json里创建。如果赋值了。就会在作用域json里赋值

b // Uncaught ReferenceError: b is not definedlet b =2

总结

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

关注我的头条号。分享更多的技术学习文章。我自己是一名从事了多年开发的web前端老程序员。目前辞职在做自己的web前端私人定制课程。今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货。各种框架都有整理。送给每一位前端小伙伴。想要获取的可以关注我的头条号并在后台私信我:前端。即可免费获取。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平