若是论正经的变量声明关键词,不抬杠那些 function
、class
等,在 ES6
之后,存在三个
const
let
var
推荐程度从上至下
var
作为一名老牌选手,陪着 JavaScript
出生到现在,但是它是存在问题的,当然在 ES6
之前,那是没得选,毕竟是声明变量唯一的存在
触目可及的一些问题
- 提升
- 全局属性
- 重复声明
- 没有块级作用域
提升
使用 var
声明时,会将声明语句提升至当前作用域的顶部,这导致可以提前使用变量,而不会报错,只是值是 undefined
这怎么说,要吃饭,先煮饭。提升则是,在煮饭完成前,就直接吃
console.log(a) // undefeind
var a = 10
console.log(a) // 10
实际的执行状况,会声明与赋值语句拆开
var a
console.log(a) // undefeind
a = 10
console.log(a) // 10
let
补上了这一点,杜绝此类事物的发生
若是在声明语句之前使用声明的变量,会导致异常直接终止程序
也就是在完成之前,强行打开高压锅,那就直接炸了
console.log(a) // ReferenceError
- var a = 10
+ let a = 10
console.log(a)
这种表现说专业点,叫做暂时性死区
从而影响了 typeof
,让它的使用,有了错误的可能性
全局属性
若是在函数之外声明变量,也就是全局作用域下使用 var
,会生成全局属性,挂载到 window
身上
var a = 10
window.a // 10
这种状况下,可能会导致覆盖掉 window
上的某个属性,还浑然不知
var open = false
window.open() // TypeError
这种情况可以使用 IIFE
来回避
(function () {
var open = false
window.open() // OK
})()
全局变量是魔鬼!
使用 let
可以很自然避开这一点,当然这不是在提倡在全局作用域下声明,不管怎样,都套上 IIFE
,而函数库默认通过 window
暴漏核心函数
重复声明
在同一个作用域下,可以存在多次声明
var a = 10
console.log(a) // 10
var a = 20
console.log(a) // 20
换成 let
再试试
- var a = 10
+ let a = 10
console.log(a)
- var a = 20
+ let a = 20
console.log(a)
没有输出,这种写法一旦存在,直接导致语法错误,没有跑的机会,早早扼杀这种低级错误
没有块级作用域
看一段循环,这情况会怎么样?
直觉告诉我,会是 1、2、3
,而真实状况是 4、4、4
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
循环属于微任务,而定时器属于宏任务
等待所有的微任务执行完毕之后,再以队列形式处理宏任务。但是此处没有块级作用域,它们所输出的是同一个 i
,并且是循环结束之后,不满足 i <= 3
的 i
,于是输出了4
可以通过 IIFE
,来这段理想的操作
for (var i = 1; i <= 3; i++) {
(function(i) {
setTimeout(function() {
console.log(i)
}, 0)
})(i)
}
慢着,为什么要这么麻烦,就不能拿变量记录一下吗
for (var i = 1; i <= 3; i++) {
var num = i
setTimeout(function() {
console.log(num)
}, 0)
}
不行,这导致了[[#重复声明]],看似有三个,但事实上操作的是同一个变量,于是变量 num
的值就成了3
可以看作是这样
var num = 1
num = 2
num = 3
console.log(num) // 3
//...
何为块级作用域
块,指的是代码块
// 全局作用域
let a = 1;
(function () {
// 函数作用域
let a = 2;
{
// 块级作用域
let a = 3
console.log(a) // 3
}
})();
这种写法就会让人很模糊,甚至说不该出现,然而 var
并不存在块级作用域,于是谁也说不准到底有没有
if (Math.random() > 0.5) {
var a = 10
}
console.log(a) // ???
当存在块级作用域时,可以明确,这个条件不管是否成立,外界都无法拿到,可以放心报错
if (Math.random() > 0.5) {
- var a = 10
+ let a = 10
}
console.log(a) // ReferenceError
化解
回到前文,因为块级作用域的存在,可以自然达成想要的效果
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
把循环拆开来看,就能明白为什么是块级作用域干的
{
var i = 1
setTimeout(function () {
console.log(i)
}, 0)
}
{
var i = 2
setTimeout(function () {
console.log(i)
}, 0)
}
{
var i = 3
setTimeout(function () {
console.log(i)
}, 0)
}
{
let i = 1
setTimeout(function () {
console.log(i)
}, 0)
}
{
let i = 2
setTimeout(function () {
console.log(i)
}, 0)
}
{
let i = 3
setTimeout(function () {
console.log(i)
}, 0)
}
留下一个片段提供思考,这会成为一个三连,还是顺子?
for (var i = 1; i <= 3; i++) {
let num = i
setTimeout(function () {
console.log(num)
}, 0)
}
结尾
文中并没有去深究一些东西,只是在说明 var
不完美的一面。当然,就算是没有 let
与 const
,也早已可以自由把控这些不完美的方方面面