ES6
一、let/const 与块级作用域
1. let 与 const 的区别
定义:
let:声明块级作用域的变量,值可以重新赋值const:声明块级作用域的常量,声明时必须初始化,且不能重新赋值
原理:
- 两者都不会发生变量提升,存在暂时性死区(TDZ)
- 都绑定在块级作用域
{}中 const保证的是变量指向的内存地址不变,对于引用类型,其内部属性仍可修改
示例代码:
// let 示例
let a = 10;
a = 20; // 可以重新赋值
console.log(a); // 20
// const 示例
const b = 10;
// b = 20; // TypeError: Assignment to constant variable
// const 引用类型
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 可以修改属性
obj.age = 25; // 可以添加属性
console.log(obj); // { name: 'Bob', age: 25 }
// obj = {}; // TypeError: 不能重新赋值
常见误区:
- 误区:
const声明的对象完全不能修改 - 正解:
const只是保证变量指向的内存地址不变,对象的属性、数组的元素仍然可以修改 - 如果需要完全不可变,应使用
Object.freeze()
2. let/const 与 var 的区别
对比表格:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 有(声明提升到函数顶部) | 无(存在暂时性死区) | 无(存在暂时性死区) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 初始化 | 可选,默认 undefined | 可选,默认 undefined | 必须初始化 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 全局变量属性 | 会成为 window 的属性 | 不会成为 window 的属性 | 不会成为 window 的属性 |
示例代码:
// 1. 作用域差异
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a); // 1
// console.log(b); // ReferenceError: b is not defined
// console.log(c); // ReferenceError: c is not defined
// 2. 变量提升差异
console.log(x); // undefined(变量提升)
// console.log(y); // ReferenceError: 暂时性死区
var x = 10;
let y = 20;
// 3. 重复声明
var m = 1;
var m = 2; // 正常
// let n = 1;
// let n = 2; // SyntaxError: Identifier 'n' has already been declared
// 4. 全局变量
var global1 = 'var';
let global2 = 'let';
console.log(window.global1); // 'var'
console.log(window.global2); // undefined
选择策略:
- 优先使用
const,表示值不会被重新赋值 - 需要重新赋值时使用
let - 避免使用
var
3. 什么是块级作用域?
定义:
块级作用域是指由一对大括号 {} 包裹的代码区域,包括 if 语句、for 循环、while 循环、函数体等。在块级作用域内声明的变量(使用 let/const)只在当前块内有效。
为什么需要块级作用域:
- 避免内层变量覆盖外层变量
// 使用 var 的问题
var tmp = 'global';
function f() {
console.log(tmp); // undefined(变量提升导致)
if (true) {
var tmp = 'local';
}
}
f();
// 使用 let 解决
let tmp2 = 'global';
function f2() {
console.log(tmp2); // 'global'
if (true) {
let tmp2 = 'local';
console.log(tmp2); // 'local'
}
}
f2();
- 避免循环变量泄露为全局变量
// 使用 var
for (var i = 0; i < 5; i++) {
console.log(i);
}
console.log(i); // 5(i 泄露到外部)
// 使用 let
for (let j = 0; j < 5; j++) {
console.log(j);
}
// console.log(j); // ReferenceError: j is not defined
经典面试题:
// 使用 var
var arr1 = [];
for (var i = 0; i < 3; i++) {
arr1.push(function() { console.log(i); });
}
arr1.forEach(fn => fn()); // 3, 3, 3
// 使用 let
var arr2 = [];
for (let i = 0; i < 3; i++) {
arr2.push(function() { console.log(i); });
}
arr2.forEach(fn => fn()); // 0, 1, 2
4. 什么是暂时性死区(TDZ)?
定义:
暂时性死区(Temporal Dead Zone, TDZ)是指使用 let/const 声明变量时,从进入作用域到变量声明完成之间的区域。在这个区域内访问变量会抛出 ReferenceError。
原理:
let/const声明的变量不会被提升- JavaScript 引擎在扫描代码时发现
let/const声明,会在该作用域中创建绑定,但在执行到声明语句之前,变量处于"暂时性死区" - 访问 TDZ 中的变量会抛出
ReferenceError
示例代码:
// TDZ 示例
console.log(a); // undefined(var 变量提升)
var a = 10;
// console.log(b); // ReferenceError: b is not defined
let b = 20;
// console.log(c); // ReferenceError: c is not defined
const c = 30;
// TDZ 检测
function test() {
console.log(x); // ReferenceError
let x = 10;
}
test();
// TDZ 与 typeof
console.log(typeof undeclared); // 'undefined'
// console.log(typeof tdzVar); // ReferenceError(即使使用 typeof)
let tdzVar = 10;
常见误区:
- TDZ 不是代码中的某个位置,而是时间上的概念
- TDZ 内变量已经存在(已创建绑定),只是不可访问
typeof在 TDZ 内也会抛出错误,这是 ES6 的改变
5. const 本质与 const 变量能否修改
const 本质:
const 实际上保证的是变量指向的内存地址不可变,而不是值不可变。
基本类型:值直接存储在变量指向的内存地址,所以不可修改
const num = 10;
// num = 20; // TypeError
const str = 'hello';
// str = 'world'; // TypeError
引用类型:变量存储的是引用地址,地址不可变,但地址指向的对象内容可以修改
const obj = { a: 1 };
obj.a = 2; // 正常
obj.b = 3; // 正常
// obj = {}; // TypeError
const arr = [1, 2, 3];
arr.push(4); // 正常
arr[0] = 10; // 正常
// arr = []; // TypeError
完全冻结对象:
const frozen = Object.freeze({ a: 1, b: 2 });
// frozen.a = 3; // 静默失败(严格模式下抛错)
// frozen.c = 4; // 静默失败
console.log(frozen.a); // 1
二、箭头函数
1. 箭头函数的特点
定义:
箭头函数是 ES6 引入的更简洁的函数写法,使用 => 语法定义。
基本语法:
// 传统函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
// 单个参数可省略括号
const double = x => x * 2;
// 无参数需要空括号
const sayHi = () => console.log('Hi');
// 多行语句需要大括号和 return
const sum = (a, b) => {
const result = a + b;
return result;
};
// 返回对象字面量需要加括号
const createObj = (name, age) => ({ name, age });
核心特点:
- 不绑定 this:this 指向定义时所在的外层作用域
- 不绑定 arguments:使用外层函数的 arguments
- 不能作为构造函数:不能使用
new调用 - 没有 prototype 属性
- 不能使用 yield:不能作为 Generator 函数
- 不能换行写
=>
2. 箭头函数与普通函数的区别
对比表格:
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
| this 绑定 | 动态绑定(调用时决定) | 静态绑定(定义时决定,继承外层) |
| arguments | 有自己的 arguments 对象 | 没有,使用外层的 arguments |
| 构造函数 | 可以作为构造函数(new) | 不能作为构造函数 |
| prototype | 有 prototype 属性 | 没有 prototype 属性 |
| call/apply/bind | 可以改变 this 指向 | 无法改变 this 指向 |
| 语法 | 标准函数语法 | 简洁的 => 语法 |
| yield | 可用于 Generator | 不能使用 yield |
示例代码:
// 1. this 指向差异
const obj = {
name: 'Alice',
regularFunc: function() {
console.log(this.name); // 'Alice'
},
arrowFunc: () => {
console.log(this.name); // undefined(指向全局)
}
};
obj.regularFunc();
obj.arrowFunc();
// 2. 回调函数中的 this
const person = {
name: 'Bob',
greet: function() {
setTimeout(function() {
console.log(this.name); // undefined(this 指向 window)
}, 100);
}
};
person.greet();
const person2 = {
name: 'Bob',
greet: function() {
setTimeout(() => {
console.log(this.name); // 'Bob'(继承外层 this)
}, 100);
}
};
person2.greet();
// 3. arguments 差异
function normalFn() {
console.log(arguments); // Arguments 对象
}
normalFn(1, 2, 3);
const arrowFn = () => {
// console.log(arguments); // ReferenceError
};
// 4. 构造函数
function Person(name) {
this.name = name;
}
const p = new Person('Alice'); // 正常
const ArrowPerson = (name) => {
this.name = name;
};
// const p2 = new ArrowPerson('Bob'); // TypeError
不适用场景:
- 对象方法(this 不指向对象)
- 需要动态 this 的回调函数
- 构造函数
- 原型方法
- 需要 arguments 的场景
3. 箭头函数的 this
定义:
箭头函数的 this 是在定义时绑定的,继承自外层最近的作用域的 this,且不能被 call、apply、bind 改变。
原理:
- 普通函数的 this 是调用时确定的(动态绑定)
- 箭头函数的 this 是定义时确定的(词法作用域)
- 箭头函数没有自己的 this 绑定
示例代码:
// 全局环境
const fn = () => console.log(this);
fn(); // window(全局 this)
// 对象方法中的箭头函数
const obj = {
name: 'obj',
fn: () => console.log(this)
};
obj.fn(); // window(箭头函数定义在全局)
// 嵌套函数中的箭头函数
function outer() {
console.log('outer this:', this);
const inner = () => {
console.log('inner this:', this);
};
inner();
}
outer.call({ name: 'test' });
// outer this: { name: 'test' }
// inner this: { name: 'test' }
// call/apply/bind 无法改变箭头函数的 this
const arrow = () => console.log(this);
arrow.call({ name: 'test' }); // 仍然指向外层 this
三、模板字符串
1. 模板字符串的特点与用法
定义: 模板字符串(Template Literals)是 ES6 引入的增强版字符串,使用反引号(`)包裹,支持字符串插值、多行字符串和标签模板。
特点:
- 支持变量插值:使用
${}语法嵌入表达式 - 支持多行字符串:保留换行和空格
- 支持表达式计算:
${}内可以是任意 JavaScript 表达式 - 支持标签模板:在模板字符串前加标签函数
示例代码:
// 基本用法
const name = 'Alice';
const age = 25;
// 变量插值
const str1 = `My name is ${name}, I am ${age} years old.`;
console.log(str1); // 'My name is Alice, I am 25 years old.'
// 多行字符串
const str2 = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
console.log(str2);
// 表达式计算
const a = 10, b = 20;
console.log(`${a} + ${b} = ${a + b}`); // '10 + 20 = 30'
// 嵌套模板字符串
const users = ['Alice', 'Bob'];
const html = `<ul>
${users.map(user => `<li>${user}</li>`).join('')}
</ul>`;
// 调用函数
const greet = () => 'Hello';
console.log(`${greet()}, World!`); // 'Hello, World!'
2. 标签模板
定义: 标签模板(Tagged Templates)是模板字符串的高级用法,在模板字符串前加上一个函数名(标签),该函数会接收解析后的字符串片段和表达式值作为参数。
原理: 标签函数接收两个参数:
strings:模板字符串的静态部分(数组)...values:模板字符串中的表达式值(剩余参数)
示例代码:
// 标签函数
function tag(strings, ...values) {
console.log(strings); // ['Hello ', ' world ', '']
console.log(values); // ['Alice']
return strings[0] + values[0] + strings[1];
}
const name = 'Alice';
const result = tag`Hello ${name} world!`;
console.log(result); // 'Hello Alice world'
// 实际应用:防止 XSS
function safeHTML(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = i < values.length ?
String(values[i]).replace(/</g, '<').replace(/>/g, '>') : '';
return result + str + value;
}, '');
}
const userInput = '<script>alert(1)</script>';
const safe = safeHTML`<div>${userInput}</div>`;
console.log(safe); // '<div><script>alert(1)</script></div>'
// 实际应用:国际化
function i18n(strings, ...values) {
const lang = 'zh';
// 根据语言翻译
return strings.reduce((result, str, i) => {
return result + str + (values[i] || '');
}, '');
}
3. 模板字符串与普通字符串的区别
对比表格:
| 特性 | 普通字符串 | 模板字符串 |
|---|---|---|
| 引号 | 单引号或双引号 | 反引号(`) |
| 变量插值 | 需要拼接 | ${} 语法 |
| 多行字符串 | 需要 \n 或 + | 直接换行 |
| 表达式 | 不支持 | 支持任意表达式 |
| 标签模板 | 不支持 | 支持 |
示例代码:
const name = 'Alice';
const age = 25;
// 普通字符串
const str1 = 'My name is ' + name + ', I am ' + age + ' years old.';
const str2 = 'Line 1\nLine 2\nLine 3';
// 模板字符串
const str3 = `My name is ${name}, I am ${age} years old.`;
const str4 = `Line 1
Line 2
Line 3`;
四、解构赋值
1. 数组解构赋值
定义: 解构赋值是 ES6 引入的语法,允许从数组或对象中提取值,按对应位置或属性名赋值给变量。
基本语法:
// 基本数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 跳过某些值
const [x, , z] = [1, 2, 3];
console.log(x, z); // 1 3
// 剩余元素
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
// 默认值
const [p, q = 10] = [1];
console.log(p, q); // 1 10
// 嵌套解构
const [m, [n, o]] = [1, [2, 3]];
console.log(m, n, o); // 1 2 3
// 变量交换
let i = 1, j = 2;
[i, j] = [j, i];
console.log(i, j); // 2 1
成功条件:
- 等号右边必须是可迭代对象(具有 Iterator 接口)
- 数组、字符串、Set、Map、NodeList 等可迭代
- 普通对象不可进行数组解构
// 成功
const [a, b] = 'hi';
console.log(a, b); // 'h', 'i'
const [c, d] = new Set([1, 2]);
console.log(c, d); // 1, 2
// 失败
// const [e, f] = { a: 1, b: 2 }; // TypeError: is not iterable
2. 对象解构赋值
基本语法:
// 基本对象解构
const { name, age } = { name: 'Alice', age: 25 };
console.log(name, age); // 'Alice', 25
// 重命名
const { name: userName, age: userAge } = { name: 'Bob', age: 30 };
console.log(userName, userAge); // 'Bob', 30
// 默认值
const { x = 1, y = 2 } = { x: 10 };
console.log(x, y); // 10, 2
// 嵌套解构
const { user: { name, address: { city } } } = {
user: {
name: 'Alice',
address: { city: 'Beijing' }
}
};
console.log(name, city); // 'Alice', 'Beijing'
// 函数参数解构
function greet({ name, age }) {
console.log(`Hi ${name}, you are ${age}`);
}
greet({ name: 'Alice', age: 25 });
// 函数参数解构 + 默认值
function createConfig({ theme = 'light', fontSize = 14 } = {}) {
return { theme, fontSize };
}
console.log(createConfig()); // { theme: 'light', fontSize: 14 }
解构赋值成功条件:
- 等号右边必须能转换为对象
- 解构的属性名必须在对象中存在(或原型链上)
- 如果等号右边是
undefined或null,会报错
// 成功
const { toString } = 123; // Number 包装对象
console.log(typeof toString); // 'function'
// 失败
// const { a } = undefined; // TypeError
// const { a } = null; // TypeError
如何让 var [a, b] = {a:1, b:2} 解构成功:
对象本身不可迭代,需要为其添加 Iterator 接口:
const obj = {
a: 1,
b: 2,
[Symbol.iterator]() {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
if (index < keys.length) {
return { value: this[keys[index++]], done: false };
}
return { done: true };
}
};
}
};
const [x, y] = obj;
console.log(x, y); // 1, 2
3. 解构赋值的应用场景
// 1. 交换变量
let x = 1, y = 2;
[x, y] = [y, x];
// 2. 函数多返回值
function getStats() {
return { min: 1, max: 100, avg: 50 };
}
const { min, max, avg } = getStats();
// 3. 提取 JSON 数据
const json = '{"id": 1, "name": "Alice", "email": "alice@test.com"}';
const { id, name } = JSON.parse(json);
// 4. 函数参数默认值
function ajax({ url, method = 'GET', data = {} } = {}) {
console.log(url, method, data);
}
ajax({ url: '/api/users' });
// 5. 遍历 Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value);
}
// 6. 加载模块
const { readFile, writeFile } = require('fs');
import { Component, useState } from 'react';
五、默认参数与剩余参数/扩展运算符
1. 函数默认参数
定义:
ES6 允许为函数参数设置默认值,当参数未传入或为 undefined 时使用默认值。
示例代码:
// 基本用法
function greet(name = 'Guest', age = 18) {
console.log(`Hi ${name}, age ${age}`);
}
greet(); // 'Hi Guest, age 18'
greet('Alice'); // 'Hi Alice, age 18'
greet('Bob', 25); // 'Hi Bob, age 25'
greet(undefined, 30); // 'Hi Guest, age 30'
// 注意:null 不会触发默认值
greet(null, 20); // 'Hi null, age 20'
// 默认值可以是表达式
function createId() {
return Date.now();
}
function createUser(name, id = createId()) {
return { name, id };
}
// 默认值与解构结合
function fetch({ url, method = 'GET' } = { url: '/default' }) {
console.log(url, method);
}
fetch(); // '/default' 'GET'
// 参数作用域
let x = 'global';
function fn(y = x) {
let x = 'local';
console.log(y); // 'global'(参数默认值在全局作用域求值)
}
fn();
2. 剩余参数(Rest 参数)
定义:
剩余参数使用 ... 语法,将多余的参数收集到一个数组中,替代 arguments 对象。
示例代码:
// 基本用法
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// 与普通参数结合
function mix(a, b, ...rest) {
console.log(a, b, rest);
}
mix(1, 2, 3, 4, 5); // 1, 2, [3, 4, 5]
// 箭头函数中的 rest
const log = (...args) => console.log(...args);
log('a', 'b', 'c');
// 解构中的 rest
const [first, ...others] = [1, 2, 3, 4];
console.log(first); // 1
console.log(others); // [2, 3, 4]
const { name, ...rest } = { name: 'Alice', age: 25, city: 'Beijing' };
console.log(name); // 'Alice'
console.log(rest); // { age: 25, city: 'Beijing' }
3. Rest 参数与 arguments 的区别
对比表格:
| 特性 | arguments | Rest 参数 |
|---|---|---|
| 类型 | 类数组对象 | 真正的数组 |
| 包含内容 | 所有实参 | 只包含未命名的参数 |
| 箭头函数 | 不可用 | 可用(使用外层函数的) |
| 数组方法 | 不能直接使用 | 可以直接使用 |
| 语法 | 隐式存在 | 显式声明 ...args |
示例代码:
// arguments 示例
function fn1() {
console.log(arguments); // Arguments 对象
console.log(Array.isArray(arguments)); // false
// arguments.map is not a function
const arr = Array.from(arguments);
console.log(arr.map(x => x * 2)); // [2, 4, 6]
}
fn1(1, 2, 3);
// Rest 参数示例
function fn2(...args) {
console.log(args); // [1, 2, 3]
console.log(Array.isArray(args)); // true
console.log(args.map(x => x * 2)); // [2, 4, 6]
}
fn2(1, 2, 3);
// 箭头函数
const arrowFn = (...args) => {
console.log(args); // [1, 2, 3]
};
arrowFn(1, 2, 3);
4. 扩展运算符(Spread 运算符)
定义:
扩展运算符使用 ... 语法,将数组或对象展开为独立的元素,用于函数调用、数组/对象合并等场景。
示例代码:
// 1. 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]
// 2. 函数调用
function add(a, b, c) {
return a + b + c;
}
const nums = [1, 2, 3];
console.log(add(...nums)); // 6
// 3. 数组拷贝(浅拷贝)
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3]
console.log(copy); // [1, 2, 3, 4]
// 4. 字符串转数组
const chars = [...'hello'];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// 5. 对象展开(ES2018)
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 3, c: 4 }
// 6. NodeList 转数组
const divs = document.querySelectorAll('div');
const divArray = [...divs];
// 7. 与解构结合
const [first, ...rest] = [1, 2, 3, 4];
六、Symbol 与迭代器
1. Symbol 的特点与用途
定义: Symbol 是 ES6 引入的第七种原始数据类型,表示独一无二的值,主要用于创建唯一的对象属性名。
特点:
- 唯一性:每次调用
Symbol()都返回不同的值 - 隐藏性:不会出现在
for...in、for...of、Object.keys()中 - 不可枚举:但可通过
Object.getOwnPropertySymbols()获取 - 不能 new:Symbol 是原始值,不是对象
示例代码:
// 基本用法
const s1 = Symbol('description');
const s2 = Symbol('description');
console.log(s1 === s2); // false(即使描述相同也不同)
console.log(s1.toString()); // 'Symbol(description)'
// 作为对象属性名
const mySymbol = Symbol();
const obj = {
[mySymbol]: 'value'
};
obj[mySymbol]; // 'value'
// 隐藏属性
const obj2 = {
[Symbol('hidden')]: 'secret',
public: 'visible'
};
console.log(Object.keys(obj2)); // ['public']
console.log(Object.getOwnPropertySymbols(obj2)); // [Symbol(hidden)]
// Symbol.for() 共享 Symbol
const shared1 = Symbol.for('app.key');
const shared2 = Symbol.for('app.key');
console.log(shared1 === shared2); // true
console.log(Symbol.keyFor(shared1)); // 'app.key'
// 内置 Symbol
// Symbol.iterator - 定义对象的迭代行为
// Symbol.toPrimitive - 定义对象转原始值的行为
// Symbol.toStringTag - 定义 Object.prototype.toString 的返回值
用途:
- 定义唯一的对象属性名,避免命名冲突
- 实现私有属性(模拟)
- 定义对象的迭代行为(Symbol.iterator)
- 定制类型转换(Symbol.toPrimitive)
- 定制 toString 输出(Symbol.toStringTag)
2. 内置 Symbol 值
// Symbol.iterator - 可迭代协议
const iterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
}
return { done: true };
}
};
}
};
for (const item of iterable) {
console.log(item); // 1, 2, 3
}
// Symbol.toPrimitive - 类型转换
const obj = {
value: 100,
[Symbol.toPrimitive](hint) {
console.log('hint:', hint); // 'number' 或 'string' 或 'default'
if (hint === 'number') return this.value * 2;
if (hint === 'string') return `Value: ${this.value}`;
return this.value;
}
};
console.log(+obj); // hint: number -> 200
console.log(String(obj)); // hint: string -> 'Value: 100'
console.log(obj == 100); // hint: default -> 100 -> true
// Symbol.toStringTag - 定制 toString
const myObj = {
[Symbol.toStringTag]: 'MyCustomObject'
};
console.log(Object.prototype.toString.call(myObj)); // '[object MyCustomObject]'
3. 迭代器(Iterator)
定义:
迭代器是一种接口,为不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口(即 Symbol.iterator 方法),就可以被 for...of 遍历。
迭代协议:
- 可迭代协议:对象必须有
Symbol.iterator方法,返回一个迭代器 - 迭代器协议:迭代器必须有
next()方法,返回{ value, done }
示例代码:
// 原生可迭代对象
// Array, String, Map, Set, arguments, NodeList, TypedArray
// 手动实现迭代器
const myIterable = {
items: ['a', 'b', 'c'],
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
}
return { done: true };
}
};
}
};
for (const item of myIterable) {
console.log(item); // 'a', 'b', 'c'
}
// 使用迭代器
const iterator = myIterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { done: true }
4. for...of 与 for...in 的区别
对比表格:
| 特性 | for...of | for...in |
|---|---|---|
| 遍历对象 | 可迭代对象(数组、Set、Map、字符串等) | 所有对象 |
| 遍历内容 | 值 | 键(属性名/索引) |
| 原型链 | 不遍历原型链属性 | 会遍历原型链上的可枚举属性 |
| 自定义对象 | 需要实现 Iterator 接口 | 直接可用 |
示例代码:
const arr = ['a', 'b', 'c'];
arr.custom = 'custom property';
// for...in - 遍历键(包括自定义属性)
for (const key in arr) {
console.log(key); // '0', '1', '2', 'custom'
}
// for...of - 遍历值(仅可迭代元素)
for (const value of arr) {
console.log(value); // 'a', 'b', 'c'
}
// 对象默认不可用 for...of
const obj = { a: 1, b: 2 };
// for (const v of obj) {} // TypeError: obj is not iterable
// 让对象可用 for...of
const iterableObj = {
a: 1,
b: 2,
[Symbol.iterator]() {
const values = Object.values(this);
let index = 0;
return {
next() {
if (index < values.length) {
return { value: values[index++], done: false };
}
return { done: true };
}
};
}
};
for (const v of iterableObj) {
console.log(v); // 1, 2
}
5. 自定义迭代器
// 范围迭代器
function range(start, end) {
return {
[Symbol.iterator]() {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
}
for (const num of range(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5
}
// 斐波那契迭代器(无限)
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
七、Generator 函数
1. Generator 的特点
定义:
Generator 函数是 ES6 提供的异步编程解决方案,使用 function* 声明,内部使用 yield 表达式暂停和恢复执行。
特点:
- 函数声明带
*:function* gen() {} - yield 暂停执行:每次调用
next()执行到下一个yield - 返回迭代器对象:调用 Generator 函数返回迭代器,不会立即执行
- 可传递值:
next(value)可以向 Generator 内部传值 - 可抛出错误:
throw()方法向内部抛错 - 可提前结束:
return()方法提前终止
示例代码:
// 基本用法
function* generator() {
console.log('start');
yield 'first';
console.log('middle');
yield 'second';
console.log('end');
return 'done';
}
const gen = generator(); // 不执行
console.log(gen.next()); // start -> { value: 'first', done: false }
console.log(gen.next()); // middle -> { value: 'second', done: false }
console.log(gen.next()); // end -> { value: 'done', done: true }
// yield 表达式传值
function* gen2() {
const a = yield 1;
console.log('a =', a);
const b = yield 2;
console.log('b =', b);
return a + b;
}
const g = gen2();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next('hello')); // a = hello -> { value: 2, done: false }
console.log(g.next('world')); // b = world -> { value: 'helloworld', done: true }
// yield* 表达式(委托另一个 Generator)
function* genA() {
yield 1;
yield 2;
}
function* genB() {
yield* genA();
yield 3;
}
console.log([...genB()]); // [1, 2, 3]
2. Generator 与 Iterator 的关系
关系说明:
- Generator 函数是 Iterator 接口的生成器
- Generator 函数执行后返回一个迭代器对象
- Generator 是 Iterator 的一种更简洁的实现方式
// Generator 自动实现 Iterator 接口
function* myGenerator() {
yield 'a';
yield 'b';
yield 'c';
}
const gen = myGenerator();
// 可以直接用于 for...of
for (const item of gen) {
console.log(item);
}
// 也可以使用展开运算符
console.log([...myGenerator()]); // ['a', 'b', 'c']
3. Generator 异步应用
Generator + Promise 实现异步流程控制:
// 模拟异步请求
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: 'Alice' }), 1000);
});
}
function fetchPosts(userId) {
return new Promise(resolve => {
setTimeout(() => resolve([{ id: 1, title: 'Post 1' }]), 1000);
});
}
// Generator 异步控制
function* main() {
const user = yield fetchUser(1);
console.log('user:', user);
const posts = yield fetchPosts(user.id);
console.log('posts:', posts);
}
// 自动执行器(简化版 co)
function run(generator) {
const gen = generator();
function step(nextFn) {
const result = nextFn();
if (result.done) return result.value;
Promise.resolve(result.value)
.then(value => step(() => gen.next(value)))
.catch(error => gen.throw(error));
}
step(() => gen.next());
}
run(main);
八、Module 模块系统
1. ES6 模块基础
定义:
ES6 模块是 JavaScript 官方的模块化方案,使用 export 导出、import 导入,每个模块有独立的作用域。
export 命令:
// 命名导出
export const name = 'Alice';
export function greet() { return 'Hello'; }
export class User {}
// 统一导出
const version = '1.0.0';
function log(msg) { console.log(msg); }
export { version, log };
// 重命名导出
export { version as v, log as logger };
import 命令:
// 导入单个
import { name } from './module.js';
// 导入多个
import { name, greet } from './module.js';
// 重命名导入
import { name as userName } from './module.js';
// 整体导入
import * as module from './module.js';
console.log(module.name);
// 默认导入
import defaultExport from './module.js';
// 组合导入
import defaultExport, { name, greet } from './module.js';
// 仅执行模块
import './polyfill.js';
2. export 与 export default 的区别
对比表格:
| 特性 | export(命名导出) | export default(默认导出) |
|---|---|---|
| 数量 | 一个模块可以有多个 | 一个模块只能有一个 |
| 导入语法 | import { name } | import name(无大括号) |
| 重命名 | 导入时 { name as newName } | 导入时可任意命名 |
| 变量/函数/类 | 直接导出 | 导出表达式或值 |
| 匿名函数/类 | 不支持 | 支持 |
示例代码:
// module.js
export const a = 1; // 命名导出
export function fn() {} // 命名导出
export default function() {} // 默认导出
// main.js
import { a, fn } from './module.js'; // 命名导入
import myFn from './module.js'; // 默认导入
3. ES6 模块与 CommonJS 的区别
对比表格:
| 特性 | CommonJS | ES6 Module |
|---|---|---|
| 语法 | require() / module.exports | import / export |
| 加载时机 | 运行时加载(动态) | 编译时加载(静态) |
| 输出值 | 值的拷贝(浅拷贝) | 值的引用(实时绑定) |
| 顶层 this | 指向当前模块 | 指向 undefined |
| 动态导入 | 支持 | 需要 import() |
| 循环依赖 | 可能得到未初始化值 | 更安全(引用方式) |
| Tree Shaking | 不支持 | 支持(静态分析) |
| 浏览器支持 | 需要打包 | 原生支持(script type="module") |
示例代码:
// CommonJS - 值的拷贝
// a.js
let counter = 0;
function add() { counter++; }
module.exports = { counter, add };
// b.js
const { counter, add } = require('./a.js');
add();
console.log(counter); // 0(拷贝的值,不受影响)
// ES6 Module - 值的引用
// a.mjs
export let counter = 0;
export function add() { counter++; }
// b.mjs
import { counter, add } from './a.mjs';
add();
console.log(counter); // 1(实时绑定)
选择策略:
- 浏览器端开发:优先使用 ES6 Module
- Node.js 项目:根据版本选择(v14+ 支持 ES Module)
- 需要动态加载:使用
import()或require() - 需要 Tree Shaking:使用 ES6 Module
4. 动态导入 import()
定义:
import() 是 ES2020 引入的动态导入语法,返回 Promise,支持按需加载和条件加载。
示例代码:
// 条件加载
if (condition) {
import('./moduleA.js').then(module => {
module.default();
});
}
// 按需加载(路由懒加载)
function loadPage(page) {
return import(`./pages/${page}.js`);
}
loadPage('home').then(module => {
module.render();
});
// async/await
async function handleClick() {
const module = await import('./heavyModule.js');
module.init();
}
// 并行加载
const [moduleA, moduleB] = await Promise.all([
import('./moduleA.js'),
import('./moduleB.js')
]);
九、Set、Map 数据结构
1. Set 数据结构
定义: Set 是 ES6 引入的集合数据结构,成员值唯一,不重复。
属性和方法:
// 创建
const set1 = new Set();
const set2 = new Set([1, 2, 3, 3, 3]);
console.log(set2); // Set(3) { 1, 2, 3 }
// 基本方法
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 重复值被忽略
set.add('3');
set.size; // 3
set.has(1); // true
set.delete(1); // true
set.clear(); // 清空
// 遍历方法
const numSet = new Set([1, 2, 3]);
numSet.forEach(val => console.log(val));
for (const val of numSet) {
console.log(val); // 1, 2, 3
}
// 转换为数组
const arr = [...numSet];
const arr2 = Array.from(numSet);
// 数组去重
const duplicateArr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(duplicateArr)];
console.log(unique); // [1, 2, 3]
// 集合运算
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
// 并集
const union = new Set([...a, ...b]); // { 1, 2, 3, 4 }
// 交集
const intersection = new Set([...a].filter(x => b.has(x))); // { 2, 3 }
// 差集
const difference = new Set([...a].filter(x => !b.has(x))); // { 1 }
2. WeakSet
定义: WeakSet 与 Set 类似,但成员只能是对象,且是弱引用(不阻止垃圾回收)。
与 Set 的区别:
| 特性 | Set | WeakSet |
|---|---|---|
| 成员类型 | 任意类型 | 只能是对象 |
| 引用类型 | 强引用 | 弱引用 |
| size 属性 | 有 | 无 |
| 遍历方法 | 支持 | 不支持 |
| 垃圾回收 | 阻止 | 不阻止 |
示例代码:
const ws = new WeakSet();
const obj = {};
const arr = [];
ws.add(obj);
ws.add(arr);
// ws.add(1); // TypeError
console.log(ws.has(obj)); // true
// obj 被垃圾回收后,WeakSet 中的对应引用也会被清除
// 适用于存储对象的状态标记
const visited = new WeakSet();
function processNode(node) {
if (visited.has(node)) return;
visited.add(node);
// 处理节点...
}
3. Map 数据结构
定义: Map 是 ES6 引入的键值对集合,键可以是任意类型(包括对象、函数等)。
属性和方法:
// 创建
const map1 = new Map();
const map2 = new Map([
['name', 'Alice'],
['age', 25]
]);
// 基本方法
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
map.set({ key: 'obj' }, 'value');
map.size; // 3
map.get('name'); // 'Alice'
map.has('age'); // true
map.delete('age'); // true
map.clear(); // 清空
// 遍历方法
const userMap = new Map([
['name', 'Alice'],
['age', 25],
['city', 'Beijing']
]);
for (const [key, value] of userMap) {
console.log(key, value);
}
userMap.forEach((value, key) => {
console.log(key, value);
});
// 转换为数组
const entries = [...userMap];
const keys = [...userMap.keys()];
const values = [...userMap.values()];
// 转换为对象
const obj = Object.fromEntries(userMap);
console.log(obj); // { name: 'Alice', age: 25, city: 'Beijing' }
4. Map 与 Object 的区别
对比表格:
| 特性 | Object | Map |
|---|---|---|
| 键类型 | 字符串、Symbol | 任意类型 |
| 键顺序 | 不保证(有特定规则) | 按插入顺序 |
| size 属性 | 无(需手动计算) | 有 |
| 遍历 | for...in、Object.keys() | for...of、forEach |
| 性能 | 频繁增删较差 | 频繁增删更优 |
| 序列化 | 支持 JSON.stringify() | 不支持 |
| 原型链 | 有默认键 | 无默认键 |
选择策略:
- 键是字符串且需要序列化:使用 Object
- 键是任意类型或需要频繁增删:使用 Map
- 需要保持插入顺序:使用 Map
5. WeakMap
定义: WeakMap 与 Map 类似,但键只能是对象,且是弱引用。
与 Map 的区别:
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意类型 | 只能是对象 |
| 引用类型 | 强引用 | 弱引用 |
| 遍历 | 支持 | 不支持 |
| size/clear | 支持 | 不支持 |
用途:
// 存储私有数据
const privateData = new WeakMap();
class Person {
constructor(name) {
privateData.set(this, { name, ssn: '123-45-6789' });
}
getName() {
return privateData.get(this).name;
}
}
const p = new Person('Alice');
console.log(p.getName()); // 'Alice'
// DOM 元素关联数据
const elementData = new WeakMap();
const div = document.createElement('div');
elementData.set(div, { createdAt: new Date(), id: 1 });
6. WeakRef 与 FinalizationRegistry
// WeakRef - 弱引用对象
const obj = { data: 'important' };
const weakRef = new WeakRef(obj);
console.log(weakRef.deref()); // { data: 'important' }
// FinalizationRegistry - 对象被垃圾回收时的回调
const registry = new FinalizationRegistry(heldValue => {
console.log(`Object with ${heldValue} was garbage collected`);
});
const target = { name: 'target' };
registry.register(target, 'target-metadata');
// target 被回收时会执行回调
十、Promise
1. Promise 基础
定义: Promise 是 ES6 引入的异步编程解决方案,代表一个异步操作的最终完成或失败。
三种状态:
pending(进行中)fulfilled(已成功)rejected(已失败)
特点:
- 状态一旦改变就不可逆
- 只能从
pending->fulfilled或pending->rejected
示例代码:
// 基本用法
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error));
// 链式调用
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve({ id: 1 }), 500);
});
}
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: 'Alice' }), 500);
});
}
fetchData()
.then(data => fetchUser(data.id))
.then(user => console.log(user))
.catch(err => console.error(err));
2. Promise 的方法
// 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]
.catch(err => console.error(err));
// 一个失败的情况
const pFail = Promise.reject('error');
Promise.all([p1, pFail, p3])
.catch(err => console.error(err)); // 'error'
// Promise.race - 最先完成的决定结果
const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 100));
const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 500));
Promise.race([fast, slow])
.then(result => console.log(result)); // 'fast'
// Promise.allSettled - 等待所有完成,返回每个的状态
const promises = [
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
];
Promise.allSettled(promises).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
});
// Promise.any - 第一个成功的决定结果
Promise.any([
Promise.reject('err1'),
Promise.resolve('success'),
Promise.reject('err2')
]).then(result => console.log(result)); // 'success'
// Promise.resolve / Promise.reject
Promise.resolve('value').then(v => console.log(v));
Promise.reject('error').catch(e => console.error(e));
3. Promise 错误处理
// 1. catch 方法
Promise.reject('error')
.then(() => console.log('不执行'))
.catch(err => console.error('捕获错误:', err));
// 2. then 的第二个参数
Promise.reject('error')
.then(
result => console.log(result),
error => console.error('捕获错误:', error)
);
// 3. finally 方法
let isLoading = true;
fetchData()
.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => {
isLoading = false;
console.log('清理工作');
});
// 4. 链式错误处理
Promise.resolve(1)
.then(x => x * 2)
.then(x => { throw new Error('出错'); })
.catch(err => {
console.error(err);
return 0; // 可以恢复链
})
.then(x => console.log(x)); // 0
十一、Class 类
1. Class 的基本语法
定义:
ES6 的 class 是构造函数的语法糖,提供更清晰的面向对象编程方式。
示例代码:
// 基本类定义
class Person {
// constructor 方法
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
return `Hi, I'm ${this.name}`;
}
// getter
get info() {
return `${this.name}(${this.age})`;
}
// setter
set info(value) {
const [name, age] = value.split(',');
this.name = name;
this.age = parseInt(age);
}
}
// 创建实例
const p = new Person('Alice', 25);
console.log(p.greet()); // "Hi, I'm Alice"
console.log(p.info); // "Alice(25)"
p.info = 'Bob,30';
console.log(p.info); // "Bob(30)"
2. 类的继承
// extends 继承
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 必须调用 super
this.grade = grade;
}
// 重写方法
greet() {
return `${super.greet()}, a student`;
}
study() {
return `${this.name} is studying`;
}
}
const s = new Student('Alice', 18, 'A');
console.log(s.greet()); // "Hi, I'm Alice, a student"
console.log(s.study()); // "Alice is studying"
// super 关键字
class Teacher extends Person {
constructor(name, age, subject) {
super(name, age);
this.subject = subject;
}
teach() {
return `${super.greet()} teaches ${this.subject}`;
}
}
3. 静态方法、静态属性、私有属性
class MathHelper {
// 静态属性(ES2022)
static PI = 3.14159;
static version = '1.0';
// 静态方法
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
}
console.log(MathHelper.add(1, 2)); // 3
console.log(MathHelper.PI); // 3.14159
// 私有属性(ES2022)
class BankAccount {
#balance = 0; // 私有属性
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
return this;
}
withdraw(amount) {
if (amount > this.#balance) {
throw new Error('余额不足');
}
this.#balance -= amount;
return this;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(100);
account.deposit(50).withdraw(30);
console.log(account.getBalance()); // 120
// console.log(account.#balance); // SyntaxError
4. 类与构造函数的区别
对比表格:
| 特性 | ES5 构造函数 | ES6 Class |
|---|---|---|
| 语法 | 函数 + prototype | class 关键字 |
| 提升 | 有变量提升 | 无提升(暂时性死区) |
| 调用 | 可以不使用 new | 必须使用 new |
| 继承 | 原型链模拟 | extends + super |
| 静态方法 | 直接挂载到构造函数 | static 关键字 |
| 私有属性 | 闭包模拟 | # 前缀 |
| 可读性 | 较混乱 | 清晰 |
示例代码:
// ES5 构造函数
function PersonES5(name, age) {
this.name = name;
this.age = age;
}
PersonES5.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
// ES6 Class
class PersonES6 {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hi, I'm ${this.name}`;
}
}
// 本质相同
console.log(typeof PersonES6); // 'function'
console.log(PersonES6 === PersonES6.prototype.constructor); // true
十二、对象扩展
1. Object.is
定义:
Object.is() 用于比较两个值是否严格相等,修正了 === 的一些行为。
console.log(Object.is(NaN, NaN)); // true(=== 为 false)
console.log(Object.is(+0, -0)); // false(=== 为 true)
console.log(Object.is(1, 1)); // true
console.log(Object.is('foo', 'foo')); // true
console.log(Object.is(null, null)); // true
console.log(Object.is({}, {})); // false
2. Object.assign
定义:
Object.assign() 用于对象的浅拷贝,将源对象的所有可枚举属性复制到目标对象。
// 基本用法
const target = { a: 1 };
const source = { b: 2, c: 3 };
Object.assign(target, source);
console.log(target); // { a: 1, b: 2, c: 3 }
// 合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // { a: 1, b: 3, c: 4 }
// 浅拷贝
const original = { a: 1, nested: { b: 2 } };
const copy = Object.assign({}, original);
copy.nested.b = 99;
console.log(original.nested.b); // 99(影响原对象)
// 默认值
function createUser(options) {
const defaults = { theme: 'light', fontSize: 14 };
return Object.assign({}, defaults, options);
}
console.log(createUser({ theme: 'dark' }));
// { theme: 'dark', fontSize: 14 }
3. Object.keys / Object.values / Object.entries / Object.fromEntries
const obj = { a: 1, b: 2, c: 3 };
// Object.keys - 获取键数组
console.log(Object.keys(obj)); // ['a', 'b', 'c']
// Object.values - 获取值数组
console.log(Object.values(obj)); // [1, 2, 3]
// Object.entries - 获取键值对数组
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
// 遍历
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`);
}
// Object.fromEntries - 键值对数组转对象
const entries = [['name', 'Alice'], ['age', 25]];
const fromEntries = Object.fromEntries(entries);
console.log(fromEntries); // { name: 'Alice', age: 25 }
// Map 转 Object
const map = new Map([['x', 1], ['y', 2]]);
const objFromMap = Object.fromEntries(map);
console.log(objFromMap); // { x: 1, y: 2 }
// 过滤对象属性
const filtered = Object.fromEntries(
Object.entries(obj).filter(([key, value]) => value > 1)
);
console.log(filtered); // { b: 2, c: 3 }
4. Object.getOwnPropertyDescriptors
定义: 获取对象所有自身属性的描述符(包括不可枚举属性)。
const obj = { name: 'Alice' };
Object.defineProperty(obj, 'age', {
value: 25,
writable: false,
enumerable: false,
configurable: true
});
console.log(Object.getOwnPropertyDescriptors(obj));
// {
// name: { value: 'Alice', writable: true, enumerable: true, configurable: true },
// age: { value: 25, writable: false, enumerable: false, configurable: true }
// }
// 用途:正确复制属性描述符
const source = Object.defineProperties({}, {
foo: { value: 1, enumerable: true },
bar: { value: 2, enumerable: false }
});
const target = Object.create(
Object.getPrototypeOf(source),
Object.getOwnPropertyDescriptors(source)
);
十三、数组扩展
1. Array.from
定义: 将类数组对象或可迭代对象转换为真正的数组。
// 类数组转数组
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b', 'c']
// NodeList 转数组
const divs = document.querySelectorAll('div');
const divArray = Array.from(divs);
// 字符串转数组
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']
// Set 转数组
console.log(Array.from(new Set([1, 2, 2, 3]))); // [1, 2, 3]
// 映射函数
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]
// 创建数组
console.log(Array.from({ length: 5 }, (_, i) => i)); // [0, 1, 2, 3, 4]
2. Array.of
定义:
将一组值转换为数组,弥补 Array() 构造函数的行为不一致问题。
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
console.log(Array.of(10)); // [10]
console.log(Array.of(undefined)); // [undefined]
// 对比 Array()
console.log(Array(3)); // [empty × 3]
console.log(Array(1, 2, 3)); // [1, 2, 3]
3. find 与 findIndex
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
];
// find - 找到第一个匹配的元素
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Bob', age: 30 }
// findIndex - 找到第一个匹配元素的索引
const index = users.findIndex(u => u.name === 'Charlie');
console.log(index); // 2
// 未找到返回 undefined / -1
console.log(users.find(u => u.id === 99)); // undefined
console.log(users.findIndex(u => u.id === 99)); // -1
4. fill
// 填充数组
const arr1 = new Array(5).fill(0);
console.log(arr1); // [0, 0, 0, 0, 0]
// 部分填充
const arr2 = [1, 2, 3, 4, 5];
arr2.fill('x', 1, 3);
console.log(arr2); // [1, 'x', 'x', 4, 5]
// 注意:引用类型共享同一引用
const arr3 = new Array(3).fill([]);
arr3[0].push(1);
console.log(arr3); // [[1], [1], [1]]
5. includes
const arr = [1, 2, NaN, 4, 5];
console.log(arr.includes(2)); // true
console.log(arr.includes(3)); // false
console.log(arr.includes(NaN)); // true(indexOf 无法检测 NaN)
console.log(arr.includes(2, 2)); // false(从索引 2 开始)
// 对比 indexOf
console.log(arr.indexOf(2)); // 1
console.log(arr.indexOf(NaN)); // -1
console.log(arr.indexOf(2) !== -1); // true
6. flat 与 flatMap
// flat - 扁平化嵌套数组
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat()); // [1, 2, [3, [4]]](默认深度 1)
console.log(nested.flat(2)); // [1, 2, 3, [4]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4]
// 去除空项
const withEmpty = [1, , 3, , 5];
console.log(withEmpty.flat()); // [1, 3, 5]
// flatMap - map + flat(深度 1)
const sentences = ['Hello World', 'JavaScript ES6'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ['Hello', 'World', 'JavaScript', 'ES6']
7. entries、keys、values
const arr = ['a', 'b', 'c'];
// keys - 索引
for (const index of arr.keys()) {
console.log(index); // 0, 1, 2
}
// values - 值
for (const value of arr.values()) {
console.log(value); // 'a', 'b', 'c'
}
// entries - 索引和值
for (const [index, value] of arr.entries()) {
console.log(index, value); // 0 'a', 1 'b', 2 'c'
}
十四、字符串扩展
1. includes、startsWith、endsWith
const str = 'Hello, World!';
// includes - 是否包含
console.log(str.includes('World')); // true
console.log(str.includes('world')); // false
console.log(str.includes('o', 5)); // true(从索引 5 开始)
// startsWith - 是否以...开头
console.log(str.startsWith('Hello')); // true
console.log(str.startsWith('World', 7)); // true
// endsWith - 是否以...结尾
console.log(str.endsWith('!')); // true
console.log(str.endsWith('Hello', 5)); // true(前 5 个字符)
2. repeat
console.log('x'.repeat(3)); // 'xxx'
console.log('hello'.repeat(2)); // 'hellohello'
console.log('na'.repeat(0)); // ''
console.log('na'.repeat(2.9)); // 'nana'(向下取整)
// console.log('na'.repeat(-1)); // RangeError
// console.log('na'.repeat(Infinity)); // RangeError
3. padStart、padEnd
// padStart - 头部补全
console.log('5'.padStart(3, '0')); // '005'
console.log('12345'.padStart(3, '0')); // '12345'
// padEnd - 尾部补全
console.log('5'.padEnd(3, '0')); // '500'
// 实际应用
// 格式化日期
const month = '3';
console.log(month.padStart(2, '0')); // '03'
// 提示长度
console.log('***'.padStart(10, '*')); // '**********'
4. trimStart、trimEnd
const str = ' hello ';
console.log(str.trim()); // 'hello'
console.log(str.trimStart()); // 'hello '
console.log(str.trimEnd()); // ' hello'
5. at
const arr = [1, 2, 3, 4, 5];
const str = 'hello';
// 正向索引
console.log(arr.at(0)); // 1
console.log(str.at(0)); // 'h'
// 负向索引(从末尾开始)
console.log(arr.at(-1)); // 5
console.log(str.at(-1)); // 'o'
console.log(arr.at(-2)); // 4
十五、正则扩展
1. u 修饰符
定义:
u 修饰符表示 Unicode 模式,正确处理大于 \uFFFF 的 Unicode 字符。
// 正确处理四字节的 Unicode 字符
const str = '\uD83D\uDC2A'; // 🐪
console.log(/^.$/.test(str)); // false(误认为两个字符)
console.log(/^.$/u.test(str)); // true
// 正确匹配码点
console.log(/\u{61}/.test('a')); // false
console.log(/\u{61}/u.test('a')); // true
2. y 修饰符(sticky)
定义:
y 修饰符表示粘连模式,每次匹配都必须从上一次匹配的结束位置开始。
const str = 'aaa_aa_a';
// g 修饰符
const gRegex = /a+/g;
console.log(gRegex.exec(str)); // ['aaa']
console.log(gRegex.exec(str)); // ['aa']
// y 修饰符
const yRegex = /a+/y;
console.log(yRegex.exec(str)); // ['aaa']
console.log(yRegex.exec(str)); // null(下一个字符是 _)
// flags 属性
const regex = /abc/giy;
console.log(regex.flags); // 'giy'
十六、数值扩展
1. 二进制与八进制表示法
// 二进制
console.log(0b1010); // 10
console.log(0B1010); // 10
// 八进制
console.log(0o755); // 493
console.log(0O755); // 493
// 十六进制
console.log(0xFF); // 255
2. Number 新增方法
// Number.isFinite - 是否有限
console.log(Number.isFinite(10)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite('10')); // false(不转换类型)
// Number.isNaN - 是否 NaN
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('NaN')); // false
// Number.isInteger - 是否整数
console.log(Number.isInteger(10)); // true
console.log(Number.isInteger(10.0)); // true
console.log(Number.isInteger(10.1)); // false
// Number.parseInt / Number.parseFloat
console.log(Number.parseInt('12.34')); // 12
console.log(Number.parseFloat('12.34')); // 12.34
// Number.EPSILON - 最小精度差
console.log(Number.EPSILON); // 2.220446049250313e-16
// 用于浮点数比较
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true
3. Math 新增方法
// Math.trunc - 去除小数部分
console.log(Math.trunc(4.9)); // 4
console.log(Math.trunc(-4.9)); // -4
// Math.sign - 判断正负
console.log(Math.sign(5)); // 1
console.log(Math.sign(-5)); // -1
console.log(Math.sign(0)); // 0
// Math.cbrt - 立方根
console.log(Math.cbrt(27)); // 3
// Math.hypot - 平方和的平方根
console.log(Math.hypot(3, 4)); // 5
// 指数运算符
console.log(2 ** 3); // 8
console.log(2 ** 10); // 1024
十七、函数扩展
1. 严格模式
// 全局严格模式
'use strict';
// 函数内严格模式
function strictFn() {
'use strict';
// x = 1; // ReferenceError
}
// 严格模式的变化
// 1. 变量必须先声明
// 2. 禁止 this 指向全局对象
// 3. 禁止使用 with
// 4. 函数参数不能重名
// 5. 禁止八进制字面量
2. name 属性
function myFunction() {}
console.log(myFunction.name); // 'myFunction'
const anonymous = function() {};
console.log(anonymous.name); // 'anonymous'
const named = function myName() {};
console.log(named.name); // 'myName'
const arrow = () => {};
console.log(arrow.name); // 'arrow'
const bound = myFunction.bind({});
console.log(bound.name); // 'bound myFunction'
3. 尾调用优化
定义: 尾调用是指函数的最后一步是调用另一个函数。尾调用优化是指在尾调用时不增加调用栈,节省内存。
// 尾调用
function f(x) {
return g(x); // 尾调用
}
// 非尾调用
function f(x) {
return g(x) + 1; // 不是尾调用
}
// 尾递归
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // 尾递归
}
console.log(factorial(5)); // 120
十八、Proxy 与 Reflect
1. Proxy 的用法
定义: Proxy 用于创建一个对象的代理,可以拦截并自定义对象的基本操作(属性读取、赋值、删除等)。
示例代码:
// 基本用法
const target = { name: 'Alice', age: 25 };
const proxy = new Proxy(target, {
get(obj, prop) {
console.log(`get: ${prop}`);
return prop in obj ? obj[prop] : 'default';
},
set(obj, prop, value) {
console.log(`set: ${prop} = ${value}`);
obj[prop] = value;
return true;
},
has(obj, prop) {
console.log(`has: ${prop}`);
return prop in obj;
},
deleteProperty(obj, prop) {
console.log(`delete: ${prop}`);
delete obj[prop];
return true;
}
});
console.log(proxy.name); // 'get: name' -> 'Alice'
console.log(proxy.nonexistent); // 'get: nonexistent' -> 'default'
proxy.age = 30; // 'set: age = 30'
console.log('age' in proxy); // 'has: age' -> true
2. Proxy 的拦截操作
const target = { a: 1 };
const handler = {
get(target, prop, receiver) {
console.log(`读取 ${prop}`);
return target[prop];
},
set(target, prop, value, receiver) {
console.log(`设置 ${prop} = ${value}`);
target[prop] = value;
return true;
},
has(target, prop) {
return prop in target;
},
deleteProperty(target, prop) {
delete target[prop];
return true;
},
apply(target, thisArg, args) {
console.log('函数调用');
return target.apply(thisArg, args);
},
construct(target, args) {
console.log('构造函数调用');
return new target(...args);
}
};
// 可撤销的 Proxy
const { proxy: revProxy, revoke } = Proxy.revocable(target, handler);
console.log(revProxy.a); // 1
revoke();
// console.log(revProxy.a); // TypeError
3. Proxy 的应用场景
// 1. 数据验证
function createValidator(target, validator) {
return new Proxy(target, {
set(obj, prop, value) {
if (validator[prop]) {
if (!validator[prop](value)) {
throw new Error(`Invalid value for ${prop}`);
}
}
obj[prop] = value;
return true;
}
});
}
const user = createValidator(
{ name: '', age: 0 },
{
name: v => typeof v === 'string' && v.length > 0,
age: v => Number.isInteger(v) && v > 0
}
);
user.name = 'Alice';
// user.age = -1; // Error
// 2. 负数索引数组
function createArray(...elements) {
const handler = {
get(target, prop) {
let index = Number(prop);
if (index < 0) {
index = target.length + index;
}
return target[index];
}
};
const proxy = new Proxy(elements, handler);
return proxy;
}
const arr = createArray('a', 'b', 'c', 'd');
console.log(arr[-1]); // 'd'
console.log(arr[-2]); // 'c'
// 3. 隐藏私有属性
function hidePrivate(obj) {
return new Proxy(obj, {
get(target, prop) {
if (typeof prop === 'string' && prop.startsWith('_')) {
return undefined;
}
return target[prop];
},
has(target, prop) {
if (typeof prop === 'string' && prop.startsWith('_')) {
return false;
}
return prop in target;
}
});
}
const obj = { name: 'Alice', _password: 'secret' };
const safe = hidePrivate(obj);
console.log(safe.name); // 'Alice'
console.log(safe._password); // undefined
console.log('_password' in safe); // false
4. Reflect
定义: Reflect 是 ES6 引入的新对象,提供了一组与对象操作相关的静态方法,与 Proxy 的拦截方法一一对应。
设计目的:
- 将 Object 的一些内部方法移植到 Reflect 上
- 修改某些 Object 方法的返回结果,使其更合理
- 让对象操作变成函数行为
- 与 Proxy 方法对应,确保 Proxy 可以调用默认行为
// Reflect 方法示例
const obj = { a: 1, b: 2 };
// Reflect.get
console.log(Reflect.get(obj, 'a')); // 1
// Reflect.set
Reflect.set(obj, 'c', 3);
console.log(obj); // { a: 1, b: 2, c: 3 }
// Reflect.has
console.log(Reflect.has(obj, 'b')); // true
// Reflect.deleteProperty
Reflect.deleteProperty(obj, 'b');
console.log(obj); // { a: 1, c: 3 }
// Reflect.apply
function sum(a, b) { return a + b; }
console.log(Reflect.apply(sum, null, [1, 2])); // 3
// Reflect.construct
function Person(name) { this.name = name; }
const p = Reflect.construct(Person, ['Alice']);
console.log(p.name); // 'Alice'
// Reflect.ownKeys
const obj2 = { a: 1 };
Object.defineProperty(obj2, 'b', { value: 2, enumerable: false });
console.log(Reflect.ownKeys(obj2)); // ['a', 'b']
5. Reflect 与 Proxy 配合
const target = { name: 'Alice' };
const proxy = new Proxy(target, {
get(target, prop, receiver) {
console.log(`get: ${prop}`);
// 使用 Reflect 调用默认行为
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`set: ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});
console.log(proxy.name); // 'get: name' -> 'Alice'
proxy.name = 'Bob'; // 'set: name = Bob'
十九、综合问题
1. ES6 引入了哪些新特性?
核心特性汇总:
| 类别 | 特性 |
|---|---|
| 变量声明 | let、const |
| 解构 | 数组解构、对象解构 |
| 函数 | 箭头函数、默认参数、剩余参数 |
| 字符串 | 模板字符串、标签模板 |
| 对象 | 属性简写、Object.assign、Object.keys/values/entries |
| 数组 | Array.from、Array.of、扩展方法 |
| 类 | class、extends、super |
| 模块 | import、export |
| 异步 | Promise |
| 数据结构 | Set、Map、WeakSet、WeakMap |
| 迭代 | Iterator、for...of |
| 生成器 | Generator 函数 |
| 代理 | Proxy、Reflect |
| 符号 | Symbol |
| 数值 | 二进制/八进制、Number 扩展方法 |
| 正则 | u/y 修饰符 |
| 其他 | 尾调用优化、指数运算符 |
2. setTimeout、Promise、async/await 的区别
对比表格:
| 特性 | setTimeout | Promise | async/await |
|---|---|---|---|
| 类型 | 宏任务(MacroTask) | 微任务(MicroTask) | 语法糖(基于 Promise) |
| 执行时机 | 事件循环的宏任务队列 | 微任务队列,优先执行 | 同 Promise |
| 语法 | 回调函数 | .then() 链式 | await 同步写法 |
| 错误处理 | try-catch 无法捕获 | .catch() | try-catch |
| 可读性 | 回调地狱 | 链式较清晰 | 同步风格最清晰 |
| 返回值 | 定时器 ID | Promise 对象 | Promise 对象 |
执行顺序示例:
console.log('1. 同步代码');
setTimeout(() => {
console.log('2. setTimeout(宏任务)');
}, 0);
Promise.resolve()
.then(() => {
console.log('3. Promise.then(微任务)');
});
async function asyncFn() {
console.log('4. async 函数同步部分');
await Promise.resolve();
console.log('5. await 后的代码(微任务)');
}
asyncFn();
console.log('6. 同步代码结束');
// 执行顺序:
// 1. 同步代码
// 4. async 函数同步部分
// 6. 同步代码结束
// 3. Promise.then(微任务)
// 5. await 后的代码(微任务)
// 2. setTimeout(宏任务)
3. ES5 与 ES6 继承的区别
对比表格:
| 特性 | ES5 继承 | ES6 继承 |
|---|---|---|
| 实现方式 | 原型链 + 构造函数借用 | extends + super |
| this 创建时机 | 先创建子类 this,再添加父类方法 | 先创建父类 this,再加工 |
| 语法 | 复杂、不规范 | 简洁、语义清晰 |
| 静态方法继承 | 需手动实现 | 自动继承 |
| 可读性 | 较差 | 清晰 |
示例代码:
// ES5 继承(寄生组合式)
function ParentES5(name) {
this.name = name;
}
ParentES5.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
function ChildES5(name, age) {
ParentES5.call(this, name); // 借用父类构造函数
this.age = age;
}
ChildES5.prototype = Object.create(ParentES5.prototype);
ChildES5.prototype.constructor = ChildES5;
ChildES5.prototype.study = function() {
return `${this.name} is studying`;
};
// ES6 继承
class ParentES6 {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, I'm ${this.name}`;
}
}
class ChildES6 extends ParentES6 {
constructor(name, age) {
super(name); // 必须先调用 super
this.age = age;
}
study() {
return `${this.name} is studying`;
}
}
4. Array.forEach() 与 Array.map() 的区别
对比表格:
| 特性 | forEach | map |
|---|---|---|
| 返回值 | undefined | 新数组 |
| 用途 | 遍历执行副作用 | 遍历并转换数据 |
| 链式调用 | 不支持 | 支持 |
| 原数组 | 不修改(除非手动修改) | 不修改 |
| 性能 | 稍快 | 稍慢(创建新数组) |
示例代码:
const numbers = [1, 2, 3, 4, 5];
// forEach - 执行操作,无返回值
numbers.forEach((num, index) => {
console.log(`Index ${index}: ${num * 2}`);
});
// map - 转换数据,返回新数组
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 链式调用
const result = numbers
.filter(n => n > 2)
.map(n => n * 10)
.reduce((sum, n) => sum + n, 0);
console.log(result); // 100
// 选择建议
// 需要返回值 -> 使用 map
// 仅执行操作(如打印、修改外部变量)-> 使用 forEach
5. 模块化的发展历程
发展历程:
| 阶段 | 方案 | 特点 |
|---|---|---|
| 1 | IIFE(立即执行函数) | 闭包隔离作用域,全局变量挂载 |
| 2 | AMD(RequireJS) | 异步加载,浏览器端 |
| 3 | CMD(SeaJS) | 延迟执行,浏览器端 |
| 4 | CommonJS(Node.js) | 同步加载,服务端 |
| 5 | UMD | 兼容 AMD + CommonJS |
| 6 | ES Module | 语言标准,编译时加载 |
示例代码:
// IIFE
var myModule = (function() {
var privateVar = 'secret';
function privateFn() { return privateVar; }
return {
publicFn: function() { return privateFn(); }
};
})();
// AMD
define(['dep1', 'dep2'], function(dep1, dep2) {
return { fn: function() {} };
});
// CommonJS
const fs = require('fs');
module.exports = { fn: function() {} };
// ES Module
import fs from 'fs';
export function fn() {}
6. 装饰器模式
定义: 装饰器是一种设计模式,用于在不修改原对象的情况下增强其功能。ES6 中可以使用 Proxy 或高阶函数实现。
高阶函数实现:
// 日志装饰器
function withLogging(fn) {
return function(...args) {
console.log(`调用 ${fn.name},参数:`, args);
const result = fn.apply(this, args);
console.log(`返回:`, result);
return result;
};
}
function add(a, b) { return a + b; }
const loggedAdd = withLogging(add);
console.log(loggedAdd(1, 2));
// 调用 add,参数:[1, 2]
// 返回:3
// 3
// 缓存装饰器
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoFib = memoize(fibonacci);
console.log(memoFib(10)); // 55(快速)
7. 新旧版本支持:ES6+ 特性在 IE11 中的兼容性
解决方案:
- Babel 转译:将 ES6+ 代码转换为 ES5
- Polyfill:为不支持的 API 提供垫片(如 Promise、Set、Map)
- core-js:现代化的 polyfill 库
- 浏览器升级提示
// .browserslistrc
> 1%
last 2 versions
not ie <= 11
// webpack + babel 配置
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3,
targets: {
ie: '11'
}
}]
]
};
8. Vue 3 响应式原理基于 Proxy
// 简化版 Vue 3 响应式系统
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, key); // 收集依赖
return typeof result === 'object' ? reactive(result) : result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key); // 触发更新
}
return result;
}
});
}
// 依赖收集
const targetMap = new WeakMap();
function track(target, key) {
// 收集当前活跃的 effect
}
function trigger(target, key) {
// 执行收集的 effects
}
// 使用
const state = reactive({ count: 0 });
// effect(() => { console.log(state.count); });
state.count++; // 触发更新
9. 微前端架构:Module Federation
定义: Webpack 5 的 Module Federation 允许在运行时动态加载其他应用的模块,实现微前端架构。
// host 应用配置
// webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js'
}
})
]
};
// 动态加载
import('app1/Button').then(Button => {
// 使用远程组件
});
// async/await
const { default: Button } = await import('app1/Button');