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 相关文章推荐
javascript FormatNumber函数实现方法
Dec 30 Javascript
提高javascript效率 一次判断,而不要次次判断
Mar 30 Javascript
使用js解决由border属性引起的div宽度问题
Nov 26 Javascript
JavaScript中使用document.write向页面输出内容实例
Oct 16 Javascript
使用Sticker.js实现贴纸效果
Jan 28 Javascript
JQuery中绑定事件(bind())和移除事件(unbind())
Feb 27 Javascript
轻量级javascript 框架Backbone使用指南
Jul 24 Javascript
javascript中eval和with用法实例总结
Nov 30 Javascript
jQuery实现对无序列表的排序功能(附demo源码下载)
Jun 25 Javascript
JS获取一个未知DIV高度的方法
Aug 09 Javascript
JavaScript屏蔽Backspace键的实现代码
Nov 02 Javascript
vue 组件中slot插口的具体用法
Apr 03 Javascript
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中strstr、strrchr、substr、stristr四个函数的区别总结
2014/09/22 PHP
Yii框架自定义数据库操作组件示例
2019/11/11 PHP
JS 参数传递的实际应用代码分析
2009/09/13 Javascript
DOM 中的事件处理介绍
2012/01/18 Javascript
Javascript表格翻页效果的具体实现
2013/10/05 Javascript
JS和Jquery获取和修改label的值的示例代码
2014/01/15 Javascript
js操作输入框提示信息且响应鼠标事件
2014/03/25 Javascript
Node.js事件循环(Event Loop)和线程池详解
2015/01/28 Javascript
JavaScript类型系统之正则表达式
2016/01/05 Javascript
JavaScript知识点总结(六)之JavaScript判断变量数据类型
2016/05/31 Javascript
vuex 使用文档小结篇
2018/01/11 Javascript
Vue2.0中集成UEditor富文本编辑器的方法
2018/03/03 Javascript
微信小程序实现发红包功能
2018/07/11 Javascript
vue中引入mxGraph的步骤详解
2019/05/17 Javascript
解决layer 关闭当前弹窗 关闭遮罩层 input值获取不到的问题
2019/09/25 Javascript
基于纯JS实现多张图片的懒加载Lazy过程解析
2019/10/14 Javascript
vuex actions异步修改状态的实例详解
2019/11/06 Javascript
浅谈vue项目,访问路径#号的问题
2020/08/14 Javascript
详解template标签用法(含vue中的用法总结)
2021/01/12 Vue.js
javascript中layim之查找好友查找群组
2021/02/06 Javascript
Python 实现随机数详解及实例代码
2017/04/15 Python
在java中如何定义一个抽象属性示例详解
2017/08/18 Python
Python Logging 日志记录入门学习
2018/06/02 Python
Window 64位下python3.6.2环境搭建图文教程
2018/09/19 Python
Python selenium文件上传下载功能代码实例
2020/04/13 Python
Css3圆角边框制作代码
2015/11/18 HTML / CSS
法国在线药房:1001Pharmacies
2021/03/07 全球购物
企业总经理职责
2014/02/02 职场文书
三八妇女节活动总结
2014/05/04 职场文书
大学生安全责任书
2014/07/25 职场文书
县委班子四风对照检查材料思想汇报
2014/09/29 职场文书
教师岗位职责
2015/02/03 职场文书
社区敬老月活动总结
2015/05/07 职场文书
入党转正申请报告
2015/05/15 职场文书
导游词之安徽九华山
2019/09/18 职场文书
python 开心网和豆瓣日记爬取的小爬虫
2021/05/29 Python