作用域是指变量所可作用的范围,分别有全局作用域和局部作用域。
要了解作用域就要先了解变量提升。
变量提升是指在JS中执行上下文发作方式的一种。具体表现方式就是所有通过var声明的变量会提升到当前作用域的最前面。
function foo() {
console.log(temp);
}
function bar() {
console.log(temp);
var temp;
}
foo(); // ReferenceError: temp is not defined
bar(); // undefined
复制代码
可以看到用var声明了的并不会报错。因为其实函数bar等同于
function bar() {
var temp;
console.log(temp);
}
复制代码
大多数类C语言语法的语言都拥有块级作用域。在一个代码块中定义的所有变量在代码块的外部是不可见的。定义在代码块中的变量在代码块被执行结束后会被释放掉。
尽管JavaScript的代码似乎支持块级作用域,但实际由于变量提升,并不能很好的支持。
所以在ES6中规定了let和const来支持块级作用域。但是,依旧存在暂时性死区。
let
let基本使用方法和var相同,而且声明的变量只在其块和子块中可用,这点也与var相同。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。
function foo() {
if(true) {
var temp = 5;
console.log(temp);
}
console.log(temp);
}
function bar() {
if(true) {
let temp = 5;
console.log(temp);
}
console.log(temp);
}
foo(); // 5 和 5
bar(); // 5 和 "ReferenceError: temp is not defined
复制代码
let声明的变量的作用域只是外层块,而不是整个外层函数。
我们可以利用这个特性来代替立即执行函数。
// IIFE
(function(){
var temp = xxx;
/*
other code
*/
}())
// 块级
{
let temp = xxx;
/*
other code
*/
}
复制代码
const
const的用法跟let差不多,但是const一定要初始化,不初始化是会报错的。
const temp = 4;
// 没有初始化报错
const t; // SyntaxError: Missing initializer in const declaration
复制代码
const是块级作用域,const跟let的语义相似,就是用来声明常量的,但是一但声明了就不能更改。值得注意的是const声明的变量记录的是指针,不可更改的指针,如果const所声明的是对象,对象的内容是可以更改的。
// 重新赋值声明导致报错
const PI = 3.14;
PI = 3.1415926; // TypeError: Assignment to constant variable.
// 给对象增加属性不会导致 obj 的指针变化,所以不会报错
const obj = { foo: 2 };
obj.bar = 3;
console.log(obj); // {foo: 2, bar: 3}
复制代码
暂时性死区
使用let和const声明的变量,在声明没到达之前,访问该变量都会导致报错,就连一直以为安全的typeof也不再安全。
// TDZ1
function foo() {
// TDZ 开始
console.log(typeof temp);
let temp = 5; // TDZ 结束
}
foo(); // ReferenceError: temp is not defined
复制代码
报错的是referenceError,如果使用var声明的话,temp输出应该是undefined,从let声明的变量的块的第一行,到声明变量之间的这个区域被称作暂时性死区。凡是在这个区域使用这些变量都会报错。
// TDZ2
function bar() {
console.log(typeof temp);
}
bar(); // undefined
复制代码
在函数里没有用let声明temp的时候,temp是undefined,讲道理在let声明前也应该是undefined,然而foo函数却报错了,证明了就算是在未达到let声明的地方,但是在用let之前已经起到了作用。这是不是说明其实let也有提升(这个提升并不是var的那种提升,只是有影响),只是在TDZ使用的时候报错了,而不是undefined。
事实上,当JS引擎检视下面的代码块有变量声明时,对于var声明的变量,会将声明提升到函数或全局作用域的顶部,而对let或const的时候会将声明放在暂时性死区内,任何在暂时性死区内访问变量的企图都会导致“运行时”错误(runtime error)。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除并可以安全使用。
禁止重复声明
在同一个块内,let和const不能声明相同的标识符。禁止情况包括:
let或const或let和const
var和let或const
函数参数与let或const
// let 和 let
let foo = 1;
let foo = 2;
// let 和 const
let foo = 1;
const foo = 2;
// var 与 let
var foo = 1;
let foo = 2;
// 函数参数与 let
function bar(foo) {
let foo = 1;
}
复制代码
以上情况都是会报syntaxerror。但是在嵌套的作用域内使用let声明同一变量是被允许的。
var foo = 1;
{
// 不会报错
let foo = 2;
// other code
}
复制代码
同时因为是let和const是块级作用域,声明的变量在当前块使用完之后就会被释放,所以就算使用相同的标识符也不会覆盖外部作用域的变量,而var是会覆盖外部作用域的变量的。
function foo() {
var bar = 1;
{
let bar = 2;
}
console.log(bar);
}
function zoo() {
var bar = 1;
{
var bar = 2;
}
console.log(bar);
}
foo(); // 1
zoo(); // 2
复制代码
在 ES6 的发展阶段,被广泛认可的变量声明方式是:默认情况下应当使用 let 而不是 var 。对于多数 JS 开发者来说, let 的行为方式正是 var 本应有的方式,因此直接用 let 替代 var 更符合逻辑。在这种情况下,你应当对需要受到保护的变量使用 const 。
在默认情况下使用 const ,而只在你知道变量值需要被更改的情况下才使用 let 。这在代码中能确保基本层次的不可变性,有助于防止某些类型的错误。