你可以把JavaScript语言想象成一部手机的操作系统。ES5(如Android 4.0)功能基础,而**ES6(ECMAScript 2015)** 就像一次重大的系统版本更新(如Android 8.0),它带来了全新的语法糖、更强大的API和更现代的编程范式,是现代前端开发的"基石"。它之后,JavaScript以"年号"命名,如ES2016, ES2017,统称ES6+。
想象ES6+的语法是"未来世界"的通用语。旧版浏览器(IE等)是"古代人",听不懂。Babel 就像一个"实时翻译官",在代码交付给浏览器前,将ES6+代码"翻译"成ES5代码,确保兼容性。在Node.js或Webpack等工具中,我们配置好这个"翻译官",就能愉快地书写现代代码了。
一、ES6 简介与开发环境
1.1 什么是 ES6? (ECMAScript 2015)
- ES6 是 ECMAScript 2015 的简称,是 JavaScript 语言的下一代标准,于 2015 年发布。
- 它为 JavaScript 带来了大量新特性 (如箭头函数、Promise、类等),是现代前端开发的基石。
- 类比:如果 JavaScript 是一门语言,ECMAScript 就是它的 "语法规范",ES6 就是这套规范的 "重大升级版本"。
1.2 环境配置
- 浏览器支持:现代浏览器(Chrome、Firefox、Safari、Edge)对 ES6 支持度很高,但旧版浏览器(如 IE)不支持。
- Babel :一个 JavaScript 编译器,核心作用是将 ES6+ 语法转换为 ES5,以兼容旧环境。
- Node.js & 打包工具 :
- Node.js 环境可直接运行大部分 ES6+ 特性。
- 结合 Webpack/Vite 等打包工具,可在项目中统一处理 ES6+ 转换和模块打包。
二、变量与常量声明
从 var的"混乱作用域"走向 let/const的"精确作用域",让变量的生命周期更清晰、可预测。
2.1 let 关键字
let 用于声明块级作用域的变量,具有以下特性:
块级作用域 :仅在 {} 内有效。let把变量囚禁在 {}这个"监狱"里,出了监狱它就失效。这解决了 var的变量泄露问题。
javascript
if (true) {
let prison = '囚犯';
var outsider = '外人';
}
console.log(outsider); // '外人',var逃出了if块
console.log(prison); // ReferenceError! let被关在{}内,无法访问
不存在变量提升:必须先声明后使用。
javascript
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
暂时性死区(TDZ) :在代码块内,使用 let 声明变量之前,该变量不可用。即在 let声明之前,变量仿佛进入了一个"死亡区域",无法访问。这本质是为了在语法上强制"先声明,后使用"的好习惯 ,避免未知的undefined值。
不允许重复声明 :同一作用域内不能用 let 重复声明同一变量。
新手误解点 :let没有变量提升?不,准确说它有"提升",但未被初始化。引擎知道它的存在,但在声明语句执行前不允许你访问,这个状态就是"暂时性死区"。
2.2 const 关键字
const 用于声明常量 ,特性与 let 类似,但有以下特殊点:
保护的是"内存地址" :这是最核心的点。const声明的变量,好比一个贴在瓶子上的固定标签。标签不能换(不能重新赋值)。
-
对于基本类型(Number、String、Boolean),值不可变。如果瓶子里装的是水(基本类型:字符串、数字等),水和瓶子一体,所以"水"也不能变。
-
对于引用类型 (对象/数组 ),内存地址不可变 (但内部属性可修改)。如果瓶子里装的是一个可变的玩具(对象/数组) ,
const只保证标签贴在这个玩具上,不保证你不能拆解、改装这个玩具。
javascript
const bottle = ['乐高A', '乐高B']; // 标签贴在乐高集合上
bottle.push('乐高C'); // ✅ 可以!改装乐高集合本身
bottle = ['新玩具']; // ❌ 报错!试图把标签撕下来贴到另一个玩具上。
声明时必须初始化:
javascript
const PI; // SyntaxError: Missing initializer in const declaration
const PI = 3.14; // 正确
不允许重复赋值:
javascript
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 允许,修改的是对象属性
obj = { name: 'Charlie' }; // TypeError: Assignment to constant variable
2.3 对比 var、let、const
| 特性 | var |
let |
const |
|---|---|---|---|
| 作用域 | 函数级 / 全局 | 块级 | 块级 |
| 变量提升 | 有 | 无 | 无 |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 初始化要求 | 无 | 无 | 必须初始化 |
新手易错点 :优先使用
const,需要重新赋值时用let,尽量避免var。
三、函数增强
3.1 箭头函数
箭头函数是 ES6 最常用的特性之一,语法简洁且 this 指向明确。
基本语法 :省去 function和 return。
javascript
// 传统函数
const add = function(a, b) { return a + b; };
// 箭头函数
const add = (a, b) => { return a + b; };
// 单行表达式可省略 `{}` 和 `return`
const add = (a, b) => a + b;
// 单个参数可省略 `()`
const square = x => x * x;
核心特性:
没有自己的 this :这是最核心的特性 。箭头函数的 this就像"透明的",它直接继承定义时所在外层代码块 的 this值,且一旦绑定,永不改变 。这解决了传统函数中 this指向混乱的经典问题。
javascript
function Timer() {
this.seconds = 0;
// 传统函数,`this` 指向调用它的对象(这里会是window或undefined)
setInterval(function() {
this.seconds++; // ❌ 这里的this不是Timer实例!
}, 1000);
// 箭头函数,`this` 继承自Timer函数(即Timer实例)
setInterval(() => {
this.seconds++; // ✅ 这里的this就是Timer实例!
}, 1000);
}
不能用作构造函数 :因为它没有自己的 this,自然无法 new。
没有 arguments 对象:可用 Rest 参数替代。
3.2 函数参数默认值
允许为函数参数设置默认值,当参数未传递或为 undefined 时使用默认值。给参数一个"保底"值,避免 undefined捣乱。function greet(name = '访客')。
javascript
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Guest!
greet('Alice'); // Hello, Alice!
3.3 Rest 参数
使用 ... 将函数的剩余参数收集为一个数组,用于处理不定数量的参数。它像一个"吸尘器",把函数多余的实参收集成一个真正的数组。完美替代了原本的类数组对象 arguments。
javascript
function sum(...nums) {
return nums.reduce((acc, curr) => acc + curr, 0);
}
sum(1, 2, 3); // 6
四、字符串与正则表达式
4.1 模板字符串
使用反引号 ````` 定义,,是字符串的超级模式解决了传统字符串拼接的痛点。
多行字符串 :直接换行即可,无需 \n拼接。
javascript
const str = `这是第一行
这是第二行`;
变量 / 表达式插值 :用 ${} 嵌入变量或表达式。${expression},可以放入任何有效的JS表达式,它会自动转换为字符串拼接进去。这本质是将数据和展示更清晰、更安全地分离。
javascript
const name = 'Alice';
const age = 25;
const info = `Name: ${name}, Age: ${age + 1}`; // Name: Alice, Age: 26
4.2 字符串扩展方法
str.includes(s):判断字符串是否包含指定子串,返回布尔值。str.startsWith(s):判断字符串是否以指定子串开头。str.endsWith(s):判断字符串是否以指定子串结尾。str.repeat(n):将字符串重复n次,返回新字符串。
javascript
'Hello'.includes('e'); // true
'Hello'.startsWith('H'); // true
'Hello'.endsWith('o'); // true
'Hi'.repeat(3); // 'HiHiHi'
4.3 正则表达式扩展
u修饰符(Unicode 模式):正确处理 4 字节的 Unicode 字符(如 emoji)。y修饰符(粘连匹配) :要求匹配必须从剩余字符串的第一个位置开始。
五、解构赋值
本质是从一个复杂的"数据包裹"(对象或数组)中,精准、快速地提取出你需要的数据项,并赋值给对应的变量。
5.1 数组的解构赋值
按位置提取数组中的值赋给变量。
-
基本用法 :
javascriptlet [a, b] = [1, 2]; console.log(a); // 1 console.log(b); // 2 -
嵌套解构 :
javascriptlet [a, [b, c]] = [1, [2, 3]]; -
默认值 :
javascriptlet [a, b = 10] = [5]; console.log(b); // 10 -
Rest 模式 :
javascriptlet [a, ...rest] = [1, 2, 3]; console.log(rest); // [2, 3]
5.2 对象的解构赋值
按属性名提取对象中的值赋给变量。
-
基本用法 :
javascriptlet { name, age } = { name: 'Alice', age: 25 }; console.log(name); // Alice -
重命名变量 :
javascriptlet { name: username } = { name: 'Alice' }; console.log(username); // Alice -
默认值 :
javascriptlet { name, gender = 'female' } = { name: 'Alice' }; -
函数参数解构 :
javascriptfunction greet({ name = 'Guest' }) { console.log(`Hello, ${name}!`); } greet({ name: 'Alice' });
六、数组的扩展
6.1 扩展运算符 ...
将数组展开为逗号分隔的序列,是 ES6 最实用的操作符之一。它和Rest参数是"一体两面":Rest是"收集",扩展是"展开"。它像一把"胡椒罐",可以把数组(一罐胡椒粉)里的内容均匀地"撒"到需要的地方。
-
函数调用 :
javascriptfunction sum(a, b, c) { return a + b + c; } const nums = [1, 2, 3]; sum(...nums); // 6 -
数组字面量 :合并数组(替代
concat)。javascriptconst arr1 = [1, 2]; const arr2 = [3, 4]; const merged = [...arr1, ...arr2]; // [1, 2, 3, 4] -
复制数组 :
javascriptconst oldArr = [1, 2, 3]; const newArr = [...oldArr]; // 浅拷贝
6.2 新增的数组方法
-
Array.from():将类数组对象 或可迭代对象 转为数组。可以理解为将"像数组的东西"(如arguments,NodeList)变成"真正的数组",以便使用数组方法。javascriptArray.from('hello'); // ['h', 'e', 'l', 'l', 'o'] -
Array.of():将一组值转为数组(解决Array()参数个数不同导致行为差异的问题)。javascriptArray.of(1, 2, 3); // [1, 2, 3] -
arr.find(fn)/arr.findIndex(fn):查找符合条件的第一个元素 / 索引, 返回第一个 满足条件的元素 。findIndex返回其索引 。javascript[1, 2, 3].find(x => x > 2); // 3 [1, 2, 3].findIndex(x => x > 2); // 2 -
arr.includes(item):判断数组是否包含某元素(比indexOf更直观,可识别NaN)。 -
arr.flat(depth):数组扁平化,depth为扁平化深度(默认 1)。javascript[1, [2, [3]]].flat(2); // [1, 2, 3]
七、对象的扩展
7.1 属性的简洁表示法
当属性名与变量名相同时,可简写;方法也可简写。
javascript
const name = 'Alice';
const obj = {
name, // 等价于 name: name
sayHi() { // 等价于 sayHi: function() {}
console.log('Hi!');
}
};
7.2 属性名表达式
在对象字面量中,用 [expression] 作为属性名。
javascript
const propName = 'age';
const obj = {
['prop' + 'Name']: 'Alice',
[propName]: 25
};
// obj: { propName: 'Alice', age: 25 }
7.3 对象的新增方法
-
Object.is(val1, val2):更严格的相等比较(解决===中NaN !== NaN和+0 === -0的问题)。javascriptObject.is(NaN, NaN); // true Object.is(+0, -0); // false -
Object.assign(target, ...sources):将源对象的属性浅拷贝 到目标对象。javascriptconst target = { a: 1 }; const source = { b: 2 }; Object.assign(target, source); // target: { a: 1, b: 2 } -
Object.keys()/Object.values()/Object.entries():分别返回对象的键数组 、值数组 、键值对数组 。javascriptconst obj = { a: 1, b: 2 }; Object.keys(obj); // ['a', 'b'] Object.values(obj); // [1, 2] Object.entries(obj); // [['a', 1], ['b', 2]]
八、Symbol
8.1 概述
Symbol 是 ES6 新增的原始数据类型 ,用于表示独一无二的值 。创建一个全局唯一 的值,主要用于解决对象属性名冲突问题。它像工厂里为每一件产品打上的永不重复的序列号
8.2 基本用法
-
创建 Symbol :
javascriptlet s1 = Symbol('desc'); // 'desc' 是描述符,仅用于调试 let s2 = Symbol('desc'); console.log(s1 === s2); // false,即使描述符相同,值也不同 -
作为对象属性名 :防止属性名冲突。
javascriptconst mySymbol = Symbol('key'); const obj = { [mySymbol]: 'value' }; console.log(obj[mySymbol]); // 'value'
九、集合类型
9.1 Set
Set 是一种成员值唯一、无序 的数据结构。是一个值永不重复 的集合。它像一本数学意义上的"集合",自动帮你去重。
-
常用 API :
javascriptadd(value):添加值。 delete(value):删除值。 has(value):判断是否包含值。 size:获取成员数量。 clear():清空所有成员。 -
数组去重 :
javascriptconst arr = [1, 2, 2, 3]; const uniqueArr = [...new Set(arr)]; // [1, 2, 3]
9.2 Map
Map 是一种键值对集合 ,键可以是任意类型(对象的键只能是字符串或 Symbol)。它弥补了传统对象只能用字符串或Symbol作键的限制。
Object是键为字符串的电话本。Map是一个万能仓库,你可以用一把真正的钥匙(对象)、一张照片(函数) 作为"标签"来存放物品。
-
常用 API :
javascriptset(key, value):设置键值对。 get(key):获取键对应的值。 has(key):判断是否包含键。 delete(key):删除键值对。 size:获取键值对数量。
javascript
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, 'value');
console.log(map.get(objKey)); // 'value'
十、迭代器与生成器
10.1 迭代器
-
Iterable 接口 :拥有
Symbol.iterator方法的对象(如数组、Set、Map、字符串)。如果一个对象拥有[Symbol.iterator]方法,它就是"可迭代的"。这个方法返回一个迭代器对象。 -
for...of循环 :用于遍历实现了 Iterable 接口的对象。是专门为消费"可迭代对象"(数组、Map、Set、字符串等)设计的语法糖。它内部会自动调用迭代器,依次获取值。javascriptfor (let item of [1, 2, 3]) { console.log(item); // 1, 2, 3 }
核心区别 :for...in遍历键名 (适合对象),for...of遍历键值(适合数组等集合)。
10.2 生成器
生成器是一种可以暂停和恢复执行 的函数,使用 function* 定义,yield 暂停执行。生成器像一个"可多次暂停和继续的函数"。它返回一个迭代器 。每次调用迭代器的 next(),函数就从上次 yield处恢复执行,直到下一个 yield或 return。用于实现惰性求值、异步编程(async/await的底层基础)。
-
基本语法 :
javascriptfunction* gen() { yield 1; yield 2; return 3; } const g = gen(); g.next(); // { value: 1, done: false } g.next(); // { value: 2, done: false } g.next(); // { value: 3, done: true } -
核心特性 :惰性求值,每次调用
next()才执行到下一个yield。
十一、Promise
11.1 异步编程的背景
传统异步编程依赖回调函数,容易导致回调地狱 (嵌套过深,代码可读性差)。它是一个"异步任务收据"。它代表一个在未来才会完成(或失败)的操作及其最终结果。它解决了"回调地狱",将异步操作以同步代码的链式调用形式组织起来。
11.2 Promise 基本概念
Promise 是异步编程的一种解决方案,代表一个异步操作的最终完成或失败。
-
三种状态 :
pending:进行中。收据已开,任务未完成。fulfilled:已成功。任务成功完成,收据有了结果值。rejected:已失败。任务失败,收据知道了原因。- 状态一旦改变,就凝固了(从Pending变为Fulfilled或Rejected),不会再变。
-
创建 Promise :
javascriptconst promise = new Promise((resolve, reject) => { // 异步操作 if (/* 成功 */) { resolve('success'); } else { reject('error'); } });
11.3 Promise 实例方法
.then(onFulfilled, onRejected):处理成功或失败。.catch(onRejected):专门处理失败(推荐使用,更清晰)。.finally(onFinally):无论成功或失败都会执行。
javascript
promise
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('done'));
11.4 Promise 静态方法
Promise.resolve(value):将现有值转为 Promise 对象(状态为 fulfilled)。Promise.reject(reason):返回一个状态为 rejected 的 Promise。Promise.all([p1, p2, ...]):等待所有 Promise 成功,有一个失败则整体失败。Promise.race([p1, p2, ...]):竞速,以第一个完成的 Promise 为准。
示例:
javascript
const promise = new Promise((resolve, reject) => {
// 执行异步任务,比如请求数据
setTimeout(() => {
const success = true;
if (success) {
resolve('数据获取成功!'); // 状态变为Fulfilled
} else {
reject('获取失败!'); // 状态变为Rejected
}
}, 1000);
});
// 消费Promise
promise
.then(data => { console.log(data); }) // 成功回调
.catch(error => { console.error(error); }) // 失败回调
.finally(() => { console.log('请求结束'); }); // 无论如何都执行
11.5 Promise.all 与 Promise.race
-
Promise.all([p1, p2, p3]) :像"集体照",必须**所有人(所有Promise)都到齐(成功)** 才算成功,一个人没来(失败)就失败。
-
Promise.race([p1, p2, p3]) :像"赛跑",以**第一个冲过终点(完成)** 的选手结果为准。
新手误解点 :.then和 .catch本身返回一个新的Promise ,这使链式调用成为可能。.catch不仅能捕获前面Promise的reject,还能捕获前面 .then回调中抛出的错误。
十二、类
12.1 class 关键字
ES6 引入 class 语法,class是ES5基于原型的继承的语法糖,它提供了更清晰、更接近传统面向对象语言的写法,但底层依然是原型链。
javascript
class Person {
// 类的内容
}
12.2 类的构成
- 构造函数
constructor():创建实例时自动调用。 - 实例方法:通过实例调用。
- 静态方法 / 属性 :用
static关键字定义,通过类本身调用。
javascript
class Person {
constructor(name, age) {
this.name = name; // 实例属性
this.age = age;
}
sayHi() { // 实例方法
console.log(`Hi, I'm ${this.name}`);
}
static isAdult(age) { // 静态方法
return age >= 18;
}
}
const alice = new Person('Alice', 25);
alice.sayHi(); // Hi, I'm Alice
Person.isAdult(25); // true
12.3 继承
extends关键字:实现子类继承父类。super关键字:调用父类的构造函数或方法。
javascript
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 调用父类构造函数
this.grade = grade;
}
study() {
console.log(`${this.name} is studying`);
}
}
十三、模块化
13.1 模块化概念
ES6 引入了原生模块化系统,一个文件就是一个模块 ,解决了命名冲突和依赖管理问题。"高内聚,低耦合"。每个文件是一个独立的模块,拥有自己的作用域,只暴露需要公开的部分,按需引入其他模块的功能。就像乐高积木,每块独立制造,通过标准接口拼接成复杂系统。
13.2 export 命令
用于导出模块中的变量、函数或类,分为命名导出 和默认导出。
-
命名导出 (可多个):
javascript// module.js export const name = 'Alice'; export function sayHi() { console.log('Hi!'); } // 或统一导出 const name = 'Alice'; function sayHi() { console.log('Hi!'); } export { name, sayHi }; -
默认导出 (只能一个):
javascript// module.js export default class Person { /* ... */ } // MyClass.js export default class MyClass { ... } // main.js import MyClass from './MyClass.js'; // 导入时可以任意命名
13.3 import 命令
用于导入其他模块的内容。
-
导入命名导出 :
javascriptimport { name, sayHi } from './module.js'; // 重命名导入 import { name as username } from './module.js'; -
导入默认导出 :
javascriptimport Person from './module.js';