我们都知道JavaScript中的var,在本系列的
JavaScript学习系列2一JavaScript中的变量作用域 中,我们详细阐述了var声明的变量的作用域
文章中提到,JavaScript中没有块级作用域(由{}限定的作用域), 也就是说JS中变量的作用域只有两种,一种是全局作用域,另一种就是函数作用域
但是,其实在ES6中,新增了let命令,改变了这种状况,它为JavaScript增加了块级作用域. ES6中还有一个const命令。 我们现在来看看它们
一个简单的例子
{ let a = 1; var b = 1; } a // ReferenceError: a is not defined b // 1
上面这个例子中,在代码块中,用let声明了一个变量a, 用var声明了一个变量b. 然后我们尝试在代码块之外去访问,发现可以访问到b,但是却没法访问到用let声明的变量a.
上面这个例子很好的说明,ES6中的let设立了一个JavaScript中的块级作用域,上面的let声明的变量a,只在{}里面的块级作用域中有效,出了这个{}块级作用域,它就不存在了,所以才会报错 ReferenceError
在JavaScript中,有一个地方非常适合使用let,哪里呢. 就是for循环中的计数器 (记住这一点 JavaScript中for循环的计算器,特别适合使用使用let声明), 我们来看一个例子
for(let i = 0; i < 100; i++) { //.... }
因为for循环中的计数器只需要在for循环中使用,所以它的作用域就是for循环这一个语句块,所以特别适合使用let来声明。 除了作用域不同之外,var和let还有哪些区别呢,我们来看看
1. 不存在var 那样的 声明提升 (hoisting)
在JavaScript学习系列2一JavaScript中的变量作用域中,我们提到了JavaScript中用var来声明变量时,存在的声明提升(hoisting), 如下
alert(name); // 输出 undefined var name = "Luke"; //实际执行过程 (变量提升) var name; alert(name); name = "Luke";
那么ES6中的块级作用域声明let是否也存在变量提升呢, 答案是否定的,我们来看看
let const 不存在变量提升
// var 声明的变量 console.log(test); // 输出 undefined var test = 2 // let 声明的变量 console.log(test) // 报错 ReferenceError let test = 2;
我们可以看到,用let来声明变量时,没有发生变量提升. 当执行console.log(test) 时,在它后面的声明语句 let test = 2; 是用let来声明的,不会发生声明提升。所以,这个时候test还是没有声明的变量,不存在,所以报错ReferenceError
let, const中的暂时性死区
什么是暂时性死区呢,我们还是先通过一个例子来直观的理解
var test = 'a'; if(true) { test = 'b'; //报错 ReferenceError let test; }
是不是觉得很难理解,感觉不应该报错啊,因为虽然在if语句块里面, 用let对test的声明是在赋值 test = 'b'之后,但是在if语句块外面,我们用var对test进行了声明,它应该也是个全局变量。好像这样理解是对的哈。但事实呢...
ES6中有规定, 如果在语句块 (区块) 中存在let 和 const命令, 这个语句块(区块) 对用let或者const声明的变量, 就形成了一个封闭作用域. 如果你在声明之前就开始使用这个变量,那么从第一次使用它开始,一直到它的声明出现的地方,这个范围我们称之为 "暂时性死区" (temporal dead zone 简称 TDZ) . 在这个 暂时性死区 TDZ 中,使用这些变量 (因为是在声明之前使用),就一定会报错。
上面的代码中,if语句块内部就是一个暂时性死区. 在这个暂时性死区中,用let声明的变量test就会绑定 (binding) 在这个区域,不再受到外部影响. 也就是说如果你在 暂时性死区 外面也声明了一个同名的全局变量,也完全对暂时性死区 里面的变量没有任何影响。
所以,在if语句块这个暂时性死区中,你在let声明test之前,就对test赋值,会报错,因为这个时候test压根就还不存在 我们再看一个例子
if(true) { // TDZ 开始 test = 'a'; console.log("The result is " + test); let test; //TDZ结束 console.log(test); // 输出 undefined test = 1; console.log(test); //输出 1 }
上面这个例子,对 暂时性死区 (TDZ) 的区分写的很清楚,在if语句块中,从test变量第一次出现开始,一直到test的声明出现,这整个范围都是 TDZ 暂时性死区
出现了let之后,以前我们知道的一个常识 “JavaScript中的typeof运算符是百分之百安全的” 就不再是正确的了. 我们来看一个let出现之前(ES6之前)的一个例子
typeof a // 输出 "undefined"
在上面的代码中,因为a没有声明,所以默认就是一个全局变量,而类型未知,所以输出它的类型就是 undefined
出现了let之后,我们再来看一个例子
typeof x; // 报错 ReferenceError let x;
按照let的暂时性死区 TDZ的说法,在let x之前一直到这一行,都是暂时性死区. 在这个 暂时性死区 TDZ 中,使用这些变量 (因为是在声明之前使用),就一定会报错。所以这个时候使用 typeof x 就一定会报错 ReferenceError, 因为它这个时候压根就还不存在
针对let的暂时性死区TDZ, 我们再来看一个有意思的例子
// 不报错 var a = a; //报错 ReferenceError: a is not defined let a = a;
上面,在用let声明变量时,就已经开始使用变量,把a赋值给a,也就是说声明和使用是几乎同步进行的。这种情况,报错 ReferenceError:a is not defined . 这说明,在let形式的暂时性死区TDZ中,只要你是在变量还没有声明完成之前使用,就会报错
所以,上面这行语句 let a = a, 在let a 对变量a进行声明语句还没有执行完成之前,就去获取a的值,就报错了。报的错误就是 a 这个时候还没有定义,所以不能使用 (a is not defined).
let,const不允许在同一个作用域内重复声明一个变量
我们来看一个例子,
上面的例子,我用var重复声明了变量a, 完全没有任何问题,成功获取到了最后一次声明时赋的值1. 我们把同样的操作放在let上,来看看会出现什么情况
从上面例子中可以看到,不能使用let重复声明同一个变量, 会报错. 我们再看看
好了,再看一个例子
由上面的两个例子可以看出,用var和let同时声明一个相同的变量,同样报错,因为同样也是,重复声明一个变量.
因为不能重复声明一个变量,所以看看下面的例子:
可以看到, 出错了. 因为函数myFunc的传入参数名为arg,它的作用域是整个函数内部. 而在函数内部又用let声明了同名的变量arg.所以就报错.
这个例子中,可以看到完全没有任何问题。因为let的作用域,函数myFunc的传入参数arg,它的作用域是整个函数内部。而我们在函数内部的{}块声明的变量let arg, 作用域只在这个{}内部,所以没有问题
我们看看下面这个例子就知道了:
也就是说 使用let来定义变量时,内层作用域可以定义和外层作用域同名的变量。但是在同一个作用域内,不能定义同名的变量
{{{ let test = ‘This is test’; {let test = 'This is test'} }}};
上面这个例子,是完全没有问题的
看完了let, 我们再来看看const
和在C#中一样,const用来声明一个常量, 既然是常量,那么它一旦声明,常量的值就不能改变. 我们来看一个例子
上面,我们声明了一个常量a=123; 声明后,它的值是不可以改变的。 上面的代码试图修改它的值为355,所以报错
用const声明的变量不能改变值,也就是说, const声明的变量一旦声明,就必须立即初始化,不能说留到以后去赋值.
const test; Uncaught SyntaxError: Missing initializer in const declaration
上面的例子中可以看到,声明一个常量 const test 但是没有给它赋初始值. 结果就是报错 Missing initializer in const declaration
上面在讲let时,我们说到, let只在其声明的块级作用域中有效, const也是一样,只在它声明的块级作用域中有效果,只要出了这个块级作用域,它就无效了
if(true) { const PI = 3.1415; } PI //报错 Uncaught ReferenceError: PI is not defined => Const定义的常量PI 只存在于if条件语句块中
和 let 声明的变量一样, const声明的变量同样不存在变量提升,也存在暂时性死区TDZ. 只可以先完成声明工作(const是声明和初始化工作),后面才可以开始使用
if(true) { console.log(a); // 报错 ReferenceError => 此时a还没有声明没有初始化,直接使用时报错 , 这里到下面的声明初始化为止,也是常量a的暂时性死区TDZ const a = 1; }
和let一样,const也不允许在同一个作用域内重复声明一个变量, 我们来看几个例子
我们看到,上面三个例子全部都是错误的,都是在同一作用域内,重复声明了一个变量a
顶层对象的属性
JavaScript中的顶层对象, 在浏览器环境中指的是window对象。 在Node中指的是global对象. 在原来的ES5中,顶层对象的属性与全局变量是等价的...这句话怎么理解呢,我们来看一个例子就理解了
window.a = 1; a // a此时为1 a = 2; window.a //此时 window.a 为2
从上面的代码中可以看到, 顶层对象的属性赋值 和 全局变量的赋值,是同一件事. 很多人都认为,顶层对象的属性与全局变量挂钩,是JavaScript语言最大的设计败笔之一.
但是,在ES6中,let命令,const命令声明的全局变量, 都不再属于顶层对象的属性. 如下面的例子
var a = 0; window.a // 此时,window.a 也是0 let b = 1; window.b // 此时,window.b是undefined
上面的例子就可以看出, 使用var声明并且初始化了全局变量a, 它同时也是顶层对象window的属性。
继而,我们又用let声明并初始化了全局变量let, 但它就不是顶层对象window的属性,所以此时window对象并没有b属性,所以返回undefined