前言
在讲var,let和const的区别之前,我们先来了解一下块级作用域
块作用域:
广义来讲:{}内就算一个块级作用域
所以if判断,for循环,function函数等这些有{}包围的地方就可以都算做块级作用域
1. var
var是用于用于声明一个全部范围或是函数范围的变量,并且可以为其初始化一个值。它是在ES5之前的变量声明的唯一关键字。
变量提升
我们在全局作用域或是局部作用域中,使用var关键字声明变量,都会被提升到该作用域的顶部。
function test() {
if (false) {
var value = 'hello'
} else {
console.log(value);
}
console.log(value);
}
test()
输出如下
在上述代码中,if代码块里面的代码都没有执行,为啥value还能获取到?
这就是因为var的变量提升,在JS的编译引擎中,在进行代码预编译时,会自动将所有代码里面的var关键字声明的语句都提升到当前的作用域的顶端,所以上述代码会被解析成下面这样:
function test() {
var value
if (false) {
var value = 'hello'
} else {
console.log(value);
}
console.log(value);
}
test()
在全局作用域下使用var声明一个变量,默认他是挂载在顶层对象window对象下(node是global)
var value = 1
console.log(window.value);
输出如下
用var声明的变量的作用域是它当前的执行上下文,可以是函数也可以是全局。
var value = 1
function test() {
var value = 2
console.log(value);
}
test()
console.log(value);
输出如下
如果在函数中没有使用var声明变量,而是使用
var value = 1
function test() {
value = 2
console.log(value);
}
test()
console.log(value);
输出如下
如果赋值给未声明的对象,当代码执行时,该变量会被隐式的创建为全局变量。
a = 1
console.log(window.a);
function b() {
b = 2
}
b()
console.log(window.b);
输出如下
暂时性死区
var因为变量提升的原因,我们可以做到先使用再声明
for循环中使用var
var关键字平时在for循环中的使用与我们理解中的for循环使用可能有所出入,在普通的for循环中使用var定义变量,与我们正常使用for循环的输出一样,但当我们在for循环中出现了异步任务时,var关键字所定义的变量就会与我们日常使用的有所出入:
for(var i=0;i<=5;i++){
console.log(i);
}
console.log('------------');
for(var i = 0;i<=5;i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
上述代码,在我们的认知中,变量i当循环第一次执行时为0,随后进行判断,i是否小于等于5,再输出i,最后i++,我们所输出的i应该为0,1,2,3,4,5。可事实如此吗?
我们看代码输出,第一个for循环中,代码的输出与我们所认知的一模一样,但在第二个for循环中,缺输出的都是6,按常理来说,这是不科学的,为啥会输出都是6呢?
var关键字,所定义的变量缺少块级作用域,它只用全局作用域和函数作用域,在我们调用这个for循环时,var定义的i由于只有全局作用域或函数作用域,在第一个循环中,var定义的i只有全局作用域,他会在全局作用域中创建一个变量i,在输出中,输出的都是同一个i,因为每次都是先输出再i++,所以没有影响。 在第二个循环中就不一样了,它与第一个循环是一样的,但不同的是,循环中有一个setTimeout函数,setTimeout里面的是异步事件,for循环时同步事件,只有当同步事件执行完后才会执行异步事件。因为我们一直在调用的是同一个var,当同步事件结束后,var已经变成了6,此时才会执行异步事件,此时输出的i都为6。
为了解决这一问题,在ES6时,我们提出了新的定义变量关键字let
2. let
let是ES6中所提出来的新的一个声明变量的关键字,与var相比他做出了如下的变化:
- 存在块级作用域。
- 出现了暂时性死区。
- 变量不可重复声明。
块级作用域
在前言中,我们解释了何为块级作用域,var所声明的变量由于缺少块级作用域,在循环中使用,我们所获的i都是同一个,在普通循环中可能问题不大,但当我们在循环中引入异步任务后,就会出现很大的问题。在let关键字出来后,我们就可以解决这一问题了。
function test() {
let value = 5
if (true) {
let value = 10
}
console.log(value);
}
test()
代码输出
上述代码中,在使用var来定义变量时,输出的value的值应该为10,但当使用let来定义变量时,输出的值为5,这由于let关键字,他所声明的变量仅在块作用域内有效。
也就是说,test()函数中,存在两个块级作用域,两个块级作用域中各有一个value,这两个value的值毫无关系,不会互相影响。
暂时性死区
let关键字所定义的变量是否存在变量提升,众说纷纭,有的认为存在,有的就认为不存在,但无论let存不存在变量提升,它都无法做到像var一样,先使用后定义这种先上车后买票的流氓行为。在let中存在这暂时性死区,正是这一东西限制了let,让他无法做到先使用后定义。
所谓的暂时性死区指的是:let,const这种变量声明所绑定的块级作用域的顶部,一直到变量声明语句执行完成之前,变量不能访问和修改的区域,称为这个变量的暂时性死区。
console.log(a);
let a = 1
let b = 2
console.log(b);
当我们在使用暂时性死区的时候,暂时性死区是基于执行顺序(时间)上的,而不是编写代码顺序(位置)上的。只要变量的访问和修改代码的执行,是在变量声明之后,就是合法的
不可重复声明
相比于var的重复声明会被忽略,let在同一作用域中对同一标志符的变量不能重复声明。
这一约定,避免了同一作用域下不同位置的变量声明冲突,消除了许多不容易遇见的运行问题。
const
const的使用方式与let基本上上一样,但两个关键字最大的区别在于,const所声明的所声明的变量不能修改其指针,但我们可以修改它的值。
比如,我们定义一个对象,我们可以修改对象里面的属性,但我们不可以重写整个对象。
const person ={
name : 'zz',
age : 22
}
person.name = 'z'
console.log(person);
person = {}
console.log(person);
const a = 1
a = 2
console.log(a);
总结
var,let,const三者最大的区别如下:
- var存在变量提升,可以重复声明变量,没有块级作用域
- let为了防止变量提升,出现了暂时性死区,出现了块级作用域,变量不能重复声明
- const在let的基础上,又增加了不能修改变量的指针