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 相关文章推荐
Windows Live的@live.com域名注册漏洞 利用代码
Dec 27 Javascript
写了一个layout,拖动条连贯,内容区可为iframe
Aug 19 Javascript
各浏览器对click方法的支持差异小结
Jul 31 Javascript
减少访问DOM的次数提升javascript性能
Feb 24 Javascript
jquery uploadify 在FF下无效的解决办法
Sep 26 Javascript
JQuery勾选指定name的复选框集合并显示的方法
May 18 Javascript
jQuery AjaxUpload 上传图片代码
Feb 02 Javascript
原生JS实现旋转木马式图片轮播插件
Apr 25 Javascript
vue+element的表格实现批量删除功能示例代码
Aug 17 Javascript
vue-cli配置flexible过程详解
Jul 04 Javascript
在Layui 的表格模板中,实现layer父页面和子页面传值交互的方法
Sep 10 Javascript
javascript设计模式之迭代器模式
Jan 30 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与XML的PDF文档生成技术
2006/10/09 PHP
Discuz板块横排显示图片的实现方法
2007/05/28 PHP
由php if 想到的些问题
2008/03/22 PHP
PHP之短标签开启设置
2013/06/17 PHP
php中mysql连接和基本操作代码(快速测试使用,简单方便)
2014/04/25 PHP
CI框架常用函数封装实例
2016/11/21 PHP
如何在Laravel5.8中正确地应用Repository设计模式
2019/11/26 PHP
JQuery 学习笔记01 JQuery初接触
2010/05/06 Javascript
javascript开发技术大全 第4章 直接量与字符集
2011/07/03 Javascript
打开新窗口关闭当前页面不弹出关闭提示js代码
2013/03/18 Javascript
onkeypress字符按键兼容所有浏览器使用介绍
2013/04/24 Javascript
NodeJS与Mysql的交互示例代码
2013/08/18 NodeJs
只需一行代码,轻松实现一个在线编辑器
2013/11/12 Javascript
JavaScript设计模式之工厂方法模式介绍
2014/12/28 Javascript
JavaScript实现文字与图片拖拽效果的方法
2015/02/16 Javascript
Jquery使用css方法改变样式实例
2015/05/18 Javascript
JS基于onclick事件实现单个按钮的编辑与保存功能示例
2017/02/13 Javascript
Webpack打包慢问题的完美解决方法
2017/03/16 Javascript
JS去掉字符串前后空格、阻止表单提交的实现代码
2017/06/08 Javascript
JS简单实现数组去重的方法分析
2017/10/14 Javascript
uin-app+mockjs实现本地数据模拟
2020/08/26 Javascript
python Django模板的使用方法(图文)
2013/11/04 Python
Python的gevent框架的入门教程
2015/04/29 Python
编写Python小程序来统计测试脚本的关键字
2016/03/12 Python
python字典值排序并取出前n个key值的方法
2018/10/17 Python
Python箱型图绘制与特征值获取过程解析
2019/10/22 Python
python3 kubernetes api的使用示例
2021/01/12 Python
HTML5学习笔记之html5与传统html区别
2016/01/06 HTML / CSS
体育纪念品、亲笔签名的体育收藏品:Steiner Sports
2020/07/31 全球购物
经理秘书岗位职责
2013/11/14 职场文书
采购员的工作职责
2013/12/26 职场文书
商场总经理岗位职责
2014/02/03 职场文书
承诺书模板
2014/08/30 职场文书
房屋产权证明书
2014/10/15 职场文书
行为规范主题班会
2015/08/13 职场文书
MySQL的join buffer原理
2021/04/29 MySQL