ES6+ 新特性详解
ES6+ 新特性详解
一、Introduction(简介)
ES6(ECMAScript 2015)是 JavaScript 语言自 2009 年 ES5 标准化以来最重要的版本更新。ES6 引入大量新语法和新特性,从根本上提升了 JavaScript 的表达能力和开发体验。此后每年发布的 ECMAScript 新版本(ES2016/ES2017/ES2018...)统称为 ES6+,不断为这门语言注入新的活力。
本篇文章将系统梳理 ES6 及后续版本中最常用的新特性,涵盖变量声明、函数增强、箭头函数、模板字符串、解构赋值、Class 语法、模块系统、Promise、async/await、Symbol、Map/Set 等核心内容。通过大量代码示例,帮助读者快速掌握并运用到实际项目中。
二、基础语法
2.1 let 与 const 变量声明
ES6 之前,JavaScript 只有 var 一种声明变量的方式,存在变量提升(hoisting)和函数级作用域的局限。ES6 引入了 let 和 const,弥补了这些缺陷。
- let:块级作用域(block scope),不存在变量提升,在声明前访问会抛出
ReferenceError(暂时性死区 TDZ)。 - const:块级作用域,声明时必须初始化,且不能重新赋值(但对对象/数组内部属性修改仍然允许)。
// var 的问题:变量提升导致意外行为
console.log(a); // undefined(变量提升,但值未定义)
var a = 10;
// let:块级作用域,不存在变量提升
{
let b = 20;
console.log(b); // 20
}
// console.log(b); // ReferenceError: b is not defined
// const:声明常量
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
// const 对象可以修改属性
const USER = { name: 'Alice', age: 25 };
USER.age = 26; // 允许
// USER = {}; // 不允许,重新赋值
console.log(USER); // { name: 'Alice', age: 26 }
2.2 箭头函数(Arrow Functions)
箭头函数是 ES6 最受欢迎的语法糖之一,用 => 符号定义函数,语法简洁且不绑定自己的 this。
// 传统函数
function sum(a, b) {
return a + b;
}
// 箭头函数
const sum = (a, b) => a + b;
// 单参数可省略圆括号
const double = x => x * 2;
// 多行函数体需要 return
const process = (x) => {
const temp = x * 2;
return temp + 1;
};
// 箭头函数不绑定 this
function Timer() {
this.time = 0;
// 传统写法需要用 that / bind(this)
setInterval(function() {
this.time++; // this 指向 window(浏览器)或 undefined(严格模式)
console.log(this.time);
}, 1000);
// 箭头函数继承外层 this
setInterval(() => {
this.time++;
console.log(this.time);
}, 1000);
}
2.3 模板字符串(Template Literals)
模板字符串使用反引号( <pre><code>const name = 'Bob'; // 字符串拼接(ES5 方式) // 模板字符串(ES6)</code>)界定字符串,支持多行文本和嵌入表达式(<code>${}</code>),告别字符串拼接的噩梦。</p>
My name is ${name}, I am ${age} years old.
const age = 30;
const bio = 'My name is ' + name + ', I am ' + age + ' years old.';
const bio2 = ;
// 多行文本
const html =
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>;
// 支持表达式
const a = 10, b = 5;
console.log(a + b = ${a + b}); // a + b = 15
// 支持函数调用
const toUpper = s => s.toUpperCase();
console.log(Hello ${toUpper('world')}); // Hello WORLD
</code></pre>
<h3>2.4 解构赋值(Destructuring)</h3>
<p>解构赋值允许从数组或对象中提取值,赋值给变量,语法优雅且功能强大。</p>
<pre><code>// 数组解构
const [first, second, third] = ['apple', 'banana', 'cherry'];
console.log(first); // apple
// 交换变量(无需临时变量)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
// 对象解构
const person = { name: 'Alice', age: 28, city: 'Shanghai' };
const { name, age } = person;
console.log(name); // Alice
// 重命名变量
const { name: userName, age: userAge } = person;
// 函数参数解构
function greet({ name, age }) {
return Hello, ${name}! You are ${age}.;
}
console.log(greet(person)); // Hello, Alice! You are 28.
// 默认值
const { hobby = 'reading' } = person;
console.log(hobby); // reading
// 嵌套解构
const config = {
server: { host: 'localhost', port: 8080 },
db: { name: 'mydb' }
};
const { server: { host, port } } = config;
console.log(host, port); // localhost 8080
</code></pre>
<h3>2.5 Class 类语法</h3>
<p>ES6 引入 <code>class</code> 关键字,提供更清晰的面向对象编程语法,本质上是构造函数(Constructor Function)的语法糖。</p>
<pre><code>// 定义类
class Animal {
// 构造函数
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
// 实例方法
speak() {
console.log(${this.name} says: ${this.sound});
}
// 静态方法(类本身调用)
static info() {
console.log('This is an Animal class');
}
}
// 继承
class Dog extends Animal {
constructor(name) {
super(name, 'Woof!'); // 调用父类构造函数
this.type = 'Dog';
}
// 方法重写
speak() {
console.log(${this.name} (a ${this.type}) barks: ${this.sound});
}
// 子类独有方法
fetch() {
console.log(${this.name} fetches the ball!`);
}
}
const dog = new Dog('Buddy');
dog.speak(); // Buddy (a Dog) barks: Woof!
dog.fetch(); // Buddy fetches the ball!
Animal.info(); // This is an Animal class
// getter / setter
class Circle {
constructor(radius) {
this._radius = radius;
}
get diameter() {
return this._radius * 2;
}
set diameter(value) {
this._radius = value / 2;
}
get area() {
return Math.PI * this._radius ** 2;
}
}
const c = new Circle(5);
console.log(c.diameter); // 10
console.log(c.area); // 78.539...
c.diameter = 20;
console.log(c._radius); // 10
2.6 模块系统(import / export)
ES6 原生支持模块化编程,通过 import 和 export 实现模块的导入导出,取代了之前依赖第三方库(如 CommonJS、RequireJS)的局面。
// ---- math.js ----
// 命名导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// 默认导出(每个文件只能有一个 default)
// export default function multiply(a, b) { return a * b; }
// ---- main.js ----
// 导入命名导出
import { PI, add, subtract } from './math.js';
console.log(add(PI, 10)); // 13.14159
// 导入时重命名
import { add as sum } from './math.js';
// 导入所有命名导出
import * as math from './math.js';
console.log(math.PI);
// 导入默认导出
// import multiply from './math.js';
// console.log(multiply(3, 4)); // 12
// 动态导入
// const module = await import('./math.js');
// console.log(module.add(1, 2));
2.7 Promise 与 async/await
Promise 是异步编程的基础,async/await 则是 ES2017 引入的 Promise 语法糖,让异步代码看起来像同步代码。
// Promise 基本用法
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve({ id: 1, name: 'Alice' });
} else {
reject(new Error('Fetch failed'));
}
}, 1000);
});
};
fetchData()
.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => console.log('Done'));
// async/await(ES2017)
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (err) {
console.error(err);
}
}
getData();
// Promise.all 并行执行
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(results => console.log(results)); // [1, 2, 3]
2.8 Symbol、Map、Set、WeakMap、WeakSet
// Symbol:唯一标识符
const sym1 = Symbol('id');
const sym2 = Symbol('id');
console.log(sym1 === sym2); // false(即使描述相同也不相等)
// Symbol 用于对象属性(不冲突)
const obj = {
[sym1]: 'value1',
[sym2]: 'value2'
};
// Map:键可以是任意类型
const map = new Map();
map.set('name', 'Alice');
map.set(42, 'answer');
map.set({}, 'object key'); // 对象作为键(普通对象不行)
console.log(map.get(42)); // answer
console.log(map.size); // 3
map.forEach((v, k) => console.log(k, v));
map.delete('name');
// map.clear();
// Set:无重复值的集合
const set = new Set([1, 2, 2, 3, 3, 3]);
console.log([...set]); // [1, 2, 3](自动去重)
set.add(4);
set.has(2); // true
set.delete(2);
// WeakMap / WeakSet:不阻止垃圾回收,适合存储对象私有数据
const wm = new WeakMap();
const obj2 = {};
wm.set(obj2, 'private data');
// 当 obj2 被回收时,WeakMap 自动清除对应条目
2.9 扩展运算符(Spread)与剩余参数(Rest)
// 扩展运算符:将可迭代对象展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 对象展开(ES2018)
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 函数剩余参数
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// 剩余参数配合解构
const [first, ...rest] = [10, 20, 30, 40];
console.log(first); // 10
console.log(rest); // [20, 30, 40]
三、运行效果
将上述代码片段保存为 es6-demo.js,使用 Node.js(v6+)或现代浏览器控制台运行:
$ node es6-demo.js
apple
2 1
My name is Bob, I am 30 years old.
Buddy (a Dog) barks: Woof!
Buddy fetches the ball!
13.141592653589793
[1, 2, 3]
false
answer
[1, 2, 3]
10
在浏览器中打开任意现代网页(Chrome、Firefox、Safari、Edge),按 F12 打开开发者工具,切到 Console 标签,粘贴代码即可看到输出结果。ES6 模块在浏览器中需要通过 <script type="module"> 引入。
四、常见问题
Q1:let/const 到底比 var 好在哪里?
答: 主要有三大区别:
- 作用域:
var是函数级作用域,let/const是块级作用域。在if、for、{}块中,var的变量会泄露到函数级,而let/const严格限定在块内。 - 变量提升:
var声明会被提升(hoisting),导致在声明前访问得到undefined;let/const存在暂时性死区(TDZ),在声明前访问直接抛出ReferenceError。 - 重复声明:
var允许重复声明,let/const不允许,在编译阶段就会报错。
// var 的坑
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3, 3, 3(而非 0, 1, 2)
}
// let 解决了这个问题
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 输出 0, 1, 2
}
Q2:箭头函数能替代所有普通函数吗?
答: 不能。箭头函数有以下局限性:
- 没有自己的
this、arguments、super,不适合作为对象方法。 - 不能用作构造函数(
new ArrowFunc()会报错)。 - 不能使用
arguments对象(可以用剩余参数替代)。 - 没有
prototype属性。
// 箭头函数不能作为构造函数
const MyClass = () => {};
// new MyClass(); // TypeError: MyClass is not a constructor
// 箭头函数作为对象方法的陷阱
const counter = {
count: 0,
// 这里的 this 指向外层(window/undefined),不是 counter
inc: () => this.count++,
};
// 普通函数更适合:
counter2 = {
count: 0,
inc() { this.count++; }
};
Q3:const 对象的属性可以修改吗?
答: 可以。const 保证的是变量引用(指针)不变,而不是对象内容不可变。因此可以修改对象的属性、数组的元素。
const arr = [1, 2, 3];
arr.push(4); // 合法
arr[0] = 100; // 合法
// arr = [5, 6]; // 不合法,重新赋值
// 想冻结对象?使用 Object.freeze()
const frozen = Object.freeze([1, 2, 3]);
// frozen.push(4); // TypeError: Cannot add property 0, object is not extensible
Q4:import 是静态还是动态导入?有什么影响?
答: ES6 的 import 是静态导入,在编译阶段就完成解析。这意味着:
- import 必须在模块顶层,不能在条件语句或函数内使用。
- 静态导入能帮助打包工具(如 Webpack、Rollup)进行 Tree Shaking,消除未使用的代码。
- 如果需要动态导入(按需加载),使用
import()函数,它返回一个 Promise。
五、延伸阅读
- MDN Web Docs - ES6:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/New_in_JavaScript — Mozilla 官方文档,最权威的 ES6 参考。
- ES6 入门教程(阮一峰):https://es6.ruanyifeng.com/ — 中文社区最受欢迎的 ES6 教程书籍(免费在线阅读)。
- Babel 官网:https://babeljs.io/ — 将 ES6+ 代码转译为 ES5 的工具,让旧浏览器也能运行新语法。
- Node.js ES6 模块支持:https://nodejs.org/api/esm.html — Node.js 原生 ESM 模块系统官方文档。
- ECMAScript 规范:https://tc39.es/ecma262/ — TC39 发布的 ECMAScript 官方语言规范,适合深入理解语言细节。
- Can I Use:https://caniuse.com/ — 查询各浏览器对 ES6 特性的支持情况。