var 和 let 区别
var
(ES5) 和let
(ES6) 区别
变量提升(声)
在 JS 代码的编译阶段:把当前作用域中所有带 var
/ function
关键字的进行提前的声明和定义 【变量提升机制】
var
的只是提前声明(declare)var a;
,如果只声明没有赋值,默认值是undefined
。1
2
3
4
5
6
7console.log(a);
var a = 13;
// 等价于
var a; // 只声明没有赋值,默认为undefined
console.log(a);
a = 13;- 输出:
undefined
- 输出:
function
不仅声明,而且还定义了(defined),准确来说就是让变量和某个值进行关联。1
2
3
4greet(); // 输出: "Hello"
function greet() {
console.log("Hello");
}
let 和 var 的区别
let
和 const
不存在变量提升机制
创建变量的六种方式中:
var / function
有变量提升let / const / class / import
没有这个机制
var
允许重复声明,而 let
不允许重复声明
所谓重复:不管之前通过什么方法,只要当前栈内存中存在了这个变量,我们再次声明这个变量就是重复声明。
在相同的作用域(或执行上下文中)
- 使用
var/function
关键词声明变量,允许重复声明。声明第一次之后,之后再遇到就不会再重复声明了。- 要注意
let
和var
在同一作用域中的冲突
- 要注意
- 使用
let/const
关键词声明变量,不允许重复声明。浏览器会校验当前作用域中是否已经存在这个变量了,如果已经存在了,则再次基于let
等重新声明就会报错
1 | var a = 12 |
1 | console.log(a) // => 这行代码就已经不会执行了 |
1 | console.log(a) |
暂时性死区问题
let
能解决typeof
检测时出现的暂时性死区问题
let
比var
更严谨
1 | console.log(a) |
typeof a
本应报错,未报错
1 | console.log(typeof a) |
使用 let
后
返回:不能在a
被定义之前使用它,解决暂时性死区问题。
1 | console.log(typeof a) |
暂时性死区 (TDZ) 是指在
let
和const
变量被正式声明之前,这些变量在其作用域内是不可访问的。即使这些变量在代码中已声明,直到声明位置代码被执行前,它们都处于一个死区状态。在此状态下,如果尝试访问这些变量,将会导致
ReferenceError
。
给 window 设置对应的属性
隐式声明:相当于给全局对象window设置了一个属性 a
1
2
3a = 10 // 相当于给全局对象window设置了一个属性a
console.log(a)
console.log(window.a) // => window.a = 10var
创建:在全局作用域下声明了一个变量 b(全局变量)。- 在这种情况下,也相当于给全局对象 window 增加了一个对应的属性 b。【只有全局作用域具备这个特点】
1
2
3var b = 14
console.log(b)
console.log(window.b) // => window.b = 14let
创建:仅仅在全局作用域下声明了一个变量 c(全局变量)。- 在这种情况下,并未给全局对象 window 增加对应的属性 c
1
2
3let c = 15
console.log(c) // => 15
console.log(window.c) // => undefined
经典问题
变量提升
1 | var a = 99; // 全局变量a |
若是对上述代码进行一些变体:
1 | var a = 99; // 全局变量a |
在函数
f
内部,let a = 10
声明会创建一个局部变量a
,它遮蔽了全局变量a
。在函数体内,
let a = 10
的声明会被提升到函数作用域的顶部,但尚未初始化。在初始化之前对a
的访问会引发ReferenceError
,因为let
变量在 TDZ 内是不可访问的。全局的a
不会被引用,因为局部a
优先级更高。
ES6可以用let定义块级作用域变量
块级作用域
在ES6之前,我们都是用 var
来声明变量,而且 JS 只有函数作用域和全局作用域,没有块级作用域,所以{}
限定不了var声明变量的访问范围。
var
的作用域是函数作用域或全局作用域。- 块级作用域的
{}
对var
没有影响。在其中声明的var
变量会被提升到整个函数或全局范围内。 - 在函数内部声明的
var
变量会被提升到函数的顶部,并且在整个函数体内都可以访问。 - 在全局范围内声明的
var
变量会成为全局对象的属性,并且在任何地方都可以访问。
1
2
3
4{
var i = 9;
}
console.log(i); // 9- 块级作用域的
let
:可以声明块级作用域的变量。【ES6新增】1
2
3
4{
let i = 9;
}
console.log(i); // => ReferenceError: i is not defined
for 循环问题
JS中的 for 循环体比较特殊,每次执行都是一个全新的独立的块作用域,用let声明的变量传入到 for循环体的作用域后,不会发生改变,不受外界的影响。
使用
var
- 知识点: JavaScript 的事件循环机制。
- 参考:JS事件循环 (Event Loop) | Fred’s Blog
1
2
3
4
5
6
7for (var i = 0; i <10; i++) {
setTimeout(function() { // 同步注册回调函数到 异步的 宏任务队列。
console.log(i); // 执行此代码时,同步代码for循环已经执行完成
}, 0);
}
// 输出结果:
10 10 10 10 10 10 10 10 10 10使用
let
- i 虽然在全局作用域声明,但是在 for 循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
1
2
3
4
5
6
7for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // i 是循环体内局部作用域,不受外界影响。
}, 0);
}
// 输出结果:
0 1 2 3 4 5 6 7 8 9