0

0

JS中作用域和变量提升(hoisting)的深入理解

高洛峰

高洛峰

发布时间:2016-12-08 13:43:22

|

1520人浏览过

|

来源于php中文网

原创

作用域(scoping)

对于Javascript初学者来说,一个最迷惑的地方就是作用域;事实上,不光是初学者。我就见过一些有经验的javascript程序员,但他们对scope理解不深。javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言。我对作用域的理解是只会对某个范围产生作用,而不会对外产生影响的封闭空间。在这样的一些空间里,外部不能访问内部变量,但内部可以访问外部变量。

c语言的变量分为全局变量和局部变量,全局变量的作用范围是任何文件和函数访问(当然,对于非变量定义的其他c文件,需要使用extern关键字进行申明,使用static关键字也可以将作用范围限定在当前文件中),局部变量的作用范围就是从申明到最近的大括号涵盖的块级范围。java则无全局变量,有类变量,成员变量和局部变量,作用范围根据public,protected,private等访问权限有不同的作用范围,这里就不多述。

JS作用域有哪些?

在ES5中,js只有两种形式的作用域:全局作用域和函数作用域。

全局作用域其实是全局对象的作用域,任意地方都可以访问到(如果没有被函数作用域覆盖)。

函数对象作用域跟c的局部变量作用域是不同的,它的作用域是整个函数范围,不论他是在函数的任意位置申明的!这就是所谓的hoisting,也就是变量提升的概念。不过不着急,下面会专门针对hoisting来进行解释。

不过,在ES6中,新增了一个块级作用域(最近的大括号涵盖的范围),但是仅限于let方式申明的变量。
作用域演示:

201611011003251.png

定义变量时,如果不写var,比如 i=0,则会被定义为全局变量,作用域为全局作用域,否则为局部变量,作用域为函数作用域。上面第一行的var i=0,之所以说它是全局变量,是因为它已经是在全局区申明的了,并不在函数范围内,因此跟 i=0 是一样的。

至于,为什么结果会是这样,继续往下看就知道了。

申明形式

变量声明:

201611011003251.png

函数申明:

201611011003251.png

变量提升(Hoisting)

引出一个问题

下面这段代码会输出什么内容?

201611011003251.png

这道题我面试过很多人,大多数人都说输出的是日期。但真实的结果是undefined。为什么是这样呢?这里就引出了一个概念--hoisting,中文的意思就是变量提升。MDN中对变量hoisting的解释是这样的:

var hoisting

Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it's declared. This behavior is called "hoisting", as it appears that the variable declaration is moved to the top of the function or global code.

这段话翻译下来就是

因为变量申明是在任意代码执行前处理的,在代码区中任意地方申明变量和在最开始(最上面)的地方申明是一样的。也就是说,看起来一个变量可以在申明之前被使用!这种行为就是所谓的“hoisting”,也就是变量提升,看起来就像变量的申明被自动移动到了函数或全局代码的最顶上。

注意:仅仅是申明提升了,定义并不会被提升。

如此,上面这段代码其实就是下面的形式:

201611011003251.png

所以,这样就应该理解了,console输出的时候,tmp变量仅仅是申明了但未定义,所以输出应该是undefined。

这里需要说明的是,虽然所有的申明(包括ES5的var、function,和ES6的function *、let、const、class)都会被提升,但是var、function、function *和let、const、class的的提升却并不相同!具体原因可以看这里的说明(大体的意思是虽然let,const,class也被提升了,但是却并不会被初始化,这时候去访问他们则会报ReferenceError异常,他们需要到语句执行的时候才会被初始化,而在被初始化之前的状态叫做temporal dead zone)。我们来看一段代码就知道了:

201611011003251.png

这里a被提升,但因为定义在后,所以输出undefined

魔匠AI论文
魔匠AI论文

专业原创的AI论文写作工具,一站式解决论文选题、写作、文献综述、答辩PPT全流程,支持毕业论文、课程论文等多种类型,轻松助力高质量论文写作。

下载

这里a虽然被提升,但却报了引用错误!

之所以或这样

因为这样的原因,推荐的做法是在申明变量的时候,将所用的变量都写在作用域(全局作用域或函数作用域)的最顶上,这样代码看起来就会更清晰,更容易看出来那个变量是来自函数作用域的,哪个又是来自作用域链(本文不对此多做解释,请读者自行百度,有机会再补充说明)。

重复声明

201611011003251.png

上面的输出其实是:1 2 2。虽然看起来里面x申明了两次,但上面说了,js的var变量只有全局作用域和函数作用域两种,且申明会被提升,因此实际上x只会在最顶上开始的地方申明一次,var x=2的申明会被忽略,仅用于赋值。也就是说上面的代码实际上跟下面是一致的。

