理解Javascript的变量提升
前言
本文2922字,阅读大约需要8分钟。
总括: 什么是变量提升,使用var,let,const,function,class声明的变量函数类在变量提升的时候都有什么区别。
- 参考文章:Hoisting in Modern JavaScript — let, const, and var
- 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍
要么庸俗,要么孤独。
正文
Javascript中的变量提升说的是在程序中可以在变量声明之前就进行使用:
console.log(a); // undefined
var a = 1;
可以看到,在变量a声明之前我们可以正常调用a,代码的实际的表现更像是这样的:
var a;
console.log(a); // undefined
a = 1;
但实际上,代码并没有被改变,上面的代码只是我们猜测的,其实Javascript引擎在执行这几行代码的时候并没有移动或是改变代码的结果。到底发生了什么呢?
变量提升
在代码的编译期间,即代码真正执行的瞬息之间,引擎会将代码块中所有的变量声明和函数声明都记录下来。这些函数声明和变量声明都会被记录在一个名为词法环境的数据结构中。词法环境是Javascript引擎中一种记录变量和函数声明的数据结构,它会被直接保存在内存中。所以,上面的console.log(a)
可以正常执行。
什么是词法环境
所谓词法环境就是一种标识符—变量映射的结构(这里的标识符指的是变量/函数的名字,变量是对实际对象[包含函数和数组类型的对象]或基础数据类型的引用)。
简单地说,词法环境是Javascript引擎用来存储变量和对象引用的地方。
词法环境的结构用伪代码表示如下:
LexicalEnvironment = {
Identifier: <value>,
Identifier: <function object>
}
关于词法环境更多的了解可以看博主之前的译文:理解Javascript中的执行上下文和执行栈。
了解了词法环境接下来让我依次看下使用var
,const
,let
,function
,class
声明的变量或函数的情况。
function声明提升
helloWorld(); // 打印 'Hello World!'
function helloWorld(){
console.log('Hello World!');
}
我们已经知道了,函数声明会在编译阶段就会被记录在词法环境中并且保存在内存中,因此我们可以在函数进行实际声明之前对该函数进行访问。
上面函数声明保存在词法环境中像下面这样:
lexicalEnvironment = {
helloWorld: < func >
}
所以在代码执行阶段,当Javascript引擎碰到helloWorld()
这行代码,会在词法环境中寻找,然后找到这个函数并执行它。
函数表达式
注意,只有函数声明才会被直接提升,使用函数表达式声明的函数不会被提升,看下面代码:
helloWorld(); // TypeError: helloWorld is not a function
var helloWorld = function(){
console.log('Hello World!');
}
如上,代码报错了。使用var
声明的helloWorld是个变量,并不是函数,Javascript引擎只会把它当成普通的变量来处理,而不会在词法环境中给它赋值。
保存在词法环境中像下面这样:
lexicalEnvironment = {
helloWorld: undefined
}
上面的代码要想可以正常运行改写如下即可:
var helloWorld = function(){
console.log('Hello World!');
}
helloWorld(); // 打印 'Hello World!'
var变量提升
看一个使用var
声明变量的例子:
console.log(a); // 打印 'undefined'
var a = 3;
如果按上面function
函数声明的方式去理解,这里应该打印3,但实际上打印了undefined
。
请记住:所谓的声明提升只是在编译阶段Javascript引擎将函数声明和变量声明存储在词法环境中,但不会给它们赋值。等到了执行阶段,真正执行到赋值那一行的时候,词法环境才会更新。
但上面的代码为什么打印了undefined
呢?
Javascript引擎会在编译阶段将使用var
声明的变量保存在词法环境中,并将它初始化为undefined
。到了执行阶段,等执行到赋值那一行代码的时候,词法环境中变量的值才会被更新。
所以上面代码的词法环境初始化像下面这样:
lexicalEnvironment = {
a: undefined
}
这也解释了为什么前面使用函数表达式声明的函数执行会报错,为什么上面的代码会打印undefined
。当代码执行到var a = 3;
这行代码的时候,词法环境中a的值就会被更新,此时词法环境会被更新如下:
lexicalEnvironment = {
a: 3
}
let和const变量提升
看一个使用let
声明变量的例子:
console.log(a);
let a = 3;
输出:
Uncaught ReferenceError: Cannot access 'a' before initialization
再看一个使用const声明变量的例子:
console.log(b);
const b = 1;
输出:
Uncaught ReferenceError: Cannot access 'b' before initialization
和var
不同,相同结构的代码换成let
或是const
都直接报错了。
难道使用let
和const
声明的变量不存在变量提升的情况么?
实际上,在Javascript中所有声明的变量(var
,const
,let
,function
,class
)都存在变量提升的情况。使用var
声明的变量,在词法环境中会被初始化为undefined
,但用let
和const
声明的变量并不会被初始化。
使用let
和const
声明的变量只有在执行到赋值那行代码的时候才会真正给他赋值,这也意味着在执行到变量声明的那行代码之前访问那个变量都会报错,这就是我们常说的暂时性死区(TDZ)。即在变量声明之前都不能对变量进行访问。
当执行到变量声明的那一行的时候,但是仍然没有赋值,那么使用let
声明的变量就会被初始化为undefined
;使用const
声明的变量就会报错; 看实际的例子:
let a;
console.log(a); // 输出 undefined
a = 5;
在代码编译阶段,Javascript引擎会把变量a存储在词法环境中,并把a保持在未初始化的状态。此时词法环境像下面这样:
lexicalEnvironment = {
a: <uninitialized>
}
此时如果尝试访问变量a或是b,Javascript引擎会在词法环境中找到该变量,但此时变量处于未初始化的状态,因此会抛出一个引用错误。
然后在执行阶段,Javascript引擎执行到赋值(专业点叫词法绑定)那一行的时候,会评估被赋值的值,如果没有被赋值,只是简单的声明,此时就会给let
声明的变量赋值为undefined
;此时词法环境像下面这样:
lexicalEnvironment = {
a: undefined
}
当执行到a = 5
这一行的时候,词法环境再次更新:
lexicalEnvironment = {
a: 5
}
再看下使用const
声明代码的情况:
let a;
console.log(a);
a = 5;
const b;
console.log(b);
输出:
Uncaught SyntaxError: Missing initializer in const declaration
上面代码直接报错,a的值也没有打印,直接报错,其实是代码在编译阶段就已经报错了,压根没执行到console.log(a);
这一行代码。
注意:在函数中,只要是能在变量声明之后引用该变量就不会报错。
什么意思呢?看如下代码:
function foo () {
console.log(a);
}
let a = 20;
foo(); // 打印 20
但下面代码就会报错:
function foo () {
console.log(a);
}
foo();
let a = 20; // 报错: Uncaught ReferenceError: Cannot access 'a' before initialization
这里报错的原因需要结合Javascript中的执行上下文和执行栈才能理解,因为此时全局执行上下文中词法环境中保存的变量a处于未初始化的状态,调用foo函数,创建了一个函数执行上下文,然后函数foo执行过程对全局执行上下文的变量a进行访问,但a还处于未初始化的状态(此时let a = 20
还没有执行)。因此报错。
这里需要纠正一个误区,就是let
和const
声明的变量只有暂时性死区,不存在变量提升,其实是不对的,举个例子证明理解一下:
let a = 1;
{
console.log(a);
let a = 2;
}
上面的代码会被报错:
Uncaught ReferenceError: Cannot access 'a' before initialization
如果不存在变量提升,理论上不会报错才对。
class声明提升
与let
、const
类似,使用class
声明的类也会被提升,然后这个类声明会被保存在词法环境中但处于未初始化的状态,直到执行到变量赋值那一行代码,才会被初始化。另外,class
声明的类一样存在暂时性死区(TDZ)。看例子:
let peter = new Person('Peter', 25);
console.log(peter);
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
打印:
Uncaught ReferenceError: Cannot access 'Person' before initialization
改写如下就可以正常运行了:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let peter = new Person('Peter', 25);
console.log(peter);
// Person { name: 'Peter', age: 25 }
上面代码在编译阶段,词法环境像这样:
lexicalEnvironment = {
Person: <uninitialized>
}
然后执行到class
声明的那一行代码,此时词法环境像下面这样:
lexicalEnvironment = {
Person: <Person object>
}
注意:使用构造函数实例化对象并不会报错:
let peter = new Person('Peter', 25);
console.log(peter);
function Person(name, age) {
this.name = name;
this.age = age;
}
// Person { name: 'Peter', age: 25 }
上面代码正常运行。
类表达式
和函数表达式一样,类表达式也一样会被提升,比如:
let peter = new Person('Peter', 25);
console.log(peter);
let Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
报错:
Uncaught ReferenceError: Cannot access 'Person' before initialization
要想正常运行,改写如下即可:
let Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let peter = new Person('Peter', 25);
console.log(peter);
// Person { name: 'Peter', age: 25 }
也就是说不管是函数表达式还是类表达式遵循的规则和变量声明是一样的。
结论
不管是var
,const
,let
,function
,class
声明的变量还是函数都存在变量提升的情况。正确理解变量提升有助于我们写更好的代码。整个变量提升的情况总结如下:
var
:存在变量提升,在编译阶段会被初始化为undefined
;let
: 存在变量提升,存在暂时性死区(TDZ),执行阶段,如果没赋值,则初始化为undefined
;const
: 存在变量提升,存在暂时性死区(TDZ),如果没有赋值,编译阶段就会报错;function
:存在变量提升,在变量声明之前可以访问并执行;class
: 存在变量提升,存在暂时性死区(TDZ);
能力有限,水平一般,欢迎勘误,不胜感激。
订阅更多文章可关注公众号「前端进阶学习」,回复「666」,获取一揽子前端技术书籍
理解Javascript的变量提升的更多相关文章
- javascript中变量提升的理解
网上找了两个经典的例子 var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); // 10 var ...
- JavaScript的变量提升机制
变量提升 JavaScript的变量提升有两种,用var声明的变量以及用function声明的变量. 用var声明的变量 我们先来看下面这段代码,a的值是多少 代码1 console.log(a); ...
- JavaScript中变量提升是语言设计缺陷
首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...
- JavaScript:变量提升和函数提升
第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...
- JavaScript中变量提升------Hoisting
原谅链接:http://www.cnblogs.com/damonlan/archive/2012/07/01/2553425.html 因为这个问题很是经典,而且容易出错,所以在介绍一次.哈哈.莫怪 ...
- 深入理解js的变量提升和函数提升
一.变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分.上个简历的例子如: ...
- JavaScript的变量提升
在JavaScript中,var变量具有函数级作用域,而且是整个函数作用域.为什么会是整个函数作用域呢?因为var变量具有变量(声明)提升功能,能将变量声明隐式的提升到函数体的顶部.这样做的一个好处就 ...
- Javascript 的变量提升与预解析
一.什么是变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分 二.怎么实现 ...
- javascript Hoisting变量提升
1. 看人家举的两个例子,我认为这里的判断是否定义: !var 其实就是 指是否在函数function里面定义了.只有在funciton里面定义了了,js才hoist到最上面去找这个变量的值,否则就按 ...
随机推荐
- win10电脑搭建网站
新建网站之后,IIS错误提示是:在计算机“.”上没有找到服务W3SVC,需要在“启动或关闭windows功能”添加.net 3.5下面的两个程序. https://img-blog.csdn.net/ ...
- OSPF笔记——LSA及其字段,及其作用
Link State ID Link State ID remains at 32 bits in length, Link State ID has shed any addressing sema ...
- OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol
gitlab版本为社区版: gitlab-ce_11.5.4-ce.0_amd64.deb 目录:/var/cache/apt/archives 配置邮箱的主要参数: user['git_user_e ...
- Linux 发行版本简述
在撰写这篇文章前,先向linux创始人 Linus Torvalds 先生致敬,感谢您二十多年前的无私开源! 其次向二十多年来维护更新的开发者们致敬! Lin ...
- 珠峰-6-node
1. js主线程是单线程的. 2. path.resolve 传('/')解析出一个绝对路径.
- codewars--js--the highest and lowest number + JS 字符串和数组相关知识
本文参考: http://blog.csdn.net/tyrionj/article/details/78653426 http://www.runoob.com/jsref/jsref-obj-st ...
- 全国省市,4个直辖市geoCoord数据,用于echart gl 3d地图
var geoCoordMap = { '北京': [116.4551, 40.2539], '东城区':[116.418757,39.917544], '西城区':[116.366794,39.91 ...
- 浅析设计模式之mvc、mvp、mvvm
mvc.mvvm.mvp是常见的设计模式,也是常见的设计思想,现对它们进行简要的归纳总结 三种模式的介绍 1.MVC:经典设计模式 View 传送指令到 Controller(控制器) Control ...
- html基本标签表单实现交互原理,单选框,复选框,下拉框介绍
表单是什么?表单是前端和服务器做交互的一种机制,表单收集用户输入信息,之后发送或者提交给服务器.用户在输入的信息称之为内容,内容的文本分为普通和密码型,用户通过单选框.复选框.下拉框(也就是下拉菜单) ...
- 用Excel排值班表用到的几个公式
用Excel排值班表用到的几个公式 最近,疫情得到了一定的缓解,但还不能放松.所以,各单位都加强值班.那就得排值班表.提到的表当然要用Excel,为什么?因为Excel中的公式真得能让我们提高工作效率 ...