Appearance
ES6+ 特性
本文介绍ECMAScript 2015及后续版本引入的新特性,包括箭头函数、解构赋值、模板字符串、类、模块系统等。
let 和 const
ES6引入了块级作用域变量声明关键字 let 和 const,解决了 var 的一些问题。
javascript
// let - 块级作用域变量
let x = 10;
if (true) {
let x = 20; // 这是一个新的变量,仅在此块中有效
console.log(x); // 20
}
console.log(x); // 10
// const - 常量声明
const PI = 3.14159;
// PI = 3.14; // 错误:不能重新赋值
// 但对于对象和数组,const只保证引用不变,内容可以修改
const person = { name: "John" };
person.name = "Jane"; // 这是允许的
// person = {}; // 错误:不能重新赋值箭头函数
箭头函数提供了更简洁的函数语法,并且不绑定自己的 this。
javascript
// 传统函数表达式
let add = function(a, b) {
return a + b;
};
// 箭头函数
let addArrow = (a, b) => a + b;
// 多行箭头函数需要使用花括号和return
let multiply = (a, b) => {
let result = a * b;
return result;
};
// 没有参数的箭头函数
let sayHello = () => "Hello!";
// 单个参数可以省略括号
let square = x => x * x;
// this绑定的区别
let counter = {
count: 0,
// 传统函数:this绑定到调用对象
incrementTraditional: function() {
setTimeout(function() {
this.count++; // this指向window,不是counter
console.log(this.count); // NaN
}, 1000);
},
// 箭头函数:this继承自外部作用域
incrementArrow: function() {
setTimeout(() => {
this.count++; // this指向counter
console.log(this.count); // 1
}, 1000);
}
};模板字符串
模板字符串提供了更强大的字符串插值和多行字符串功能。
javascript
// 传统字符串连接
let name = "Alice";
let greeting = "Hello, " + name + "!";
// 模板字符串
let greetingTemplate = `Hello, ${name}!`;
// 多行字符串
let multiline = `这是第一行
这是第二行
这是第三行`;
// 表达式插值
let a = 5;
let b = 10;
console.log(`${a} + ${b} = ${a + b}`);
// 带标签的模板字符串
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<strong>${values[i]}</strong>` : "");
}, "");
}
let user = "John";
let status = "premium";
let message = highlight`用户 ${user} 拥有 ${status} 会员资格`;
// 结果: "用户 <strong>John</strong> 拥有 <strong>premium</strong> 会员资格"解构赋值
解构赋值允许从数组或对象中提取值并赋给变量。
javascript
// 数组解构
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 跳过元素
let [x, , z] = [1, 2, 3];
console.log(x, z); // 1 3
// 剩余元素
let [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
// 默认值
let [p = 10, q = 20] = [1];
console.log(p, q); // 1 20
// 对象解构
let person = { name: "Alice", age: 25, job: "Engineer" };
let { name, age } = person;
console.log(name, age); // "Alice" 25
// 重命名
let { name: userName, age: userAge } = person;
console.log(userName, userAge); // "Alice" 25
// 默认值
let { name: n, salary = "未知" } = person;
console.log(n, salary); // "Alice" "未知"
// 嵌套解构
let company = {
name: "Tech Corp",
address: {
city: "San Francisco",
country: "USA"
}
};
let { address: { city } } = company;
console.log(city); // "San Francisco"
// 函数参数解构
function printPersonInfo({ name, age, job = "未知" }) {
console.log(`${name}, ${age}岁, ${job}`);
}
printPersonInfo(person); // "Alice, 25岁, Engineer"默认参数
ES6允许为函数参数设置默认值。
javascript
// 传统方式
function greet(name) {
name = name || "Guest";
return "Hello, " + name;
}
// ES6默认参数
function greetES6(name = "Guest", time = "day") {
return `Good ${time}, ${name}!`;
}
console.log(greetES6()); // "Good day, Guest!"
console.log(greetES6("Alice")); // "Good day, Alice!"
console.log(greetES6("Bob", "morning")); // "Good morning, Bob!"
// 默认参数可以是表达式
function getDefaultName() {
return "Anonymous";
}
function welcome(name = getDefaultName()) {
return `Welcome, ${name}!`;
}扩展运算符
扩展运算符(...)可以展开数组或对象。
javascript
// 数组展开
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// 合并数组
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 复制数组
let copy = [...arr1];
// 将字符串展开为字符数组
let chars = [..."Hello"];
console.log(chars); // ["H", "e", "l", "l", "o"]
// 在函数调用中使用
function sum(a, b, c) {
return a + b + c;
}
let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
// 对象展开(ES2018)
let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, d: 4 };
// 合并对象
let mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 2, c: 3, d: 4 }
// 复制对象并修改某些属性
let modified = { ...obj1, b: 3, e: 5 };
console.log(modified); // { a: 1, b: 3, e: 5 }类
ES6引入了类语法,使面向对象编程更加直观。
javascript
// 类声明
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
sayHello() {
return `Hello, my name is ${this.name}`;
}
// 获取器
get profile() {
return `${this.name}, ${this.age} years old`;
}
// 设置器
set profile(value) {
[this.name, this.age] = value.split(", ");
}
// 静态方法
static isAdult(age) {
return age >= 18;
}
}
// 创建实例
let alice = new Person("Alice", 25);
console.log(alice.sayHello()); // "Hello, my name is Alice"
console.log(alice.profile); // "Alice, 25 years old"
// 调用静态方法
console.log(Person.isAdult(20)); // true
// 继承
class Employee extends Person {
constructor(name, age, company) {
// 调用父类构造函数
super(name, age);
this.company = company;
}
// 重写父类方法
sayHello() {
return `${super.sayHello()} and I work at ${this.company}`;
}
// 新方法
getCompany() {
return this.company;
}
}
let bob = new Employee("Bob", 30, "Tech Corp");
console.log(bob.sayHello()); // "Hello, my name is Bob and I work at Tech Corp"模块系统
ES6引入了标准化的模块系统,用于导入和导出功能。
javascript
// 导出变量、函数或类 (math.js)
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export class Calculator {
add(a, b) {
return a + b;
}
}
// 默认导出
export default function subtract(a, b) {
return a - b;
}
// 导入 (app.js)
// 导入特定项
import { add, multiply, PI } from './math.js';
// 导入并重命名
import { add as sum, multiply as product } from './math.js';
// 导入默认导出
import subtract from './math.js';
// 导入所有内容
import * as math from './math.js';
console.log(math.PI); // 3.14159
console.log(math.add(2, 3)); // 5Promise
Promise提供了更优雅的异步编程方式。
javascript
// 创建Promise
let promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
let success = true;
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
}, 1000);
});
// 使用Promise
promise
.then(result => {
console.log(result); // "操作成功"
return "下一步";
})
.then(nextResult => {
console.log(nextResult); // "下一步"
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log("无论成功或失败都会执行");
});
// Promise.all - 等待所有Promise完成
let promise1 = Promise.resolve(1);
let promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000));
let promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [1, 2, 3]
});
// Promise.race - 返回最先完成的Promise结果
Promise.race([promise1, promise2, promise3])
.then(value => {
console.log(value); // 1 (最快完成的)
});async/await
ES2017引入的async/await语法使异步代码更易读。
javascript
// 异步函数
async function fetchData() {
try {
// 等待Promise完成
let response = await fetch('https://api.example.com/data');
let data = await response.json();
return data;
} catch (error) {
console.error("获取数据失败:", error);
throw error;
}
}
// 调用异步函数
async function processData() {
try {
console.log("开始获取数据...");
let data = await fetchData();
console.log("数据获取成功:", data);
// 可以继续使用await处理其他异步操作
await saveToDatabase(data);
console.log("数据保存成功");
} catch (error) {
console.error("处理数据失败:", error);
}
}
// 异步函数总是返回Promise
fetchData().then(data => {
console.log("通过Promise处理结果:", data);
});
// 并行执行多个异步操作
async function fetchMultipleData() {
// 同时启动多个异步操作
let promise1 = fetch('https://api.example.com/data1');
let promise2 = fetch('https://api.example.com/data2');
// 等待所有操作完成
let [response1, response2] = await Promise.all([promise1, promise2]);
// 处理结果
let data1 = await response1.json();
let data2 = await response2.json();
return [data1, data2];
}其他ES6+特性
Map和Set
javascript
// Map - 键值对集合,任何值都可以作为键
let map = new Map();
map.set('key1', 'value1');
map.set(42, 'value2');
map.set({}, 'value3');
console.log(map.get('key1')); // "value1"
console.log(map.size); // 3
console.log(map.has(42)); // true
map.delete(42);
map.forEach((value, key) => {
console.log(`${key} = ${value}`);
});
// Set - 唯一值的集合
let set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3 (重复值被忽略)
console.log(set.has(2)); // true
set.add(4);
set.delete(1);
for (let value of set) {
console.log(value); // 2, 3, 4
}Symbol
javascript
// 创建唯一标识符
let sym1 = Symbol();
let sym2 = Symbol('description');
let sym3 = Symbol('description');
console.log(sym2 === sym3); // false,即使描述相同
// 用作对象属性键
let obj = {};
obj[sym1] = 'value1';
obj[sym2] = 'value2';
console.log(obj[sym1]); // "value1"
console.log(Object.keys(obj)); // [] (Symbol不出现在常规枚举中)
// 获取对象的Symbol属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol(description)]
// 全局Symbol注册表
let globalSym1 = Symbol.for('global');
let globalSym2 = Symbol.for('global');
console.log(globalSym1 === globalSym2); // true
console.log(Symbol.keyFor(globalSym1)); // "global"可选链操作符(ES2020)
javascript
let user = {
name: "Alice",
address: {
city: "New York"
}
};
// 传统方式
let city = user && user.address && user.address.city;
// 使用可选链
let cityOptional = user?.address?.city;
console.log(cityOptional); // "New York"
let zipCode = user?.address?.zipCode;
console.log(zipCode); // undefined (不会报错)
// 函数调用
let result = user.getDetails?.(); // 如果方法不存在,不会报错空值合并操作符(ES2020)
javascript
// 传统方式
let name = user.name !== undefined && user.name !== null ? user.name : "Anonymous";
// 使用空值合并操作符
let nameNullish = user.name ?? "Anonymous";
console.log(nameNullish); // "Alice"
let username = user.username ?? "Anonymous";
console.log(username); // "Anonymous"
// 与逻辑或的区别
let count = 0;
let result1 = count || 10; // 0被视为假值,返回10
let result2 = count ?? 10; // 0不是null或undefined,返回0私有类字段(ES2022)
javascript
class Person {
// 公共字段
name;
// 私有字段(以#开头)
#age;
constructor(name, age) {
this.name = name;
this.#age = age;
}
// 公共方法
getProfile() {
return `${this.name}, ${this.#getAgeDescription()}`;
}
// 私有方法
#getAgeDescription() {
return this.#age < 18 ? "未成年" : "成年人";
}
}
let person = new Person("Alice", 25);
console.log(person.name); // "Alice"
// console.log(person.#age); // 错误:私有字段不可访问
console.log(person.getProfile()); // "Alice, 成年人"