JavaScript中Hoisting详解 (变量提升与函数声明提升)


Posted in Javascript onAugust 18, 2017

本文主要给大家介绍了关于JavaScript中Hoisting(变量提升与函数声明提升)的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

如何将 函数声明 / 变量 “移动” 到作用域的顶部。

术语 Hoisting(提升) 在很多 JavaScript 博文中被用来解释标识符的解析。其实 Hoisting(提升) 这个词是用来解释 变量 和 函数声明 是如何被提升到 函数或全局 作用域顶部的。你在任何的 JavaScript 文档中找不到这个术语,我们说的 Hoisting(提升) 只是使用了其字面含义来做个比喻。

如果你已经对 JavaScript 作用域工作原理有基本的了解,那么更深入的了解 Hoisting(提升) 有助于你建立更强大的基础知识。(愚人码头注:作为 JavaScript 中的一个总要概念,变量提升和函数声明提升经常在前端开发面试时被问及,或者在前端开发笔试题中出现。可见了解 Hoisting(提升) 的重要性。)

为了更好地理解基础知识,让我们来回顾一下 “Hoisting(提升)” 到底意味着什么。另外,给你一个提醒,JavaScript 是一种解释性语言,这不同于编译性语言,这意味着JS代码是逐行执行的。

请考虑以下示例:

console.log(notyetdeclared);
// 打印 'undefined'
 
var notyetdeclared = 'now it is declared';
 
hoisting();
 
function hoisting(){
 console.log(notyetdeclared);
 // 打印 'undefined'
 
 var notyetdeclared = 'declared differently';
 
 console.log(notyetdeclared);
 // 打印 'declared differently'
}

在分析上面的示例代码之后,提出几个问题:

  • 第 6 行,该函数声明之前为何能访问?
  • 第 1 行,没有抛出错误,是因为这时变量 notyetdeclared 不存在吗?
  • 第 4 行,notyetdeclared 已经在全局作用域内声明了,为什么在第 9 行打印时还是  undefined 呢?

JavaScript 是非常合乎逻辑的,所有这些奇怪问题都有一个明确的解释。

我们从顶部开始解释,当代码在 JavaScript 中执行时,就会建立一个执行期上下文。 JavaScript 中有两种主要的执行期上下文类型 ? 全局执行期上下文和函数执行期上下文(愚人码头注:特别注意,执行期上下文和我们平常说的上下文不同,执行期上下文指的是作用域,而平常说的上下文是 this 的取值指向)。由于 JavaScript 是基于单线程执行模型,所以每次只能执行一段代码。

对于我们上面的代码,这个过程如图所示:

JavaScript中Hoisting详解 (变量提升与函数声明提升)

上述示例代码的调用栈:

  • 程序从栈(stack)上的全局执行期上下文开始执行。
  • 当调用 hoisting() 函数时,将一个新的函数执行期上下文推到栈(stack)上,并且全局执行期上下文被暂停。
  • 在 hoisting() 执行完成后 , hoisting()执行期上下文从栈(stack)中弹出,全局执行期上下文恢复。

这个过程是自解释的,但并没有真正解释我们在执行示例代码时所看到的异常。当执行期上下文跟踪代码的执行情况时,词法环境跟踪标识符到特定变量的映射。词法环境基本上是 JavaScript 作用域机制的内部实现。通常,词法环境与 JavaScript 代码的特定结构相关联,例如一个函数或一个 for 循环代码块。每当创建一个函数时,对其创建的词法环境的引用将在一个名为 [[Environment]] 的内部属性中传递。

所有这些术语涵盖的是一个简单而非常合乎逻辑的概念。允许将其分解。词法环境是一个有趣的名称,用于跟踪代码块中的变量和函数。除了跟踪局部变量、函数声明和参数之外,每个词法环境还跟踪其父级词法环境。所以上面的示例代码在 JavaScript 引擎中会被这样解析。上述代码的词法环境,如图所示:

JavaScript中Hoisting详解 (变量提升与函数声明提升)

注:

如果理解起来有问题,请查看以下三篇文章:

  • 深入理解JavaScript中的作用域和上下文
  • JavaScript 核心概念之作用域和闭包
  • 实例分析 JavaScript 作用域

为了在词法环境中解析标识符, JavaScript 引擎将检查当前环境的引用。如果没有找到引用,则通过使用 [[environment]] 移动到外部环境。这将一直持续进行下去,直到标识符被找到,或者抛出一个 ‘not defined'(未定义) 的错误。

基本上,JavaScript 代码的执行分为两个阶段。第一个阶段在当前词法环境中注册所有的变量和函数声明。完成之后,第二个阶段的 JavaScript 执行就开始了!

所以要详细说明第一阶段:它在两个步骤中起作用。

  • 扫描当前函数声明中的代码。函数表达式和箭头函数会被跳过。对于每个被发现的函数,都会创建一个新的函数,并使用函数名称将其绑定到环境中。如果标识符的名称已经存在,那么它的值就会被覆盖。
  • 然后扫描当前环境的变量。找到使用 var 定义的变量和放置在其他函数之外的变量,并注册一个标识符,其值初始化为 undefined 。如果存在标识符,则该值将保持不变。

注意:用 let 和 const 定义的是块变量,与 var 的处理稍微不同。在另一篇文章中了解更多的内容。

现在你应该已经对词法环境这个基本概念有了一定的了解,那么让我们回到示例代码中,并解释这些问题。