201611011003251.png

函数和变量同时提升的问题

如果是函数和变量类型同时申明定义了,会发生什么事情呢?看下面的代码

201611011003251.png

上面的输出结果其实是: function foo(){} ,也就是函数内容。

而如果是这样的形式呢

201611011003251.png

它的输出却变成:undefined

为什么会这样呢?

原来函数提升分为两种情况:

      一种:函数申明。就是上面A,function foo(){}这种形式

     另一种:函数表达式。就是上面B,var foo=function(){}这种形式

第二种形式其实就是var变量的声明定义,因此上面的B输出结果为undefined应该就能理解了。

而第一种函数申明的形式,在提升的时候,会被整个提升上去,包括函数定义的部分!因此A跟下面的这种方式是等价的!

201611011003251.png

原因是因为:1、函数声明被提升到最顶上;2、申明只进行一次,因此后面var foo='i am text'的申明会被忽略。

并且函数申明的优先级优于变量申明,所以以下形式的输出,同样是函数内容:

201611011003251.png

总结

要彻底理解JS的作用域和Hoisting,只要记住以下三点即可:

      1、所有申明都会被提升到作用域的最顶上

      2、同一个变量申明只进行一次,并且因此其他申明都会被忽略

      3、函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升

注意:

通过with语句,可以临时改变运行期上下文的作用域链,此时的对非var定义的变量进行访问,会首先访问with中对象的属性,然后才会向上顺着作用域链向上检查该属性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
全国统一发票查询平台入口合集
全国统一发票查询平台入口合集

本专题整合了全国统一发票查询入口地址合集,阅读专题下面的文章了解更多详细入口。

7

2026.02.03

短剧入口地址汇总
短剧入口地址汇总

本专题整合了短剧app推荐平台,阅读专题下面的文章了解更多详细入口。

13

2026.02.03

植物大战僵尸版本入口地址汇总
植物大战僵尸版本入口地址汇总

本专题整合了植物大战僵尸版本入口地址汇总,前往文章中寻找想要的答案。

6

2026.02.03

c语言中/相关合集
c语言中/相关合集

本专题整合了c语言中/的用法、含义解释。阅读专题下面的文章了解更多详细内容。

2

2026.02.03

漫蛙漫画网页版入口与正版在线阅读 漫蛙MANWA官网访问专题
漫蛙漫画网页版入口与正版在线阅读 漫蛙MANWA官网访问专题

本专题围绕漫蛙漫画(Manwa / Manwa2)官网网页版入口进行整理,涵盖漫蛙漫画官方主页访问方式、网页版在线阅读入口、台版正版漫画浏览说明及基础使用指引,帮助用户快速进入漫蛙漫画官网,稳定在线阅读正版漫画内容,避免误入非官方页面。

5

2026.02.03

Yandex官网入口与俄罗斯搜索引擎访问指南 Yandex中文登录与网页版入口
Yandex官网入口与俄罗斯搜索引擎访问指南 Yandex中文登录与网页版入口

本专题汇总了俄罗斯知名搜索引擎 Yandex 的官网入口、免登录访问地址、中文登录方法与网页版使用指南,帮助用户稳定访问 Yandex 官网,并提供一站式入口汇总。无论是登录入口还是在线搜索,用户都能快速获取最新稳定的访问链接与使用指南。

50

2026.02.03

Java 设计模式与重构实践
Java 设计模式与重构实践

本专题专注讲解 Java 中常用的设计模式,包括单例模式、工厂模式、观察者模式、策略模式等,并结合代码重构实践,帮助学习者掌握 如何运用设计模式优化代码结构,提高代码的可读性、可维护性和扩展性。通过具体示例,展示设计模式如何解决实际开发中的复杂问题。

2

2026.02.03

C# 并发与异步编程
C# 并发与异步编程

本专题系统讲解 C# 异步编程与并发控制,重点介绍 async 和 await 关键字、Task 类、线程池管理、并发数据结构、死锁与线程安全问题。通过多个实战项目,帮助学习者掌握 如何在 C# 中编写高效的异步代码,提升应用的并发性能与响应速度。

2

2026.02.03

Python 强化学习与深度Q网络(DQN)
Python 强化学习与深度Q网络(DQN)

本专题深入讲解 Python 在强化学习(Reinforcement Learning)中的应用,重点介绍 深度Q网络(DQN) 及其实现方法,涵盖 Q-learning 算法、深度学习与神经网络的结合、环境模拟与奖励机制设计、探索与利用的平衡等。通过构建一个简单的游戏AI,帮助学习者掌握 如何使用 Python 训练智能体在动态环境中作出决策。

2

2026.02.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.8万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号