ES6 新特性全面总结
ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多强大的新特性,极大地提升了JavaScript的开发体验和能力。以下是ES6主要新增知识点的详细总结:
(一)、ES6变量声明:let 和 const 详解
一、let 和 const 的基本概念
ES6引入了两种新的变量声明方式:let
和const
,它们与传统的var
声明有显著区别。
1. let 声明
let
用于声明块级作用域的变量:
javascript
let x = 10;
if (true) {
let x = 20; // 不同的变量
console.log(x); // 20
}
console.log(x); // 10
2. const 声明
const
用于声明常量,声明后不能重新赋值:
javascript
const PI = 3.1415;
// PI = 3; // TypeError: Assignment to constant variable
二、与 var 的关键区别
特性 | var | let | const |
---|---|---|---|
作用域 | 函数作用域或全局作用域 | 块级作用域 | 块级作用域 |
变量提升 | 是 | 是(但存在TDZ) | 是(但存在TDZ) |
重复声明 | 允许 | 不允许 | 不允许 |
初始值 | 可不初始化 | 可不初始化 | 必须初始化 |
重新赋值 | 允许 | 允许 | 不允许 |
三、块级作用域详解
1. 什么是块级作用域
块级作用域是指由{}
包围的代码块形成的作用域:
javascript
{
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
变量会触发暂时性死区错误:
javascript
console.log(a); // undefined
var a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
2. 原理
虽然let
和const
也会提升,但在声明前处于"暂时性死区",访问会报错。
五、const 的特殊说明
1. 必须初始化
javascript
const a; // SyntaxError: Missing initializer in const declaration
2. 对象和数组的特殊性
const
只保证变量名绑定的内存地址不变,不保证内部数据不变:
javascript
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
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
2. 块级作用域变量
javascript
function processData(data) {
{
let temp = transformData(data);
// 处理temp...
}
// temp在这里不可访问
}
3. 模块中的常量
javascript
// 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. 基础语法形式
javascript
// 传统函数写法
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) => {<br> const c = a + b;<br> return c * 2;<br>} |
function(a, b) {<br> const c = a + b;<br> return c * 2;<br>} |
返回对象 | () => ({ a: 1 }) |
function() { return { a: 1 }; } |
二、箭头函数的特性
1. 没有自己的this
箭头函数没有自己的this
,它会捕获所在上下文的this
值。
javascript
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
。
javascript
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
属性。
javascript
const Foo = () => {};
// new Foo(); // TypeError: Foo is not a constructor
4. 没有super和new.target
箭头函数没有super
和new.target
绑定。
三、箭头函数的适用场景
1. 回调函数
javascript
// 数组方法
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
// 事件处理
button.addEventListener('click', () => {
console.log('Button clicked');
});
2. 需要保持this上下文的场景
javascript
class Counter {
constructor() {
this.count = 0;
// 使用箭头函数保持this指向
this.increment = () => {
this.count++;
};
}
}
3. 简洁的单行函数
javascript
const isEven = n => n % 2 === 0;
const greet = name => `Hello, ${name}!`;
四、箭头函数的不适用场景
1. 对象方法
javascript
const obj = {
value: 0,
// 错误:箭头函数不会绑定this到obj
increment: () => {
this.value++; // this指向window/undefined
}
};
2. 需要动态this的场景
javascript
// 错误:无法通过call/apply/bind改变this
const greet = () => console.log(this.name);
const alice = { name: 'Alice' };
greet.call(alice); // 无效
3. 需要arguments对象的函数
javascript
// 错误:无法访问自己的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: 和普通函数一样,直接在参数中指定:
javascript
const greet = (name = 'Guest') => `Hello, ${name}!`;
Q5: 箭头函数可以有name属性吗?
A: 可以,当箭头函数被赋值给变量时,会使用变量名作为函数名:
javascript
const foo = () => {};
console.log(foo.name); // "foo"
七、高级用法
1. 立即执行箭头函数(IIFE)
javascript
((name) => {
console.log(`Hello, ${name}!`);
})('Alice');
2. 链式调用
javascript
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. 配合解构使用
javascript
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. 基础语法对比
javascript
// 传统字符串
const name = 'Alice';
const greeting = 'Hello, ' + name + '!';
// 模板字符串
const greeting = `Hello, ${name}!`;
2. 核心特性
• 多行字符串 :直接支持换行
• 字符串插值 :使用${expression}
嵌入变量和表达式
• 标签模板:可以自定义字符串处理函数
二、多行字符串处理
1. 传统方式的痛点
javascript
// ES5实现多行字符串
var message = '第一行\n' +
'第二行\n' +
'第三行';
2. 模板字符串解决方案
javascript
const message = `第一行
第二行
第三行`;
3. 实际应用场景
javascript
// HTML模板
const html = `
<div class="container">
<h1>${title}</h1>
<p>${content}</p>
</div>
`;
// SQL查询
const query = `
SELECT * FROM users
WHERE id = ${userId}
ORDER BY name DESC
`;
三、字符串插值详解
1. 基本插值
javascript
const name = 'Alice';
const age = 25;
console.log(`Name: ${name}, Age: ${age}`); // "Name: Alice, Age: 25"
2. 表达式计算
javascript
const a = 10;
const b = 20;
console.log(`Sum: ${a + b}`); // "Sum: 30"
3. 函数调用
javascript
function getAge() {
return 25;
}
console.log(`Age: ${getAge()}`); // "Age: 25"
4. 嵌套模板
javascript
const isMember = true;
console.log(`Status: ${
isMember ? `Member since ${2020}` : 'Not a member'
}`); // "Status: Member since 2020"
四、标签模板(Tagged Templates)
1. 基本概念
标签模板允许使用函数解析模板字符串,第一个参数是字符串数组,后续参数是插值表达式。
javascript
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转义
javascript
// 定义一个标签函数 safeHtml,用于处理模板字符串并防止 XSS 攻击
function safeHtml(strings, ...values) {
let result = ''; // 初始化一个空字符串,用于存储最终生成的 HTML 内容
// 遍历 strings 数组,它包含了模板字符串中的所有文本部分
for (let i = 0; i < strings.length; i++) {
result += strings[i]; // 将当前的文本部分添加到 result 中
// 检查是否存在对应的嵌入值
if (i < values.length) {
// 将嵌入值转换为字符串,并进行 HTML 转义
result += String(values[i])
.replace(/&/g, '&') // 将 & 替换为 &
.replace(/</g, '<') // 将 < 替换为 <
.replace(/>/g, '>') // 将 > 替换为 >
.replace(/"/g, '"') // 将 " 替换为 "
.replace(/'/g, '''); // 将 ' 替换为 '
}
}
return result; // 返回最终生成的 HTML 内容
}
// 定义一个可能包含恶意脚本的用户输入
const userInput = '<script>alert("XSS")</script>';
// 使用 safeHtml 标签函数处理模板字符串
const safeOutput = safeHtml`<div>${userInput}</div>`;
// 输出结果
console.log(safeOutput); // <div><script>alert("XSS")</script></div>
b) 国际化处理
javascript
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查询构建
javascript
function sqlQuery(strings, ...values) {
// 实际应用中应该使用数据库驱动提供的参数化查询
let query = strings[0];
for (let i = 0; i < values.length; i++) {
query += `$${i + 1}` + strings[i + 1];
}
return {
text: query,
values: values
};
}
const userId = 123;
const query = sqlQuery`SELECT * FROM users WHERE id = ${userId}`;
// {
// text: "SELECT * FROM users WHERE id = $1",
// values: [123]
// }
五、特殊字符处理
1. 转义字符
javascript
console.log(`反引号: \` 美元符号: \${`); // "反引号: ` 美元符号: ${"
2. 原始字符串
使用String.raw
标签获取原始字符串(不处理转义字符):
javascript
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 < values.length; i++) {
result += values[i] + strings.raw[i + 1];
}
return result;
}
六、性能考量
- 静态字符串:对于纯静态字符串,模板字符串与普通字符串性能相当
- 动态插值:频繁变化的插值内容可能影响性能,在极端性能敏感场景需测试
- 标签模板:自定义处理会增加开销,但通常可忽略不计
七、最佳实践
-
优先使用模板字符串:替代所有字符串拼接场景
-
复杂逻辑处理:对于复杂插值逻辑,考虑提前计算表达式
javascript// 不推荐 console.log(`Result: ${calculateA() + calculateB() * complexCalculation()}`); // 推荐 const result = calculateA() + calculateB() * complexCalculation(); console.log(`Result: ${result}`);
-
多行缩进处理 :使用
.trim()
消除不必要的缩进javascriptfunction getHtml() { return ` <div> <p>Content</p> </div> `.trim(); }
-
安全注意事项 :
• 不要直接将用户输入插入HTML/URL/SQL
• 使用专用转义库或标签模板处理危险内容
八、浏览器兼容性
现代浏览器均支持模板字符串特性,对于旧版浏览器需要通过Babel等工具转译:
• ES6转ES5:将模板字符串转换为普通字符串拼接
• 标签模板:转换为函数调用形式
九、扩展应用
1. 配合React等框架
jsx
const name = 'Alice';
const element = <div>Hello, {name}!</div>;
// JSX本质上也是一种模板字符串的扩展应用
2. 生成动态CSS
javascript
const primaryColor = '#3498db';
const css = `
.button {
background: ${primaryColor};
padding: 10px 20px;
}
`;
3. 创建DSL(领域特定语言)
javascript
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. 基本用法
javascript
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. 嵌套解构
javascript
const arr = [1, [2, 3], 4];
const [a, [b, c], d] = arr; // a=1, b=2, c=3, d=4
3. 默认值
javascript
const [a=1, b=2] = []; // a=1, b=2
const [a, b=2] = [5]; // a=5, b=2
4. 跳过元素
javascript
const [a, , b] = [1, 2, 3]; // a=1, b=3 (跳过第二个元素)
5. 剩余模式
javascript
const [a, ...rest] = [1, 2, 3]; // a=1, rest=[2, 3]
三、对象解构
1. 基本用法
javascript
const obj = { x: 1, y: 2 };
// 传统方式
const x = obj.x;
const y = obj.y;
// 解构赋值
const { x, y } = obj; // x=1, y=2
2. 别名赋值
javascript
const { x: a, y: b } = { x: 1, y: 2 }; // a=1, b=2
3. 默认值
javascript
const { a=1, b=2 } = {}; // a=1, b=2
const { a: x=1, b: y=2 } = { b: 3 }; // x=1, y=3
4. 嵌套解构
javascript
const obj = { a: { b: 1, c: 2 }, d: 3 };
const { a: { b, c }, d } = obj; // b=1, c=2, d=3
5. 剩余模式
javascript
const { a, ...rest } = { a: 1, b: 2, c: 3 }; // a=1, rest={b:2, c:3}
四、混合解构
可以混合使用数组和对象解构:
javascript
const props = {
arr: [1, { b: 2, c: 3 }]
};
const { arr: [a, { b, c }] } = props; // a=1, b=2, c=3
五、函数参数解构
1. 对象参数解构
javascript
function draw({ x=0, y=0, radius=1 }) {
console.log(x, y, radius);
}
draw({ x: 10, y: 20 }); // 10 20 1
2. 数组参数解构
javascript
function sum([a=0, b=0]) {
return a + b;
}
sum([1, 2]); // 3
3. 复杂参数解构
javascript
function process({
id,
name: firstName,
address: { city } = {}
}) {
console.log(id, firstName, city);
}
process({ id: 1, name: 'Alice', address: { city: 'Beijing' } });
六、特殊应用场景
1. 交换变量值
javascript
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
2. 函数返回多个值
javascript
function getData() {
return [1, 2, 3];
}
const [a, b, c] = getData();
3. 正则表达式匹配
javascript
const url = 'https://example.com/path';
const { 1: protocol, 2: host } = url.match(/(\w+):\/\/([^/]+)/);
4. 模块导入
javascript
import { Component, useState } from 'react';
5. 配置对象处理
javascript
function init({
width = 100,
height = 200,
color = 'red'
} = {}) {
// 使用解构参数并设置默认值
}
七、注意事项
-
解构失败 :如果解构不成功,变量的值等于
undefined
javascriptconst [a] = []; // a=undefined const { b } = {}; // b=undefined
-
模式匹配:解构赋值的左边是模式,不是变量
javascriptconst { a: b } = { a: 1 }; // 模式是a,变量是b
-
不可迭代值:对非迭代值使用数组解构会报错
javascriptconst [a] = null; // TypeError
-
已声明变量:已声明变量解构需要用括号包裹
javascriptlet a; ({ a } = { a: 1 }); // 必须加括号
-
默认值生效条件 :只有当解构的值严格等于
undefined
时,默认值才会生效javascriptconst { a = 1 } = { a: null }; // a=null
八、最佳实践
- 合理使用默认值:为可能不存在的属性设置默认值
- 避免过度嵌套:过深的解构会降低代码可读性
- 明确变量名:使用别名时选择有意义的名称
- 处理错误情况:考虑解构失败时的处理方式
- 文档注释:对复杂解构添加注释说明结构
九、浏览器兼容性
现代浏览器均支持解构赋值,对于旧版浏览器需要通过Babel等工具转译:
• 对象解构转换为Object.assign()
或逐个属性赋值
• 数组解构转换为下标访问
解构赋值是ES6中最实用的特性之一,合理使用可以显著提高代码的简洁性和可读性,特别是在处理复杂数据结构时。
(五)、函数参数默认值
javascript
function sayHello(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
sayHello(); // Hello, Guest!
(六)、ES6 扩展运算符(Spread Operator)深度解析
扩展运算符(...
)是 ES6 引入的一个重要特性,它允许将可迭代对象(如数组、字符串、Map、Set 等)"展开"为单独的元素。
一、基本语法与概念
扩展运算符使用三个点(...
)表示,主要功能是将一个可迭代对象展开为多个元素。
javascript
const arr = [1, 2, 3];
console.log(...arr); // 1 2 3
二、数组中的应用
1. 数组复制(浅拷贝)
javascript
const original = [1, 2, 3];
const copy = [...original]; // 创建新数组
2. 数组合并
javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
3. 数组解构
javascript
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]
4. 插入元素
javascript
const numbers = [1, 2, 3];
const newNumbers = [0, ...numbers, 4]; // [0, 1, 2, 3, 4]
5. 替代 apply 方法
javascript
// ES5
Math.max.apply(null, [1, 2, 3]);
// ES6
Math.max(...[1, 2, 3]);
三、对象中的应用(ES2018+)
1. 对象复制(浅拷贝)
javascript
const obj = { a: 1, b: 2 };
const copy = { ...obj }; // { a: 1, b: 2 }
2. 对象合并
javascript
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2 }
3. 属性覆盖
javascript
const defaults = { color: 'red', size: 'medium' };
const settings = { ...defaults, color: 'blue' };
// { color: 'blue', size: 'medium' }
4. 添加新属性
javascript
const person = { name: 'Alice' };
const withAge = { ...person, age: 25 }; // { name: 'Alice', age: 25 }
四、函数参数中的应用
1. 收集剩余参数
javascript
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
2. 传递数组参数
javascript
const numbers = [1, 2, 3];
sum(...numbers); // 等同于 sum(1, 2, 3)
五、其他可迭代对象中的应用
1. 字符串展开
javascript
const str = 'hello';
const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
2. Set 展开
javascript
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1, 2, 3]
3. Map 展开
javascript
const map = new Map([['a', 1], ['b', 2]]);
const entries = [...map]; // [['a', 1], ['b', 2]]
六、高级用法
1. 实现数组扁平化
javascript
function flatten(arr) {
return [].concat(...arr);
}
flatten([[1], [2, 3], [4]]); // [1, 2, 3, 4]
2. 实现深度克隆(仅适用于特定情况)
javascript
const deepClone = obj => JSON.parse(JSON.stringify(obj));
const cloned = deepClone({ a: [1, 2], b: { c: 3 } });
3. 条件展开
javascript
const condition = true;
const obj = {
...(condition && { a: 1 }),
b: 2
}; // { a: 1, b: 2 } 或 { b: 2 }
七、注意事项
-
浅拷贝问题:扩展运算符只进行浅拷贝
javascriptconst obj = { a: { b: 1 } }; const copy = { ...obj }; copy.a.b = 2; console.log(obj.a.b); // 2 (被修改)
-
性能考虑:对于大型数组/对象,扩展运算符可能不是最高效的选择
-
浏览器兼容性:对象展开是 ES2018 特性,旧环境可能需要 Babel 转译
-
不可迭代对象:不能展开普通对象(在数组上下文中)
javascriptconst 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 是可迭代对象:
javascript
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. 基本语法
javascript
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 成功 */) {
resolve(value); // 状态变为fulfilled
} else {
reject(error); // 状态变为rejected
}
});
二、Promise 实例方法
1. then() 方法
javascript
promise.then(
value => { /* 成功处理 */ },
error => { /* 失败处理 */ }
);
2. catch() 方法
javascript
promise.catch(
error => { /* 失败处理 */ }
);
3. finally() 方法
javascript
promise.finally(
() => { /* 无论成功失败都会执行 */ }
);
三、Promise 静态方法
1. Promise.resolve()
javascript
Promise.resolve('success')
.then(val => console.log(val)); // 'success'
2. Promise.reject()
javascript
Promise.reject('error')
.catch(err => console.log(err)); // 'error'
3. Promise.all()
javascript
Promise.all([promise1, promise2])
.then(values => { /* 所有promise都成功 */ })
.catch(error => { /* 任一promise失败 */ });
4. Promise.race()
javascript
Promise.race([promise1, promise2])
.then(value => { /* 第一个完成的promise */ });
5. Promise.allSettled()
javascript
Promise.allSettled([promise1, promise2])
.then(results => { /* 所有promise都完成 */ });
6. Promise.any()
javascript
Promise.any([promise1, promise2])
.then(value => { /* 第一个成功的promise */ })
.catch(errors => { /* 所有promise都失败 */ });
四、Promise 链式调用
Promise 的 then() 方法返回一个新的 Promise,可以实现链式调用:
javascript
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(finalResult))
.catch(failureCallback);
五、Promise 错误处理
1. 使用 catch()
javascript
promise
.then(handleSuccess)
.catch(handleError);
2. then() 的第二个参数
javascript
promise
.then(handleSuccess, handleError);
3. 区别
• .then(success, error)
:只能捕获当前 then 之前的错误
• .catch(error)
:可以捕获整个链中的错误
六、Promise 最佳实践
- 总是返回 Promise:在 then() 回调中返回 Promise 或值
- 避免嵌套:使用链式调用而非嵌套
- 总是捕获错误:使用 catch() 处理错误
- 命名 Promise:给 Promise 变量有意义的名称
- 避免冗余代码:合理使用 Promise 静态方法
七、Promise 实现示例
1. 封装 setTimeout
javascript
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000).then(() => console.log('1秒后执行'));
2. 封装 XMLHttpRequest
javascript
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
javascript
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 之上的语法糖:
javascript
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: 可以封装一个重试函数:
javascript
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. 基本语法
javascript
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)
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
2. 实例方法
javascript
class Person {
// ...
greet() {
console.log(`Hi, I'm ${this.name}`);
}
}
3. 静态方法 (static)
javascript
class MathUtils {
static sum(a, b) {
return a + b;
}
}
MathUtils.sum(1, 2); // 3
4. 静态属性
javascript
class Config {
static apiUrl = 'https://api.example.com';
}
console.log(Config.apiUrl);
5. 私有字段 (ES2022)
javascript
class Counter {
#count = 0; // 私有字段
increment() {
this.#count++;
}
getCount() {
return this.#count;
}
}
三、Class 继承
1. extends 继承
javascript
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. 方法重写
javascript
class Student extends Person {
sayHello() {
super.sayHello(); // 调用父类方法
console.log("I'm a student");
}
}
4. 继承内置类
javascript
class MyArray extends Array {
first() {
return this[0];
}
last() {
return this[this.length - 1];
}
}
四、Getter 和 Setter
javascript
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. 命名类表达式
javascript
const Person = class NamedPerson {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
};
2. 匿名类表达式
javascript
const Person = class {
// ...
};
六、Class 与原型的关系
Class 本质上是构造函数的语法糖:
javascript
typeof Person; // "function"
Person.prototype.constructor === Person; // true
七、Class 与函数声明的重要区别
- 提升(hoisting):类声明不会被提升
- 严格模式:类声明和类表达式默认在严格模式下执行
- 调用方式 :类必须使用
new
调用 - 方法枚举:类方法不可枚举
八、高级用法
1. Mixin 模式
javascript
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. 抽象基类
javascript
class Abstract {
constructor() {
if (new.target === Abstract) {
throw new Error('Cannot instantiate abstract class');
}
}
}
class Concrete extends Abstract {}
3. Symbol.iterator 实现
javascript
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const range = new Range(1, 5);
[...range]; // [1, 2, 3, 4, 5]
九、最佳实践
- 单一职责原则:每个类应该只有一个职责
- 开放封闭原则:对扩展开放,对修改封闭
- 优先组合而非继承:减少复杂的继承层次
- 合理使用私有字段:保护内部状态
- 文档注释:使用 JSDoc 注释类和方法
十、常见问题解答
Q1: Class 与构造函数有什么区别?
A: Class 本质上是构造函数的语法糖,但提供了更清晰的语法和更好的错误检查。
Q2: 如何实现接口?
A: JavaScript 没有接口,可以通过抽象类或 TypeScript 实现。
Q3: 如何实现多重继承?
A: JavaScript 不支持多重继承,可以通过 Mixin 模式模拟。
Q4: 私有方法和属性有哪些实现方式?
A:
• 使用 ES2022 私有字段 #field
• 使用 WeakMap 或 Symbol 模拟
• 命名约定(如 _privateMethod
)
Q5: 如何检测一个类是否是另一个类的子类?
A: 使用 instanceof
或 Object.prototype.isPrototypeOf()
javascript
class Parent {}
class Child extends Parent {}
console.log(Child.prototype instanceof Parent); // true
console.log(Parent.prototype.isPrototypeOf(Child.prototype)); // true
ES6 Class 为 JavaScript 提供了更优雅的面向对象编程方式,虽然它本质上是基于原型的继承的语法糖,但这种语法更符合大多数程序员的习惯,也使代码更易于理解和维护。
(九)、模块系统
1. 导出
javascript
// math.js
export const PI = 3.1415;
export function square(x) {
return x * x;
}
2. 导入
javascript
import { PI, square } from './math.js';
console.log(square(PI)); // 9.86902225
(十)、Symbol
javascript
const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false
const obj = {
[sym1]: 'value'
};
console.log(obj[sym1]); // value
(十一)、迭代器和生成器
1. 迭代器
javascript
const iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) {
return { value: step, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const value of iterable) {
console.log(value); // 1, 2, 3
}
2. 生成器
javascript
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
(十二)、Set和Map
1. Set
javascript
const set = new Set([1, 2, 3, 3, 4]);
console.log(set.size); // 4
console.log([...set]); // [1, 2, 3, 4]
2. Map
javascript
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
console.log(map.get('name')); // Alice
(十三)、Proxy和Reflect
1. Proxy
javascript
const target = {};
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
2. Reflect
javascript
const obj = { a: 1 };
console.log(Reflect.get(obj, 'a')); // 1
Reflect.set(obj, 'b', 2);
console.log(obj.b); // 2
(十四)、新的数据类型
1. TypedArray
javascript
const buffer = new ArrayBuffer(16);
const int32View = new Int32Array(buffer);
2. DataView
javascript
const view = new DataView(buffer);
view.setInt32(0, 42);
console.log(view.getInt32(0)); // 42
(十五)、字符串和数组新增方法
1. 字符串方法
javascript
'hello'.startsWith('he'); // true
'hello'.endsWith('lo'); // true
'hello'.includes('ell'); // true
'abc'.repeat(3); // 'abcabcabc'
2. 数组方法
javascript
[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]
(十六)、尾调用优化
javascript
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total); // 尾调用优化
}
(十七)、二进制和八进制字面量
javascript
const binary = 0b1010; // 10
const octal = 0o12; // 10
(十八_、Object新增方法
javascript
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开发的基石。