文章转自:一篇文章看懂变量提升(hoisting) - 知乎

前沿

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


例子

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

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

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

再看下这个例子:

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

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

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


作用域浅析

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

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

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

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

jsonfoo 是function pointer,返回function

下一步:

console.log(typeof bar);

jsonbarundefined,输出undefined


let 和 const

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

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

总结

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

1 : 提高性能
因为在代码执行会进行一次代码检查和预编译(只进行一次),这样就不需要在函数变量每次执行的时候都再进行一次代码检查和重新解析,
2: 容错了提升

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

例如这样的一段代码,正是因为变量的提示在执行时就不会进行报错。

一篇文章看懂变量提升(hoisting)相关推荐

  1. 一篇文章看懂:量化交易

    什么是量化交易? 度娘官方版 - 理论这么说 量化交易是指以先进的数学模型替代人为的主观判断,利用计算机技术从庞大的历史数据中海选能带来超额收益的多种"大概率"事件以制定策略,极大 ...

  2. 一篇文章看懂@Scheduled定时器/@Async/CompletableFuture

    一篇文章看懂@Scheduled定时器/@Async/CompletableFuture @Scheduled注解解析: 1.cron:最重要的一个参数 cron表达式[秒] [分] [小时] [日] ...

  3. c++ socket线程池原理_一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题...

    编辑:业余草来源:https://www.xttblog.com/?p=4946 一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题. ThreadLocal 相信不 ...

  4. 一篇文章看懂MySQL的多表连接(包含左/右/全外连接)

    MySQL的多表查询 这是第二次学习多表查询,关于左右连接还是不是很熟悉,因此重新看一下.小目标:一篇文章看懂多表查询!! 这篇博客是跟着宋红康老师学习的,点击此处查看视频,关于数据库我放在了Gith ...

  5. 一篇文章看懂makefile编写规则

    该篇文章为转载,是对原作者系列文章的总汇加上标注. 支持原创,请移步陈浩大神博客:(最原始版本) http://blog.csdn.net/haoel/article/details/2886 我转自 ...

  6. Java读取excel的方式,一篇文章看懂(详细)

    目录 一.excel读取的两种方式 1.1 jxl 和 poi 的区别和选择 二.jxl 的使用 2.1 导入相关依赖 2.2 操作 三.poi 的使用 3.1 导入相关依赖 3.2 操作 四.总结 ...

  7. 一篇文章看懂函数式编程与命令式编程

    文章目录 1 历史来源 2 编程范式 3 函数式编程的崛起 4 函数式编程 4.1 函数 4.2 纯函数 4.3 变量与表达式 4.5 函数与方法 4.6 状态 4.7 函数式编程的特性 4.7.1 ...

  8. 一篇文章看懂ADAS

    本文是DR数据复兴原创小课堂,每周二.四你可以在DR数据复兴公众号看到这个连载(除特别情况外). DR数据复兴是由上海微技术工业研究院建立推广的传感器•物联网•大数据创新创业平台,为创业者和投资者提供 ...

  9. 一篇文章看懂select_poll_epoll

    书上还有各种博客介绍select.poll.epoll基本上都是一上来就介绍数据结构,参数等等,这对新手很不友好啊,于是看到了知乎大神的帖子:https://www.zhihu.com/questio ...

最新文章

  1. 在Asp.Net中从sqlserver检索(retrieve)图片
  2. JSP网页开发安装2019-03 eclipse,详细并且简单教程这里有。
  3. android 自定义折线图
  4. ACdream 1224 Robbers (贪心)
  5. queue:queue清空的方法?
  6. 【web安全】Web应用隔离防护之Web弱口令爆破
  7. PostgreSQL 数据库备份
  8. 【Linux】一步一步学Linux——gzip命令(63)
  9. SAP云平台和第三方CRM解决方案(火锅)互联
  10. 读书笔记--SQL必知必会03--排序检索数据
  11. 51nod1712 区间求和
  12. C++程序注册Dll
  13. 资深SRE带你看阿里云香港故障
  14. 年份必须是4位数 C语言,输入年份(四位数),判断是否是闰年 C语言编程
  15. zabbix之3触发器/action及模板
  16. background系列属性(background-color背景颜色、background-image背景图片、background-repeat重复方式以及background-position)
  17. 如何写一个video播放器的demo?
  18. Attention Mechanisms in Computer Vision: A Survey(一)
  19. GigE发现设备——IP Configuration和Device Enumeration
  20. linux交换分区的文件格式为,linux利用交换分区空间类提供虚拟内存,交换分区的文件系统类型必须是() (5.0分)...

热门文章

  1. 西部数码 php版本,西部数码云主机如何安装PHP版本
  2. 关于Invalid bound statement和Error creating bean with name 'xxx'错误问题全收录
  3. java集合框架中抽象有序列表的接口是_JAVA基础接口集合框架
  4. 冯洛伊曼计算机工作原理的设计思想是,冯诺依曼计算机工作原理的设计思想是...
  5. java 发送tr069报文_TR069报文交互
  6. Bootstrap 创建表格通讯录
  7. SQL必知必会(第五版)
  8. 观看 比尔盖茨及梅琳达在斯坦福2014届毕业生毕业典礼上的演讲有感
  9. c#.net SQL存储过程直接传表;用户定义表类型的表
  10. 进入瓶颈期,没有方向