一、ES5中的变量var
在我们ES6出现之前,我么声明变量一般都是使用 var,var最大的特点就是变量提升机制:
在函数作用域或者全局作用域中通过var声明的变量,不管处于什么位置,都会被当做在当前作用域的最顶端,当前作用域内任意位置都可以以访问
function test(bool) {
console.log(`${val} 111`)
if (bool) {
console.log(`${val} 222`)
var val = 11;
} else {
console.log(`${val} 333`)
return null
}
console.log(`${val} 444`)
}
test(true)
console.log('分割线~~~~~~')
test(false)
结果显示,在var 声明之前访问变量没有报错,只是值为 undefined
二、ES6中的块级声明
块级声明用于声明在指定块作用域之外无法访问的变量。块级作用域存在于:
- 函数内部
- {} 块中
let声明
let的基本用法和var基本相同,区别在于:
- let声明不会变量提升
- 不可重声明(已经声明过的变量不可以通过let复写)
// let声明变量不提升
function test(bool) {
if (bool) {
console.log(`${val} 222`) // 直接报错
let val = 11;
}
}
test(true)
console.log('分割线~~~~~~')
test(false)
<script>
// 不可重声
var test = 'wy';
let test = 'ypp'; // Uncaught SyntaxError: Identifier 'test' has already been declared
</script>
const声明
const声明,也称之为常量声明;声明之后不可修改,主要特点是:
- const声明不会变量提升
- 不可重声明(已经声明过的变量不可以通过let复写)
- 不可修改(引用类型的值可以修改内部属性和值,基本类型不可更改)
- 不可声明一个空的常量
// 常量定义
const obj = {};
obj.name = 'wy';
const arr = [];
arr[0] = 1;
const str = 'wy';
str = 'ypp'; // test.html:18 Uncaught TypeError: Assignment to constant variable.
const bool; // Uncaught SyntaxError: Missing initializer in const declaration
<script>
// 不可重声
var test = 'wy';
const test = 'ypp'; // Uncaught SyntaxError: Identifier 'test' has already been declared
</script>
// 不可变量提升
console.log(val); // test.html:15 Uncaught ReferenceError: Cannot access 'val' before initialization
const val = 'wy';
临时死区(Temporal Dead Zone TDZ)
在let 和 const 定义的变量之前调用变量,会报错,这时候就引出了临时死区的概念:在JS预编译初始化let 和const声明的变量时,会将其加入临死死区,而此时访问该变量会导致报错,只有当变量被赋值的时候,才会从临时死区移除,才可以被读取。
循环中的特殊使用
我们一般在for循环中,经常会遇到这样的情况:
var arr = [];
for (var i = 0; i < 10; i++) {
arr.push(function () {
console.log(i)
});
}
arr.forEach(function (func) {
func() // 弹 10次 10
})
这个时候发现我们取到的i值我们我们想要的(0~9),因为var变量的申明提前,导致循环结束之后还能使用,且此时的值就是循环之后的最终值。
当然我们可以使用闭包的形式解决这个问题:
var arr = [];
for (var i = 0; i < 10; i++) {
(function (num) {
arr.push(function () {
console.log(num)
});
})(i)
}
arr.forEach(function (func) {
func() // 弹 0 ~ 9
})
但 let 可以更简单的解决这个问题:
var arr = [];
for (let i = 0; i < 10; i++) {
arr.push(function () {
console.log(i)
});
}
arr.forEach(function (func) {
func() // 弹 0 ~ 9
})
注:因为for循环中的循环基数一直在被修改,所以const不可使用,但是在对象的 for-in方法中可以使用:
var obj = {
name: 'wy',
age: 33,
sayName: function () {
return this.name
}
}
var arr = [];
for (const key in obj) {
arr.push(function () {
console.log(key)
})
}
arr.forEach(function (func) {
func() // 输出 name age sayName
})
全局块作用域的绑定
在我们使用var 去声明全局环境变量的时候(浏览器内 window上),同时也是将属性绑定为window的一个方法,并且可以覆盖原本window上的同名方法:
console.log(window.test) // undefined
console.log(window.RegExp) // ƒ RegExp() { [native code] }
var test = 'wy';
var RegExp = /\d/;
console.log(window.test) // wy
console.log(window.RegExp) // /\d /
而 使用 let 或者 const 则是另外一种表现:
console.log(window.test) // undefined
console.log(window.con) // undefined
console.log(window.RegExp) // ƒ RegExp() { [native code] }
console.log(window.alert) // ƒ alert() { [native code] }
let test = 'wy';
const con = '12345678';
let RegExp = /\d/;
const alert = 123456;
console.log(window.test) // undefined
console.log(window.con) // undefined
console.log(window.RegExp) // ƒ RegExp() { [native code] }
console.log(window.alert) // ƒ alert() { [native code] }
总结
ES6中的块级声明让我们在使用JS变量的过程中有了更多的选择,所以我们应该有更好的使用习惯:
- 如果是需要一个全局变量(后面可修改),使用var
- 如果是声明一个常量(不被修改,或者不重新复制的引用类型),尽量使用const
- 在for循环或者其他函数中等块作用域中,声明一个可修改的变量,使用let,并且尽可能在作用域最顶端声明,防止出现临时死区。