ES6 新特性全面总结
ES6 新特性全面总结
ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多强大的新特性,极大地提升了JavaScript的开发体验和能力。以下是ES6主要新增知识点的详细总结:
(一)、ES6变量声明:let 和 const 详解
一、let 和 const 的基本概念
ES6引入了两种新的变量声明方式:let和const,它们与传统的var声明有显著区别。
1. let 声明
let用于声明块级作用域的变量:
let x = 10; if (true) { let x = 20; // 不同的变量 console.log(x); // 20 } console.log(x); // 10
2. const 声明
const用于声明常量,声明后不能重新赋值:
const PI = 3.1415; // PI = 3; // TypeError: Assignment to constant variable
二、与 var 的关键区别
特性 | var | let | const |
---|---|---|---|
作用域 | 函数作用域或全局作用域 | 块级作用域 | 块级作用域 |
变量提升 | 是 | 是(但存在TDZ) | 是(但存在TDZ) |
重复声明 | 允许 | 不允许 | 不允许 |
初始值 | 可不初始化 | 可不初始化 | 必须初始化 |
重新赋值 | 允许 | 允许 | 不允许 |
三、块级作用域详解
1. 什么是块级作用域
块级作用域是指由{}包围的代码块形成的作用域:
{ let a = 1; var b = 2; } console.log(a); // ReferenceError: a is not defined console.log(b); // 2
2. 常见块级作用域场景
• if语句
• for循环
• while循环
• switch语句
• 单独的{}块
四、暂时性死区(TDZ)
1. 概念
在声明前访问let或const变量会触发暂时性死区错误:
console.log(a); // undefined var a = 1; console.log(b); // ReferenceError: Cannot access 'b' before initialization let b = 2;
2. 原理
虽然let和const也会提升,但在声明前处于"暂时性死区",访问会报错。
五、const 的特殊说明
1. 必须初始化
const a; // SyntaxError: Missing initializer in const declaration
2. 对象和数组的特殊性
const只保证变量名绑定的内存地址不变,不保证内部数据不变:
const obj = {a: 1}; obj.a = 2; // 允许 // obj = {}; // 不允许 const arr = [1, 2]; arr.push(3); // 允许 // arr = []; // 不允许
六、最佳实践建议
- 默认使用const:除非需要重新赋值,否则优先使用const
- 需要重新赋值时用let:当变量需要改变时使用let
- 避免使用var:除非有特殊需求,否则不使用var
- 声明位置:尽量在作用域顶部声明变量
- 命名规范:const常量可以使用全大写命名(如MAX_SIZE)
七、常见使用场景
1. for循环中的let
for (let i = 0; i console.log(i), 100); // 0, 1, 2 }
2. 块级作用域变量
function processData(data) { { let temp = transformData(data); // 处理temp... } // temp在这里不可访问 }
3. 模块中的常量
// config.js export const API_URL = 'https://api.example.com'; export const MAX_RETRIES = 3;
八、常见问题解答
Q1: 什么时候用let,什么时候用const?
A: 优先使用const,只有确定变量需要重新赋值时才使用let。
Q2: const声明的对象属性可以修改吗?
A: 可以修改对象属性,但不能重新赋值整个对象。
Q3: let和const能替代var吗?
A: 在大多数情况下可以完全替代,但要注意作用域差异。
Q4: 为什么会有暂时性死区?
A: 这是为了更早发现编程错误,避免变量提升带来的混淆。
Q5: 全局作用域下let和var有什么区别?
A: 全局作用域下,var声明的变量会成为window对象的属性,而let不会。
(二)、ES6箭头函数(Arrow Functions)全面解析
一、基本语法
箭头函数是ES6引入的一种更简洁的函数写法,使用=>符号定义。
1. 基础语法形式
// 传统函数写法 function sum(a, b) { return a + b; } // 箭头函数写法 const sum = (a, b) => a + b;
2. 不同形式的箭头函数
情况 | 示例 | 等价传统函数 |
---|---|---|
单个参数 | x => x * 2 | function(x) { return x * 2; } |
多个参数 | (x, y) => x + y | function(x, y) { return x + y; } |
无参数 | () => 42 | function() { return 42; } |
多行函数体 | (a, b) => { const c = a + b; return c * 2; } | function(a, b) { const c = a + b; return c * 2; } |
返回对象 | () => ({ a: 1 }) | function() { return { a: 1 }; } |
二、箭头函数的特性
1. 没有自己的this
箭头函数没有自己的this,它会捕获所在上下文的this值。
const obj = { name: 'Alice', // 定义一个对象属性 name,值为 'Alice' sayName: function() { console.log(this.name); // 输出当前对象的 name 属性值,即 'Alice' // 使用箭头函数作为 setTimeout 的回调函数 setTimeout(() => { console.log(this.name); // 输出当前对象的 name 属性值,即 'Alice' // 箭头函数不会创建自己的 this 上下文,而是捕获外层函数的 this // 因此这里的 this 指向 obj 对象 }, 100); }, sayNameError: function() { // 使用普通函数作为 setTimeout 的回调函数 setTimeout(function() { console.log(this.name); // 输出 undefined // 普通函数有自己的 this 上下文,默认指向全局对象(浏览器中是 window) // 因此这里的 this 不指向 obj 对象,而是 window 对象 // 由于 window 对象中没有 name 属性,所以输出 undefined }, 100); } };
2. 没有arguments对象
箭头函数没有自己的arguments对象,但可以访问外围函数的arguments。
function outer(a, b) { const inner = () => { // arguments 是一个特殊的类数组对象,它包含了函数调用时传入的所有参数。 console.log(arguments); // 访问outer的arguments }; inner(); } outer(1, 2); // [1, 2]
关键点
- arguments 的作用域:
- 在普通函数中,arguments 是一个类数组对象,包含了函数调用时传入的所有参数。
- 在箭头函数中,arguments 不会被自动绑定,而是继承自外层函数的 arguments。
- 箭头函数的特性:
- 箭头函数不会创建自己的 this 和 arguments,而是继承自外层函数的上下文。
3. 不能作为构造函数
箭头函数不能使用new调用,没有prototype属性。
const Foo = () => {}; // new Foo(); // TypeError: Foo is not a constructor
4. 没有super和new.target
箭头函数没有super和new.target绑定。
三、箭头函数的适用场景
1. 回调函数
// 数组方法 const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); // 事件处理 button.addEventListener('click', () => { console.log('Button clicked'); });
2. 需要保持this上下文的场景
class Counter { constructor() { this.count = 0; // 使用箭头函数保持this指向 this.increment = () => { this.count++; }; } }
3. 简洁的单行函数
const isEven = n => n % 2 === 0; const greet = name => `Hello, ${name}!`;
四、箭头函数的不适用场景
1. 对象方法
const obj = { value: 0, // 错误:箭头函数不会绑定this到obj increment: () => { this.value++; // this指向window/undefined } };
2. 需要动态this的场景
// 错误:无法通过call/apply/bind改变this const greet = () => console.log(this.name); const alice = { name: 'Alice' }; greet.call(alice); // 无效
3. 需要arguments对象的函数
// 错误:无法访问自己的arguments const sum = () => { console.log(arguments); // 引用外层arguments或报错 };
五、箭头函数与普通函数的对比
特性 箭头函数 普通函数 this绑定 词法作用域 动态绑定 arguments 无 有 构造函数 不能 能 prototype 无 有 yield 不能用作生成器 可以 简洁性 高 低 适用场景 回调、需要固定this 方法、构造函数 六、常见问题解答
Q1: 什么时候应该使用箭头函数?
A: 适合需要保持this一致性的回调函数、简单的单行函数、不需要arguments的场景。
Q2: 箭头函数能替代所有普通函数吗?
A: 不能。对象方法、构造函数、需要动态this或arguments的场景仍需使用普通函数。
Q3: 箭头函数有prototype属性吗?
A: 没有,这也是它不能用作构造函数的原因之一。
Q4: 如何给箭头函数添加默认参数?
A: 和普通函数一样,直接在参数中指定:
const greet = (name = 'Guest') => `Hello, ${name}!`;
Q5: 箭头函数可以有name属性吗?
A: 可以,当箭头函数被赋值给变量时,会使用变量名作为函数名:
const foo = () => {}; console.log(foo.name); // "foo"
七、高级用法
1. 立即执行箭头函数(IIFE)
((name) => { console.log(`Hello, ${name}!`); })('Alice');
2. 链式调用
const operations = { value: 1, add: (n) => { operations.value += n; return operations; }, multiply: (n) => { operations.value *= n; return operations; } }; operations.add(2).multiply(3).add(1); // value = 10
3. 配合解构使用
const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]; const names = users.map(({ name }) => name); // ['Alice', 'Bob']
箭头函数是ES6中最受欢迎的特性之一,正确理解和使用它可以使代码更简洁、更可读,同时避免许多this绑定的陷阱。
(三)、ES6模板字符串(Template Literals)深度解析
一、基本概念与语法
模板字符串是ES6引入的一种新型字符串表示法,使用反引号(`)包裹内容,相比传统字符串具有更强大的功能。
1. 基础语法对比
// 传统字符串 const name = 'Alice'; const greeting = 'Hello, ' + name + '!'; // 模板字符串 const greeting = `Hello, ${name}!`;
2. 核心特性
• 多行字符串:直接支持换行
• 字符串插值:使用${expression}嵌入变量和表达式
• 标签模板:可以自定义字符串处理函数
二、多行字符串处理
1. 传统方式的痛点
// ES5实现多行字符串 var message = '第一行\n' + '第二行\n' + '第三行';
2. 模板字符串解决方案
const message = `第一行 第二行 第三行`;
3. 实际应用场景
// HTML模板 const html = `
${title}
${content}
`; // SQL查询 const query = ` SELECT * FROM users WHERE id = ${userId} ORDER BY name DESC `;三、字符串插值详解
1. 基本插值
const name = 'Alice'; const age = 25; console.log(`Name: ${name}, Age: ${age}`); // "Name: Alice, Age: 25"
2. 表达式计算
const a = 10; const b = 20; console.log(`Sum: ${a + b}`); // "Sum: 30"
3. 函数调用
function getAge() { return 25; } console.log(`Age: ${getAge()}`); // "Age: 25"
4. 嵌套模板
const isMember = true; console.log(`Status: ${ isMember ? `Member since ${2020}` : 'Not a member' }`); // "Status: Member since 2020"
四、标签模板(Tagged Templates)
1. 基本概念
标签模板允许使用函数解析模板字符串,第一个参数是字符串数组,后续参数是插值表达式。
function tag(strings, ...values) { console.log(strings); // ["Hello ", "!"] console.log(values); // ["Alice"] return 'Processed string'; } const name = 'Alice'; const result = tag`Hello ${name}!`; // 当调用 tag 函数时: // strings 参数接收模板字符串中的文本部分。 // values 参数接收模板字符串中的嵌入值。
2. 实际应用案例
a) 安全HTML转义
// 定义一个标签函数 safeHtml,用于处理模板字符串并防止 XSS 攻击 function safeHtml(strings, ...values) { let result = ''; // 初始化一个空字符串,用于存储最终生成的 HTML 内容 // 遍历 strings 数组,它包含了模板字符串中的所有文本部分 for (let i = 0; i 替换为 > .replace(/"/g, '"') // 将 " 替换为 " .replace(/'/g, '''); // 将 ' 替换为 ' } } return result; // 返回最终生成的 HTML 内容 } // 定义一个可能包含恶意脚本的用户输入 const userInput = 'alert("XSS")'; // 使用 safeHtml 标签函数处理模板字符串 const safeOutput = safeHtml`${userInput}`; // 输出结果 console.log(safeOutput); // <script>alert("XSS")</script>
b) 国际化处理
function i18n(strings, ...values) { const translations = { 'Hello': '你好', 'Welcome': '欢迎' }; let translated = ''; strings.forEach((str, i) => { translated += translations[str.trim()] || str; if (values[i]) translated += values[i]; }); return translated; } const name = 'Alice'; console.log(i18n`Hello ${name}!`); // "你好 Alice!"
c) SQL查询构建
function sqlQuery(strings, ...values) { // 实际应用中应该使用数据库驱动提供的参数化查询 let query = strings[0]; for (let i = 0; i
五、特殊字符处理
1. 转义字符
console.log(`反引号: \` 美元符号: \${`); // "反引号: ` 美元符号: ${"
2. 原始字符串
使用String.raw标签获取原始字符串(不处理转义字符):
const path = String.raw`C:\Development\project\files`; console.log(path); // "C:\Development\project\files" // 等同于 function raw(strings, ...values) { let result = strings.raw[0]; for (let i = 0; i
六、性能考量
- 静态字符串:对于纯静态字符串,模板字符串与普通字符串性能相当
- 动态插值:频繁变化的插值内容可能影响性能,在极端性能敏感场景需测试
- 标签模板:自定义处理会增加开销,但通常可忽略不计
七、最佳实践
-
优先使用模板字符串:替代所有字符串拼接场景
-
复杂逻辑处理:对于复杂插值逻辑,考虑提前计算表达式
// 不推荐 console.log(`Result: ${calculateA() + calculateB() * complexCalculation()}`); // 推荐 const result = calculateA() + calculateB() * complexCalculation(); console.log(`Result: ${result}`);
-
多行缩进处理:使用.trim()消除不必要的缩进
function getHtml() { return `
Content
`.trim(); } -
安全注意事项:
• 不要直接将用户输入插入HTML/URL/SQL
• 使用专用转义库或标签模板处理危险内容
八、浏览器兼容性
现代浏览器均支持模板字符串特性,对于旧版浏览器需要通过Babel等工具转译:
• ES6转ES5:将模板字符串转换为普通字符串拼接
• 标签模板:转换为函数调用形式
九、扩展应用
1. 配合React等框架
const name = 'Alice'; const element = Hello, {name}!; // JSX本质上也是一种模板字符串的扩展应用
2. 生成动态CSS
const primaryColor = '#3498db'; const css = ` .button { background: ${primaryColor}; padding: 10px 20px; } `;
3. 创建DSL(领域特定语言)
function createRoute(strings, ...values) { return { path: strings.join('').replace(/\s+/g, ''), params: values }; } const id = 123; const route = createRoute`/users/ ${id} /profile`; // { path: "/users/123/profile", params: [123] }
模板字符串彻底改变了JavaScript处理字符串的方式,使代码更简洁、更可读,同时通过标签模板提供了强大的扩展能力。正确使用这一特性可以显著提升开发效率和代码质量。
(四)、ES6解构赋值(Destructuring Assignment)全面解析
一、基本概念
解构赋值是ES6引入的一种语法,允许按照一定模式从数组或对象中提取值,然后对变量进行赋值。这种语法可以极大简化数据提取的代码。
二、数组解构
1. 基本用法
const arr = [1, 2, 3]; // 传统方式 const a = arr[0]; const b = arr[1]; const c = arr[2]; // 解构赋值 const [a, b, c] = arr; // a=1, b=2, c=3
2. 嵌套解构
const arr = [1, [2, 3], 4]; const [a, [b, c], d] = arr; // a=1, b=2, c=3, d=4
3. 默认值
const [a=1, b=2] = []; // a=1, b=2 const [a, b=2] = [5]; // a=5, b=2
4. 跳过元素
const [a, , b] = [1, 2, 3]; // a=1, b=3 (跳过第二个元素)
5. 剩余模式
const [a, ...rest] = [1, 2, 3]; // a=1, rest=[2, 3]
三、对象解构
1. 基本用法
const obj = { x: 1, y: 2 }; // 传统方式 const x = obj.x; const y = obj.y; // 解构赋值 const { x, y } = obj; // x=1, y=2
2. 别名赋值
const { x: a, y: b } = { x: 1, y: 2 }; // a=1, b=2
3. 默认值
const { a=1, b=2 } = {}; // a=1, b=2 const { a: x=1, b: y=2 } = { b: 3 }; // x=1, y=3
4. 嵌套解构
const obj = { a: { b: 1, c: 2 }, d: 3 }; const { a: { b, c }, d } = obj; // b=1, c=2, d=3
5. 剩余模式
const { a, ...rest } = { a: 1, b: 2, c: 3 }; // a=1, rest={b:2, c:3}
四、混合解构
可以混合使用数组和对象解构:
const props = { arr: [1, { b: 2, c: 3 }] }; const { arr: [a, { b, c }] } = props; // a=1, b=2, c=3
五、函数参数解构
1. 对象参数解构
function draw({ x=0, y=0, radius=1 }) { console.log(x, y, radius); } draw({ x: 10, y: 20 }); // 10 20 1
2. 数组参数解构
function sum([a=0, b=0]) { return a + b; } sum([1, 2]); // 3
3. 复杂参数解构
function process({ id, name: firstName, address: { city } = {} }) { console.log(id, firstName, city); } process({ id: 1, name: 'Alice', address: { city: 'Beijing' } });
六、特殊应用场景
1. 交换变量值
let a = 1, b = 2; [a, b] = [b, a]; // a=2, b=1
2. 函数返回多个值
function getData() { return [1, 2, 3]; } const [a, b, c] = getData();
3. 正则表达式匹配
const url = 'https://example.com/path'; const { 1: protocol, 2: host } = url.match(/(\w+):\/\/([^/]+)/);
4. 模块导入
import { Component, useState } from 'react';
5. 配置对象处理
function init({ width = 100, height = 200, color = 'red' } = {}) { // 使用解构参数并设置默认值 }
七、注意事项
-
解构失败:如果解构不成功,变量的值等于undefined
const [a] = []; // a=undefined const { b } = {}; // b=undefined
-
模式匹配:解构赋值的左边是模式,不是变量
const { a: b } = { a: 1 }; // 模式是a,变量是b
-
不可迭代值:对非迭代值使用数组解构会报错
const [a] = null; // TypeError
-
已声明变量:已声明变量解构需要用括号包裹
let a; ({ a } = { a: 1 }); // 必须加括号
-
默认值生效条件:只有当解构的值严格等于undefined时,默认值才会生效
const { a = 1 } = { a: null }; // a=null
八、最佳实践
- 合理使用默认值:为可能不存在的属性设置默认值
- 避免过度嵌套:过深的解构会降低代码可读性
- 明确变量名:使用别名时选择有意义的名称
- 处理错误情况:考虑解构失败时的处理方式
- 文档注释:对复杂解构添加注释说明结构
九、浏览器兼容性
现代浏览器均支持解构赋值,对于旧版浏览器需要通过Babel等工具转译:
• 对象解构转换为Object.assign()或逐个属性赋值
• 数组解构转换为下标访问
解构赋值是ES6中最实用的特性之一,合理使用可以显著提高代码的简洁性和可读性,特别是在处理复杂数据结构时。
(五)、函数参数默认值
function sayHello(name = 'Guest') { console.log(`Hello, ${name}!`); } sayHello(); // Hello, Guest!
(六)、ES6 扩展运算符(Spread Operator)深度解析
扩展运算符(...)是 ES6 引入的一个重要特性,它允许将可迭代对象(如数组、字符串、Map、Set 等)"展开"为单独的元素。
一、基本语法与概念
扩展运算符使用三个点(...)表示,主要功能是将一个可迭代对象展开为多个元素。
const arr = [1, 2, 3]; console.log(...arr); // 1 2 3
二、数组中的应用
1. 数组复制(浅拷贝)
const original = [1, 2, 3]; const copy = [...original]; // 创建新数组
2. 数组合并
const arr1 = [1, 2]; const arr2 = [3, 4]; const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
3. 数组解构
const [first, ...rest] = [1, 2, 3, 4]; console.log(first); // 1 console.log(rest); // [2, 3, 4]
4. 插入元素
const numbers = [1, 2, 3]; const newNumbers = [0, ...numbers, 4]; // [0, 1, 2, 3, 4]
5. 替代 apply 方法
// ES5 Math.max.apply(null, [1, 2, 3]); // ES6 Math.max(...[1, 2, 3]);
三、对象中的应用(ES2018+)
1. 对象复制(浅拷贝)
const obj = { a: 1, b: 2 }; const copy = { ...obj }; // { a: 1, b: 2 }
2. 对象合并
const obj1 = { a: 1 }; const obj2 = { b: 2 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2 }
3. 属性覆盖
const defaults = { color: 'red', size: 'medium' }; const settings = { ...defaults, color: 'blue' }; // { color: 'blue', size: 'medium' }
4. 添加新属性
const person = { name: 'Alice' }; const withAge = { ...person, age: 25 }; // { name: 'Alice', age: 25 }
四、函数参数中的应用
1. 收集剩余参数
function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0); } sum(1, 2, 3); // 6
2. 传递数组参数
const numbers = [1, 2, 3]; sum(...numbers); // 等同于 sum(1, 2, 3)
五、其他可迭代对象中的应用
1. 字符串展开
const str = 'hello'; const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
2. Set 展开
const set = new Set([1, 2, 3]); const arr = [...set]; // [1, 2, 3]
3. Map 展开
const map = new Map([['a', 1], ['b', 2]]); const entries = [...map]; // [['a', 1], ['b', 2]]
六、高级用法
1. 实现数组扁平化
function flatten(arr) { return [].concat(...arr); } flatten([[1], [2, 3], [4]]); // [1, 2, 3, 4]
2. 实现深度克隆(仅适用于特定情况)
const deepClone = obj => JSON.parse(JSON.stringify(obj)); const cloned = deepClone({ a: [1, 2], b: { c: 3 } });
3. 条件展开
const condition = true; const obj = { ...(condition && { a: 1 }), b: 2 }; // { a: 1, b: 2 } 或 { b: 2 }
七、注意事项
-
浅拷贝问题:扩展运算符只进行浅拷贝
const obj = { a: { b: 1 } }; const copy = { ...obj }; copy.a.b = 2; console.log(obj.a.b); // 2 (被修改)
-
性能考虑:对于大型数组/对象,扩展运算符可能不是最高效的选择
-
浏览器兼容性:对象展开是 ES2018 特性,旧环境可能需要 Babel 转译
-
不可迭代对象:不能展开普通对象(在数组上下文中)
const obj = { a: 1, b: 2 }; // [...obj]; // TypeError: obj is not iterable
八、最佳实践
-
优先使用扩展运算符:替代 concat、apply 等传统方法
-
合理使用解构:结合解构赋值处理复杂数据结构
-
注意不可变性:在 React/Redux 等需要不可变数据的场景中特别有用
-
命名清晰:使用有意义的变量名提高可读性
-
文档注释:对复杂展开逻辑添加注释说明
九、常见问题解答
Q1: 扩展运算符和剩余参数有什么区别?
A: 语法相同但使用场景不同:
• 扩展运算符用于展开元素
• 剩余参数用于收集元素
Q2: 如何实现深度克隆?
A: 扩展运算符只能浅拷贝,深度克隆需要递归或使用 JSON.parse(JSON.stringify())(有局限性)
Q3: 可以展开 Generator 吗?
A: 可以,Generator 是可迭代对象:
function* gen() { yield 1; yield 2; } [...gen()]; // [1, 2]
Q4: 为什么对象展开是 ES2018 特性?
A: 数组展开在 ES6 引入,对象展开稍晚标准化
Q5: 扩展运算符会影响原对象吗?
A: 不会,但浅拷贝的属性引用相同
扩展运算符极大简化了 JavaScript 中对数组和对象的操作,是现代 JavaScript 开发中不可或缺的特性。合理使用可以使代码更加简洁、可读性更强。
(七)、ES6 Promise 深度解析
一、Promise 基本概念
Promise 是 ES6 引入的异步编程解决方案,用于处理异步操作。它代表一个尚未完成但预期将来会完成的操作及其结果值。
1. 三种状态
• pending(待定):初始状态
• fulfilled(已兑现):操作成功完成
• rejected(已拒绝):操作失败
状态转换是不可逆的:pending → fulfilled 或 pending → rejected
2. 基本语法
const promise = new Promise((resolve, reject) => { // 异步操作 if (/* 成功 */) { resolve(value); // 状态变为fulfilled } else { reject(error); // 状态变为rejected } });
二、Promise 实例方法
1. then() 方法
promise.then( value => { /* 成功处理 */ }, error => { /* 失败处理 */ } );
2. catch() 方法
promise.catch( error => { /* 失败处理 */ } );
3. finally() 方法
promise.finally( () => { /* 无论成功失败都会执行 */ } );
三、Promise 静态方法
1. Promise.resolve()
Promise.resolve('success') .then(val => console.log(val)); // 'success'
2. Promise.reject()
Promise.reject('error') .catch(err => console.log(err)); // 'error'
3. Promise.all()
Promise.all([promise1, promise2]) .then(values => { /* 所有promise都成功 */ }) .catch(error => { /* 任一promise失败 */ });
4. Promise.race()
Promise.race([promise1, promise2]) .then(value => { /* 第一个完成的promise */ });
5. Promise.allSettled()
Promise.allSettled([promise1, promise2]) .then(results => { /* 所有promise都完成 */ });
6. Promise.any()
Promise.any([promise1, promise2]) .then(value => { /* 第一个成功的promise */ }) .catch(errors => { /* 所有promise都失败 */ });
四、Promise 链式调用
Promise 的 then() 方法返回一个新的 Promise,可以实现链式调用:
doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => console.log(finalResult)) .catch(failureCallback);
五、Promise 错误处理
1. 使用 catch()
promise .then(handleSuccess) .catch(handleError);
2. then() 的第二个参数
promise .then(handleSuccess, handleError);
3. 区别
• .then(success, error):只能捕获当前 then 之前的错误
• .catch(error):可以捕获整个链中的错误
六、Promise 最佳实践
- 总是返回 Promise:在 then() 回调中返回 Promise 或值
- 避免嵌套:使用链式调用而非嵌套
- 总是捕获错误:使用 catch() 处理错误
- 命名 Promise:给 Promise 变量有意义的名称
- 避免冗余代码:合理使用 Promise 静态方法
七、Promise 实现示例
1. 封装 setTimeout
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } delay(1000).then(() => console.log('1秒后执行'));
2. 封装 XMLHttpRequest
function getJSON(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onload = () => resolve(xhr.responseText); xhr.onerror = () => reject(xhr.statusText); xhr.send(); }); }
3. 封装 fetch
function fetchData(url) { return fetch(url) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }); }
八、Promise 与 async/await
async/await 是建立在 Promise 之上的语法糖:
async function fetchData() { try { const response = await fetch(url); const data = await response.json(); return data; } catch (error) { console.error('Error:', error); } }
九、常见问题解答
Q1: Promise 和回调函数有什么区别?
A: Promise 提供了更清晰的链式调用和错误处理,避免了回调地狱。
Q2: 如何取消一个 Promise?
A: 原生 Promise 无法取消,但可以使用 AbortController 或第三方库实现类似功能。
Q3: Promise 是微任务吗?
A: 是的,Promise 的回调会作为微任务执行,比 setTimeout 等宏任务优先级高。
Q4: 如何实现 Promise 重试机制?
A: 可以封装一个重试函数:
function retry(fn, times) { return new Promise((resolve, reject) => { function attempt() { fn().then(resolve).catch(err => { if (times-- > 0) attempt(); else reject(err); }); } attempt(); }); }
Q5: Promise.all 和 Promise.allSettled 有什么区别?
A: all 在任一 promise 失败时立即拒绝,allSettled 会等待所有 promise 完成。
Promise 是现代 JavaScript 异步编程的核心,理解其原理和用法对于编写高质量的异步代码至关重要。
(八)、ES6 Class 类全面解析
一、Class 基本概念
ES6 引入的 class 关键字实质上是 JavaScript 基于原型的继承的语法糖,它提供了更接近传统面向对象语言的写法。
1. 基本语法
class Person { constructor(name) { this.name = name; } sayHello() { console.log(`Hello, ${this.name}!`); } } const alice = new Person('Alice'); alice.sayHello(); // "Hello, Alice!"
二、Class 核心特性
1. 构造方法 (constructor)
class Person { constructor(name, age) { this.name = name; this.age = age; } }
2. 实例方法
class Person { // ... greet() { console.log(`Hi, I'm ${this.name}`); } }
3. 静态方法 (static)
class MathUtils { static sum(a, b) { return a + b; } } MathUtils.sum(1, 2); // 3
4. 静态属性
class Config { static apiUrl = 'https://api.example.com'; } console.log(Config.apiUrl);
5. 私有字段 (ES2022)
class Counter { #count = 0; // 私有字段 increment() { this.#count++; } getCount() { return this.#count; } }
三、Class 继承
1. extends 继承
class Student extends Person { constructor(name, grade) { super(name); // 调用父类构造函数 this.grade = grade; } study() { console.log(`${this.name} is studying`); } }
2. super 关键字
• super() 调用父类构造函数
• super.method() 调用父类方法
3. 方法重写
class Student extends Person { sayHello() { super.sayHello(); // 调用父类方法 console.log("I'm a student"); } }
4. 继承内置类
class MyArray extends Array { first() { return this[0]; } last() { return this[this.length - 1]; } }
四、Getter 和 Setter
class Temperature { constructor(celsius) { this.celsius = celsius; } get fahrenheit() { return this.celsius * 1.8 + 32; } set fahrenheit(value) { this.celsius = (value - 32) / 1.8; } } const temp = new Temperature(25); console.log(temp.fahrenheit); // 77 temp.fahrenheit = 86; console.log(temp.celsius); // 30
五、Class 表达式
1. 命名类表达式
const Person = class NamedPerson { constructor(name) { this.name = name; } sayName() { console.log(this.name); } };
2. 匿名类表达式
const Person = class { // ... };
六、Class 与原型的关系
Class 本质上是构造函数的语法糖:
typeof Person; // "function" Person.prototype.constructor === Person; // true
七、Class 与函数声明的重要区别
- 提升(hoisting):类声明不会被提升
- 严格模式:类声明和类表达式默认在严格模式下执行
- 调用方式:类必须使用 new 调用
- 方法枚举:类方法不可枚举
八、高级用法
1. Mixin 模式
function mixin(...mixins) { class Mix { constructor() { for (let mixin of mixins) { copyProperties(this, new mixin()); } } } for (let mixin of mixins) { copyProperties(Mix, mixin); copyProperties(Mix.prototype, mixin.prototype); } return Mix; } class DistributedEdit extends mixin(Loggable, Serializable) { // ... }
2. 抽象基类
class Abstract { constructor() { if (new.target === Abstract) { throw new Error('Cannot instantiate abstract class'); } } } class Concrete extends Abstract {}
3. Symbol.iterator 实现
class Range { constructor(start, end) { this.start = start; this.end = end; } *[Symbol.iterator]() { for (let i = this.start; i yield i; } } } const range = new Range(1, 5); [...range]; // [1, 2, 3, 4, 5] } class Child extends Parent {} console.log(Child.prototype instanceof Parent); // true console.log(Parent.prototype.isPrototypeOf(Child.prototype)); // true return x * x; } PI, square } from './math.js'; console.log(square(PI)); // 9.86902225 [sym1]: 'value' }; console.log(obj[sym1]); // value [Symbol.iterator]() { let step = 0; return { next() { step++; if (step return { value: step, done: false }; } return { value: undefined, done: true }; } }; } }; for (const value of iterable) { console.log(value); // 1, 2, 3 } let id = 1; while (true) { yield id++; } } const gen = idGenerator(); console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 }; const handler = { get(target, prop) { return prop in target ? target[prop] : 37; } }; const proxy = new Proxy(target, handler); proxy.a = 1; console.log(proxy.a); // 1 console.log(proxy.b); // 37 a: 1 }; console.log(Reflect.get(obj, 'a')); // 1 Reflect.set(obj, 'b', 2); console.log(obj.b); // 2
h3(十四)、新的数据类型/h3 h41. TypedArray/h4 pre class="brush:python;toolbar:false"const buffer = new ArrayBuffer(16); const int32View = new Int32Array(buffer); /pre h42. DataView/h4 pre class="brush:python;toolbar:false"const view = new DataView(buffer); view.setInt32(0, 42); console.log(view.getInt32(0)); // 42 /pre h3(十五)、字符串和数组新增方法/h3 h41. 字符串方法/h4 pre class="brush:python;toolbar:false"'hello'.startsWith('he'); // true 'hello'.endsWith('lo'); // true 'hello'.includes('ell'); // true 'abc'.repeat(3); // 'abcabcabc' /pre h42. 数组方法/h4 pre class="brush:python;toolbar:false"[1, 2, 3].find(x = x > 1); // 2 [1, 2, 3].findIndex(x => x > 1); // 1 [1, 2, 3].fill(4); // [4, 4, 4] Array.from('hello'); // ['h', 'e', 'l', 'l', 'o'] Array.of(1, 2, 3); // [1, 2, 3](十六)、尾调用优化
function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); // 尾调用优化 }
(十七)、二进制和八进制字面量
const binary = 0b1010; // 10 const octal = 0o12; // 10
(十八_、Object新增方法
Object.assign({}, {a: 1}, {b: 2}); // {a: 1, b: 2} Object.is(NaN, NaN); // true Object.setPrototypeOf(obj, prototype); Object.getOwnPropertySymbols(obj);
总结
ES6的这些新特性极大地丰富了JavaScript的功能,使代码更加简洁、可读性更强,同时也提高了开发效率。掌握这些特性对于现代JavaScript开发至关重要。随着JavaScript的不断发展,这些特性已经成为现代Web开发的基石。
- 箭头函数不会创建自己的 this 和 arguments,而是继承自外层函数的上下文。