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

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

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

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

请考虑以下示例:

JavaScript 代码:
  1. console.log(notyetdeclared);
  2. // 打印 'undefined'
  3. var notyetdeclared = 'now it is declared';
  4. hoisting();
  5. function hoisting(){
  6. console.log(notyetdeclared);
  7. // 打印 'undefined'
  8. var notyetdeclared = 'declared differently'; //注: (若此行无定义,则上一行输出: now it is declared)
  9. console.log(notyetdeclared);
  10. // 打印 'declared differently'
  11. }

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

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

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

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

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

上述示例代码的调用栈:

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

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

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

愚人码头注:

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

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

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

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

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

  1. 扫描当前函数声明中的代码。函数表达式和箭头函数会被跳过。对于每个被发现的函数,都会创建一个新的函数,并使用函数名称将其绑定到环境中。如果标识符的名称已经存在,那么它的值就会被覆盖。
  2. 然后扫描当前环境的变量。找到使用 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 核心概念之作用域和闭包
  • 实例分析 JavaScript 作用域
  • JavaScript 中的 Hoisting (变量提升和函数声明提升)

英文原文:https://codeburst.io/hoisting-in-javascript-515c987336d3

JavaScript 中的 Hoisting (变量提升和函数声明提升)相关推荐

  1. 浅聊JavaScript中的Hoisting(变量提升)

    一直有写博客的想法但因为懒惰等各种情况没有付出实际行动,择日不如撞日,那就今天让我给大家简单归纳总结一下JavaScript中的Hoisting(变量提升)吧! 1.对于变量 //variablesc ...

  2. JS变量(声明)提升和函数声明提升

    目录 一.概念理解 1.变量声明提升 2.函数声明提升(又叫函数提升) 变量提升和函数提升的优先级 小结: 一.概念理解 变量提升:在当前作用于中,js代码自上而下执行之前,浏览器会把所有带var/f ...

  3. Js 变量声明提升和函数声明提升

    Js代码分为两个阶段:编译阶段和执行阶段 Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容 包括变量声明(var a)和函数声明(function a(){ ...

  4. JavaScript函数声明提升

    首先,JavaScript中函数有两种创建方式,即函数声明.函数表达式两种. 1.函数声明. function boo(){console.log(123); } boo() 2.函数表达式. var ...

  5. js中变量名提升和函数名提升

    首先,js中变量没有块级作用域,但是有函数作用域,即只有函数可以约数变量的作用域. 并且,函数的实质也是一个变量,所以可以改变它的值,即赋值.所以变量名提升和函数名提升非常相像. 1.变量名的提升发生 ...

  6. JavaScript 中的 hoisting 到底是甚麼 ?

    JavaScript 中的 hoisting 到底是甚麼 ? 前言 正文 到底什麼是 hoisting? let const 與 hoisting 為什麼要有 hoisting? hoisting 到 ...

  7. JavaScript中的钩子(钩子机制\钩子函数\hook)是什么?

    我的博客https://www.ideaopen.cn/ 首先,看到我们的标题: JavaScript中的钩子(钩子机制\钩子函数\hook) 是什么? 我们前端的JavaScript中,经常提到钩子 ...

  8. js判断是否为数字_第23题:JavaScript 中如何判断变量是否为数字 ?

    在JavaScript中,诸如NaN,Infinity(正无穷)和-Infinity(负无穷)之类的特殊值也是数字类型的. 判断方法3中: Number.isFinite() Number.isNaN ...

  9. Javascript中的循环变量声明,到底应该放在哪儿?

    不放走任何一个细节.相信很多Javascript开发者都在声明循环变量时犹 豫过var i到底应该放在哪里:放在不同的位置会对程序的运行产生怎样的影响?哪一种方式符合Javascript的语言规范?哪 ...

最新文章

  1. 多条件组合查询+分页
  2. iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求
  3. 650c公路车推荐_2020最具性价比的中高端公路整车盘点
  4. appium+python自动化57-chromedriver与chrome版本
  5. Windows 2000服务器安全配置精华技巧
  6. mysql signal函数_MySQL:简单记录信号处理
  7. MongoDB 副本集
  8. 【领域适应】训练梯度反向层(gradient reversl layer, GRL)
  9. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_04 IO字节流_1_IO概述(概念分类)...
  10. Xcode中打印显示Unicode的解决办法
  11. 腾讯QQ群视频功能“视频秀”即将上线
  12. 2.3 常用导数公式及推导
  13. 浏览器市场占有率最新分析
  14. ES5和ES6的类,静态方法,继承实现代码
  15. Scrum立会报告+燃尽图(Beta阶段第五次)
  16. php 获取微博cookie,如何获取微博 Cookie
  17. 生成付款的二维码,实际流程
  18. 华东师大计算机系博士几年,2018华东师范大学就业结果公布!就业率超97%,博士生月薪1.1万!...
  19. 从传感器和算法原理讲起,机器人是如何避障的
  20. html添加背景音乐自动循环播放,html在谷歌浏览器的背景音乐怎么做才能自动循环播放...

热门文章

  1. 一文带你了解开源数据库中的佼佼者 TOP 10
  2. oracle 10.3.5,AIX5,3 oracle 10201升10204 rac补丁 误操作记录
  3. 计网PPT 第三章 数据链路层
  4. 【前端面试题】—21道有关移动端的面试题(附答案)
  5. ucos任务间的同步与通信
  6. Begin at the Beginning
  7. 前端ip地址格式验证
  8. 圈叉棋游戏c语言编写,圈叉棋小游戏的简单实现代码
  9. 思科认证or华为认证,选择哪个比较好
  10. srvctl 控制 asm