回顾:JavaScript 的 var 声明变量
1. 允许重复的变量声明:导致数据被覆盖。
var a = 1;
function print() {
console.log(a);
}
// 假设这里有一千行代码,重复的变量可能会导致变量被更改
var a = 2;
print(); // a = 2
2. 变量提升:怪异的数据访问、闭包问题。
怪异的数据访问:
if(Math.random() < 0.5) {
var a = "abc";
console.log(a);
}else{
console.log(a)
}
console.log(a);
// 逻辑为:如果随机数小于0.5,则声明变量a,赋值为abc,输出a;如果随机数不小于0.5,则输出a。
// 变量提升:先声明变量a,如果随机数小于0.5,赋值为abc,输出a;否则输出a。
闭包问题:
<div id="divBtns"></div>
var div = document.getElementById("divBtns");
for(var i = 1; i <= 10; i ++) {
var btn = document.createElement("button");
btn.innerHTML = "按钮" + i;
div.appendChild(btn);
// 当btn生成后,循环结束,结束之后才开始点击事件
btn.onclick = function() {
console.log(i); // 使用同一个变量 i
}
}
// 循环结束后 i = 1
3. 全局变量挂载到全局对象:全局对象成员污染问题。
var abc = "123";
console.log(window.abc); // 会导致window对象的成员被覆盖
let 声明变量
ES6不仅引入let关键字用于解决变量声明的问题,同时引入了块级作用域的概念。
let 声明的变量只在 let 命令所在的代码块内有效。
块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域。
let a = 123; // 全局作用域
{
// 块级作用域
let a = 456;
console.log(a); // 456
}
console.log(a); // 123
1. let声明的变量不会挂载到全局对象。
let a = 123;
console.log(window.a); // undefined
2. let声明的变量,不允许当前作用域范围内重复声明。在块级作用域中用let定义的变量,在作用域外不能访问。
let a = 123;
let a = 456; // 检查到当前作用域(全局作用域)已经声明了a,会报错
let a = 123; // 全局作用域
{
// 块级作用域
let a = 456;
console.log(a); // 456
}
console.log(a); // 123
3. 使用let不会有变量提升,因此不能在定义let变量之前,使用let变量。
if(Math.random() < 0.5) {
let a = "abc"; // 定义在当前块级作用域
console.log(a); // 当前块级作用域的a。
}else{
// 这是另一个块级作用域,该作用域找不到a
console.log(a)
}
console.log(a);
底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错“Cannot access 'a' before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
console.log(a); // Cannot access 'a' before initialization
let a = 123;
4. 在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的变量)。在循环中,使用let声明的循环变量,在循环结束后会被销毁。
<div id="divBtns"></div>
<script>
let div = document.getElementById("divBtns");
for(let i = 1; i <= 10; i ++) {
let button = document.createElement("button");
button.innerHTML = "按钮" + i;
div.appendChild(button);
button.onclick = function() {
console.log(i); // 使用的是当前作用域的 i
}
}
</script>
const声明常量
const 和 let 完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。
const a; // 未赋值会报错:“SyntaxError: Missing initializer in const declaration”
const a = 1;
a = 2; // 重新赋值会报错:“TypeError: Assignment to constant variable.”
实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会被随意篡改。原因如下:
- 根据经验,开发中的很多变量,都是不会更改,也不应该更改的。
- 后来的很多框架或是第三方js库,都是要求数据不可变,使用常量可以在一定程度上保证这一点。
注意细节:
1. 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
const obj = {a: 2};
obj.a = 3;
obj.x = 4;
console.log(obj); // {a: 3, x: 4}
2. 常量的命名
特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间使用下划线分割。
普通的常量:使用和之间一样的命名即可。
3. 在for循环中,循环变量不可以使用常量。