在设置全局上下文时,将对环境进行扫描,并将 hoisting() 函数附加到标识符上。然后在下一步中,变量 notyetdeclared 被注册,其值初始化为 undefined 。按照这个步骤继续理解代码。

现在我们来解释示例代码中提出的3个问题:

第 6 行,该函数声明之前为何能访问?

第1阶段, hoisting() 函数已经注册到了标识符中,当JS代码在第2阶段的全局执行期上下文中开始执行时,它会查找 hoisting 的词法环境,并在其定义之前找到该函数。

第 1 行,没有抛出错误,是因为这时变量 notyetdeclared 不存在吗?

同样的,notyetdeclared 被注册到了标识符,并在第1阶段中初始化为 undefined ,因此不会抛出任何错误。

最后,

第 4 行,notyetdeclared 已经在全局作用域内声明了,为什么在第 9 行打印时还是 undefined 呢?

现在我们进入函数 hoisting 环境中。在第1阶段中,notyetdeclared 被注册并初始化为  undefined,因为在这个词法环境中,notyetdeclared 的变量还没有被注册。如果第 12 行不包含var 关键字,那么情况就不同了。

希望现在可以清楚地看到,在 JavaScript 中 Hoisting(提升) 只是我们用于解释其背后原理的一个观点,从技术上来讲,函数和变量并不会移动到任何地方。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
建立良好体验度的Web注册系统ajax
Jul 09 Javascript
JavaScript回调(callback)函数概念自我理解及示例
Jul 04 Javascript
JavaScript实现文字跟随鼠标特效
Aug 06 Javascript
关于JS中setTimeout()无法调用带参函数问题的解决方法
Jun 21 Javascript
JS获取html元素的标记名实现方法
Oct 08 Javascript
Javascript使用function创建类的两种方法(推荐)
Nov 19 Javascript
使用AngularJS2中的指令实现按钮的切换效果
Mar 27 Javascript
bootstrap table实现单击单元格可编辑功能
Mar 28 Javascript
JavaScript用二分法查找数据的实例代码
Jun 17 Javascript
JavaScript箭头函数_动力节点Java学院整理
Jun 28 Javascript
vue项目中使用Hbuilder打包app 设置沉浸式状态栏的方法
Oct 22 Javascript
vue前端和Django后端如何查询一定时间段内的数据
Feb 28 Vue.js
JavaScript实现旋转轮播图
Aug 18 #Javascript
JavaScript定时器setTimeout()和setInterval()详解
Aug 18 #Javascript
ECMAscript 变量作用域总结概括
Aug 18 #Javascript
微信小程序之前台循环数据绑定
Aug 18 #Javascript
Vue Cli与BootStrap结合实现表格分页功能
Aug 18 #Javascript
解决微信二次分享不显示摘要和图片的问题
Aug 18 #Javascript
详谈innerHTML innerText的使用和区别
Aug 18 #Javascript
You might like
《PHP边学边教》(04.编写简易的通讯录――视频教程1)
2006/12/13 PHP
在Linux系统下一键重新安装WordPress的脚本示例
2015/06/30 PHP
Laravel5.1框架路由分组用法实例分析
2020/01/04 PHP
php实现将数组或对象写入到文件的方法小结【三种方法】
2020/04/22 PHP
textarea的value是html文件源代码,存成html文件的代码
2007/04/20 Javascript
javascript实现的在当前窗口中漂浮框的代码
2010/03/15 Javascript
js中查找最近的共有祖先元素的实现代码
2010/12/30 Javascript
javascript 实现字符串反转的三种方法
2013/11/23 Javascript
js日期、星座的级联显示代码
2014/01/23 Javascript
js 判断浏览器使用的语言示例代码
2014/03/22 Javascript
js获取表格的行数和列数的方法
2015/10/23 Javascript
jquery解析XML及获取XML节点名称的实现代码
2016/05/18 Javascript
weebox弹出窗口不居中显示的解决方法
2017/11/27 Javascript
Vue中render方法的使用详解
2018/01/26 Javascript
jQuery.validate.js表单验证插件的使用代码详解
2018/10/22 jQuery
Vue press 支持图片放大功能的实例代码
2018/11/09 Javascript
Python下使用Psyco模块优化运行速度
2015/04/05 Python
Python代码解决RenderView窗口not found问题
2016/08/28 Python
python生成二维码的实例详解
2017/10/29 Python
使用APScheduler3.0.1 实现定时任务的方法
2019/07/22 Python
Mac中PyCharm配置Anaconda环境的方法
2020/03/04 Python
python利用opencv实现颜色检测
2021/02/23 Python
欧舒丹加拿大官网:L’Occitane加拿大
2017/10/29 全球购物
Champs Sports加拿大:北美最大的以商场为基础的专业运动鞋和服装零售商之一
2018/05/01 全球购物
环境科学专业优秀毕业生自荐书
2014/02/03 职场文书
护士岗位求职应聘自荐书范文
2014/02/12 职场文书
办公室综合文员岗位职责范本
2014/02/13 职场文书
酒店大堂副理的职责范文
2014/02/13 职场文书
教师节宣传方案
2014/05/23 职场文书
领导班子四风对照检查材料
2014/09/23 职场文书
公司出纳岗位职责
2015/03/31 职场文书
老人院义工活动感想
2015/08/07 职场文书
2016师德师风学习心得体会
2016/01/12 职场文书
如何解决springcloud feign 首次调用100%失败的问题
2021/06/23 Java/Android
Python装饰器详细介绍
2022/03/25 Python
深入解析Apache Hudi内核文件标记机制
2022/03/31 Servers