在 ES5
中,我们使用关键字 var
声明变量。但是,随着这门语言的发展,以及业务需求的不断改进,var
这个老家伙似乎存在不少问题,比如:变量提升,没有块级作用域等等。所以,在 ES6
中新增了 let
、const
两个关键字来解决这些问题。
变量提升
var
关键字带来的问题之一就是变量提升。
在讨论变量提升之前,先考虑一个问题:我们写的 js 代码或者说脚本如何被解释器解释的呢?
在此处只讨论代码如何执行,对于具体的解释过程参考后面的文章 (待更)。
首先,一个 js
脚本文件肯定不是 “无脑” 的从上到下执行的。解释器会先把使用 var
关键字声明的变量提到最前面,注意,此时并不会进行赋值操作,只是声明变量(初始化值为 undefined
)。这种现象就被称为变量提升。这种现象带来的是一种不安全的行为:
a = 2;
var a;
console.log(a);//2
上面这段代码的执行顺序是:
- 声明变量
a
- 将 2 赋值给变量
a
- 输出变量
a
的值
这很奇怪吧,也不是我们想要的效果,我们往往更期望的是一个变量必须在声明以后才能进行赋值操作,否则我们期望得到的是一个错误对象。
关键字 let
的出现就解决了变量提升的问题,使用 let
定义的变量只有在初始化以后才能对一个变量进行操作:
//正确
let a = 2;
console.log(a);
//错误
a = 2;
let a;
console.log(a);//报错,a is not defined
或许你会对上面的代码有疑问:如果在全局作用域中直接使用一个未使用任何关键字声明的变量(比如:a = 2
),那么这个变量不会被定义为全局对象 window
的属性吗?
答案就是接下来要讲的全局属性和临时死区。
全局属性和临时死区
在全局作用域中,直接使用一个未通过任何关键字声明的变量,那么这个变量就会被定义为全局对象的属性:
a = 2;
console.log(window.a === a);//true
在全局作用域中,使用关键字 var
声明的变量也会被定义为全局对象的属性:
var a = 2;
console.log(window.a === a);//ture
到了这里,可以下这么一个定论:在全局作用域中,使用 var
关键字或者不使用任何关键字声明的变量,都会被定义在全局对象上作为属性。
但是,对于前面提出的问题还没有回答:
//错误
a = 2;
let a;
console.log(a);//报错,a is not defined
你会认为先执行 a = 2
,然后此时不就在 window
上添加了一个属性 a
吗?所以打印应该输出 2,为什么会报错呢?
这里就涉及到另外一个概念:临时死区
在一个作用域中(无论是全局作用域、函数作用域甚至是块级作用域) ,凡是通过 let
或 const
关键字声明的变量,那么它就是这个作用域的唯一,不能在它们声明之前的任何地方使用,所以,在一个变量声明(通过 let
或 const
)之前的地方就称为这个变量的临时死区。 比如上面的例子 a = 2
就是一个临时死区。在临时死区中,访问变量就会抛出异常。
除了上面的情况以外,函数作用域里面也有临时死区:
var a = 2;
function foo(){
a = 3;
let a = 2;
}
foo();//报错,a is not defined
报错的原因也是一样的,都是因为在临时死区中访问了变量。
所以,let
、const
两个关键字虽然没有变量提升的问题,但是出现了一个临时死区需要我们注意。我们应该在声明变量以后再进行操作,这才是一个好的习惯。
块级作用域
在 ES5
中,并没有块级作用域这个说法,比如,条件语句,循环语句等。它们不是一个完整的作用域。没有块级作用域会导致很多不可思议的问题,例如:
for(var i = 0;i<10;i++){
console.log(i);
}
console.log(i);//10
上面的这个循环只是为了打印数字 0-9 ,但是奇怪的是,作为循环标记的变量 i
竟然在循环以外的地方还能访问,这就造成了作用域污染的问题了,这也不是我们期望的,因为这个循环标记只用在循环体里面,出了循环以外这个标记将毫无意义。所以,在 ES6
中,就引入了块级作用域的概念,条件语句,循环语句,凡是使用大括号 {} 包裹着的代码,都是一个块级作用域,在块级作用域中,如果使用 let
或 const
定义的变量,那么在这个作用域以外(下级的作用域除外)的地方就无法访问这些变量。
//块级作用域
{
let a = 2;
}
console.log(a);//报错,a is not defined
总结:凡是通过 let
或 const
定义的变量都存在块级作用域。
const
在 ES5
中,如果需要定义一个常量,那么还是使用关键字 var
,然后从编程习惯上将这个变量的所有字母都用大写来表示,让人从直观上看这是一个常量,但是本质并不是一个常量,我们仍然可以给它赋值。所以,这不是定义常量的方法。从而,为了解决这个问题,ES6
引入了关键字 const
。
在我的理解里面,const
关键字只是将一个变量在栈内存中储存的信息锁定了,比如:
const a = 2;//变量a在栈内存中保存了一个值为2,这个值不能被修改
const obj = {};//变量obj在栈内存中保存了一个对象(一个16进制的引入地址值),这个值不能改变(并不代表对象中的内容不能改变)