一、先搞懂 ES6 是什么
- JavaScript 是弱类型的动态语言,名字蹭了 Java 的热度,但两者没有关系。
- 早期设计目标很单纯:给网页加交互(幻灯片、按钮响应),所以是浏览器的副产品,语言本身有些没经过深思熟虑的瑕疵。
- JS 的语言标准是 ECMAScript(ES),JS 是这套标准的实现。
- ES6(2015)是一个分水岭:此前是 ES5,此后叫 ES6+。从这一版起,JS 开始向企业级大型项目开发演进。
- 学习路径:
DOM 编程 → ES5 → ES6+。
二、作用域(scope)
1. 三种作用域
| 作用域 | 产生方式 |
|---|---|
| 全局作用域 | 在最外层声明 |
| 函数局部作用域 | 函数体内的变量 |
块级作用域 {} | if、for、单独的代码块 |
变量属于作用域——这是理解一切的前提。JS 是弱类型,变量声明时不用写类型,类型由值决定。
// 全局作用域
var height = 200;
function setWidth() {
// 函数局部作用域
var width = 100;
console.log(width, height); // 100 200,内层可以读到外层
}
setWidth();
// console.log(width); // ReferenceError:局部变量在外面不存在
2. 变量查找规则:逐层向外冒泡
- 先在当前作用域找;
- 找不到就向外层作用域找(冒泡查找);
- 一直找到全局作用域还没有,就停下来报错。
3. 内存视角:变量的生命周期
- 声明一个变量 = 在内存中申请了一块区域。
- 函数 / 代码块执行结束后,作用域销毁,内存被垃圾回收,变量随之消亡。
4. 三种典型报错
| 报错 | 触发场景 |
|---|---|
Assignment to constant variable | 给 const 重新赋值 |
ReferenceError: XXX is not defined | 变量从未声明 / 拼写错误 / 不在当前作用域 |
ReferenceError: Cannot access 'pizza' before initialization | 在 let/const 声明之前访问它(暂时性死区) |
三、var / let / const 三者对比
早期 JS 只有 var,没有常量,靠程序员自己用代码规范约束「这个值不许改」(比如全大写命名 PI、CHATMODEL)。ES6 补上了 let 和 const。
var | let | const | |
|---|---|---|---|
| 块级作用域 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 声明时赋值 | 可选 | 可选 | 必须(除声明对象属性外不能留空) |
| 能否改变值 | 可以 | 可以 | 简单类型不可改;复杂类型「值可改、指向不可改」 |
| 能否改变类型 | 可以 | 可以 | 不可以 |
| 变量提升 | 有(值为 undefined) | 有提升但处于暂时性死区(TDZ) | 同 let |
const = constant variable,字面意思「不可变的变量」。
const PI = 3.1415926;
let a; // undefined,let 允许先声明后赋值
使用建议
- 默认用
const,确认这个值后面要变,再换成let。 - 不要再用
var:它不支持块级作用域,是历史遗留。 let虽然可以改类型,但不要这么写,不好维护:
let point = 50;
point = 21; // 正常:值变了,类型仍是 number
point = "21"; // 语法允许,但不好维护
四、核心考点:for + setTimeout
这是最能体现 var 与 let 差异的场景。
for (let i = 0; i < 10; i++) {
console.log(i); // 同步代码,尽快执行
setTimeout(function () {
console.log(`this number is ${i}`); // 异步代码,延迟执行
}, 1000);
}
for循环每一轮都会创建一个块级作用域,if语句每次执行同理。- 用
let:每轮循环的i都是独立的局部变量,回调里捕获的是各自的i,1 秒后依次打印0 1 2 ... 9。 - 换成
var:整段只有一个i,循环结束时同步代码已经把i加到 10,1 秒后所有回调打印的都是 10。 - 根因是
var不支持块级作用域,闭包捕获的是同一个变量。
五、const 与复杂数据类型
const 锁住的是绑定(引用),不是对象内部的属性。
const person = {
name: 'i',
age: 18
};
person.age++; // ✅ 合法:对象内部的值可以改
console.log(person); // { name: 'i', age: 19 }
person = "111"; // ❌ TypeError: Assignment to constant variable
一句话记忆:简单数据类型不可改值,复杂数据类型值可改、类型和指向不可改。
六、变量提升(hoisting)
JS 的执行分两个阶段,这是提升现象的来源:
- 编译阶段:准备执行上下文(变量环境)。此阶段就已经登记了变量名,所以
var pizza在编译后是undefined。 - 执行阶段:赋值操作在这一阶段才真正发生。
console.log(pizza); // undefined —— var 提升后能读到,但值是 undefined
var pizza = 'Deep Dish';
提升被认为是个不好的设计:它让代码的行为和书写顺序、人的直觉不一致,容易出现「变量莫名其妙是 undefined」的 bug。应当主动避免依赖变量提升。
console.log(pizza); // ❌ ReferenceError: Cannot access 'pizza' before initialization
let pizza = 'Deep Dish';
需要精确表述的一点:let / const 并非没有提升——它们同样会被登记到作用域中,只是从块开始到声明语句之间处于暂时性死区(TDZ),在死区内访问会直接抛错,而不是得到 undefined。所以实践上表现为「必须先声明后使用」。