四、数组与集合
4.1 数组方法大全
创建数组:
// 字面量
const arr1 = [1, 2, 3];
// 构造函数
const arr2 = new Array(5); // 创建长度为5的空数组
const arr3 = new Array(1, 2, 3);
// 静态方法
const arr4 = Array.from("hello"); // ["h","e","l","l","o"]
const arr5 = Array.of(1, 2, 3); // [1,2,3]
遍历方法:
const arr = [1, 2, 3, 4, 5];
// forEach —— 遍历,不返回新数组
arr.forEach((item, index, array) => {
console.log(item);
});
// map —— 映射,返回新数组
const doubled = arr.map(x => x * 2); // [2,4,6,8,10]
// filter —— 过滤,返回新数组
const evens = arr.filter(x => x % 2 === 0); // [2,4]
// reduce —— 归并
const sum = arr.reduce((acc, cur) => acc + cur, 0); // 15
const max = arr.reduce((max, cur) => Math.max(max, cur), -Infinity);
// reduceRight —— 从右向左归并
const reversed = arr.reduceRight((acc, cur) => [...acc, cur], []);
// some —— 只要一个满足条件
const hasEven = arr.some(x => x % 2 === 0); // true
// every —— 全部满足条件
const allPositive = arr.every(x => x > 0); // true
// find —— 找到第一个满足条件的元素
const firstEven = arr.find(x => x % 2 === 0); // 2
// findIndex —— 找到第一个满足条件的索引
const indexEven = arr.findIndex(x => x % 2 === 0); // 1
增删改查方法:
let arr = [1, 2, 3];
// 尾部操作
arr.push(4); // [1,2,3,4],返回新长度
arr.pop(); // [1,2,3],返回删除的元素
// 头部操作
arr.unshift(0); // [0,1,2,3],返回新长度
arr.shift(); // [1,2,3],返回删除的元素
// 任意位置
arr.splice(1, 1); // 删除索引1的元素,返回 [2]
arr.splice(1, 0, 5, 6); // 在索引1插入5和6
arr.splice(1, 2, 10); // 替换索引1开始的2个元素为10
// 切片(不改变原数组)
const slice = arr.slice(1, 3); // 提取索引1到2
// 连接
const mergedArr = arr.concat([4, 5]); // 不改变原数组
// 填充(ES6)
const filled = new Array(3).fill(0); // [0,0,0]
arr.fill(9, 1, 3); // 将索引1-2填充为9
其他实用方法:
// 排序
const unsorted = [3, 1, 4, 1, 5];
unsorted.sort(); // [1,1,3,4,5](字符串排序)
unsorted.sort((a, b) => a - b); // 升序
unsorted.sort((a, b) => b - a); // 降序
// 反转
unsorted.reverse();
// 查找索引
[1, 2, 3, 2].indexOf(2); // 1
[1, 2, 3, 2].lastIndexOf(2); // 3
[1, 2, 3].includes(2); // true
// 扁平化(ES2019)
const nested = [1, [2, [3, [4]]]];
nested.flat(); // [1,2,[3,[4]]] 默认深度1
nested.flat(2); // [1,2,3,[4]]
nested.flat(Infinity); // [1,2,3,4]
// 扁平化映射
const flatMapped = [1, 2, 3].flatMap(x => [x, x * 2]); // [1,2,2,4,3,6]
// 连接为字符串
[1, 2, 3].join("-"); // "1-2-3"
4.2 Set(集合)
// 创建 Set
const set = new Set([1, 2, 3, 3, 4]); // {1,2,3,4} 自动去重
// 常用方法
set.add(5); // 添加元素
set.delete(2); // 删除元素
set.has(3); // 判断是否存在,返回 true
set.size; // 大小
set.clear(); // 清空
// 遍历
set.forEach(value => console.log(value));
for (let value of set) { console.log(value); }
// 应用:数组去重
const unique = [...new Set([1, 2, 2, 3, 3, 4])]; // [1,2,3,4]
// 集合运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
// 并集
const union = new Set([...setA, ...setB]); // {1,2,3,4}
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x))); // {2,3}
// 差集
const difference = new Set([...setA].filter(x => !setB.has(x))); // {1}
4.3 Map(字典)
// 创建 Map
const map = new Map();
// 常用方法
map.set("name", "张三");
map.set("age", 25);
map.set(1, "数字键");
map.get("name"); // "张三"
map.has("age"); // true
map.delete("age"); // 删除
map.size; // 2
map.clear(); // 清空
// 遍历
map.forEach((value, key) => console.log(key, value));
for (let [key, value] of map) { console.log(key, value); }
for (let key of map.keys()) { console.log(key); }
for (let value of map.values()) { console.log(value); }
// 与对象的区别
// - 键可以是任意类型(对象、函数等)
// - 保持插入顺序
// - 可以直接获取大小
// 对象转 Map
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
// Map 转对象
const objFromMap = Object.fromEntries(mapFromObj);
4.4 WeakSet 与 WeakMap
// WeakSet:只能存储对象,弱引用,不可遍历
const weakSet = new WeakSet();
let obj = { name: "临时对象" };
weakSet.add(obj);
obj = null; // 对象会被垃圾回收
// WeakMap:键必须是对象,弱引用
const weakMap = new WeakMap();
let key = { id: 1 };
weakMap.set(key, "关联数据");
key = null; // 数据会被垃圾回收
// 应用场景:缓存、DOM 节点关联数据
const domCache = new WeakMap();
const element = document.getElementById("app");
domCache.set(element, { clicks: 0 });
五、字符串与正则
5.1 字符串方法
// 访问字符
const str = "Hello, World!";
str[0]; // "H"
str.charAt(1); // "e"
str.charCodeAt(0); // 72,Unicode 编码
// 查找
str.indexOf("o"); // 4
str.lastIndexOf("o"); // 8
str.includes("World"); // true
str.startsWith("Hell"); // true
str.endsWith("!"); // true
str.search(/World/); // 7,返回索引
// 提取
str.slice(0, 5); // "Hello"
str.substring(7, 12); // "World"
str.substr(7, 5); // "World"(已废弃)
str.split(", "); // ["Hello", "World!"]
// 大小写转换
str.toUpperCase(); // "HELLO, WORLD!"
str.toLowerCase(); // "hello, world!"
// 去除空白
" hello ".trim(); // "hello"
" hello".trimStart(); // "hello"
"hello ".trimEnd(); // "hello"
// 填充(ES2017)
"5".padStart(3, "0"); // "005"
"5".padEnd(3, "0"); // "500"
// 替换
str.replace("World", "JavaScript"); // "Hello, JavaScript!"
str.replaceAll("l", "L"); // "HeLLo, WorLd!"
// 重复
"*".repeat(5); // "*****"
// 模板字符串特性
const name = "张三";
const age = 25;
const message = `我叫${name},今年${age}岁`;
const multiLine = `
第一行
第二行
`;
5.2 正则表达式
创建正则:
// 字面量
const reg1 = /abc/gim;
// 构造函数
const reg2 = new RegExp("abc", "gi");
常用标志:
g:全局匹配
i:忽略大小写
m:多行匹配
u:Unicode 模式(ES6)
y:粘性匹配(ES6)
正则方法:
const str = "Hello 123 World 456";
const reg = /\d+/g;
// test —— 测试是否匹配
reg.test(str); // true
// exec —— 执行匹配,返回详细信息
let match;
while ((match = reg.exec(str)) !== null) {
console.log(match[0]); // 123, 456
}
// 字符串方法中使用正则
str.match(/\d+/g); // ["123", "456"]
str.search(/\d+/); // 6,返回索引
str.replace(/\d+/g, "X"); // "Hello X World X"
str.split(/\s+/); // ["Hello", "123", "World", "456"]
元字符与语法:
// 字符类
/\d/ // 数字
/\D/ // 非数字
/\w/ // 字母、数字、下划线
/\W/ // 非单词字符
/\s/ // 空白字符
/\S/ // 非空白字符
/./ // 任意字符(除换行)
// 量词
/a*/ // 0次或多次
/a+/ // 1次或多次
/a?/ // 0次或1次
/a{3}/ // 恰好3次
/a{2,5}/ // 2到5次
// 位置锚点
/^abc/ // 以abc开头
/abc$/ // 以abc结尾
/\bword\b/ // 单词边界
// 分组与捕获
/(abc)/ // 捕获组
/(?:abc)/ // 非捕获组
/(?<name>abc)/ // 命名捕获组(ES2018)
// 前瞻后顾
/x(?=y)/ // 正向肯定前瞻,x后面紧跟y
/x(?!y)/ // 正向否定前瞻,x后面不跟y
/(?<=y)x/ // 反向肯定后顾(ES2018),x前面是y
/(?<!y)x/ // 反向否定后顾(ES2018),x前面不是y
// 示例:验证邮箱
const emailRegex = /^[^\s@]+@([^\s@]+\.)+[^\s@]+$/;
// 示例:提取 URL 参数
const url = "https://example.com?id=123&name=张三";
const params = {};
url.replace(/([?&])([^=]+)=([^&]+)/g, (_, p, key, value) => {
params[decodeURIComponent(key)] = decodeURIComponent(value);
});
六、异步编程
6.1 回调函数
// 传统回调
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: "数据" };
callback(null, data);
}, 1000);
}
fetchData((err, data) => {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
// 回调地狱(Callback Hell)
getUser(1, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
console.log(details);
// 嵌套过深,难以维护
});
});
});
6.2 Promise
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve({ data: "成功的数据" });
} else {
reject(new Error("操作失败"));
}
}, 1000);
});
// 使用 Promise
promise
.then(result => {
console.log(result);
return result.data;
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log("无论成功失败都会执行");
});
// Promise 静态方法
Promise.resolve("立即成功").then(console.log);
Promise.reject("立即失败").catch(console.log);
// 并行执行
const p1 = fetch("/api/user");
const p2 = fetch("/api/orders");
Promise.all([p1, p2])
.then(([userRes, ordersRes]) => {
console.log("所有请求完成");
})
.catch(err => {
console.log("任意一个失败就会触发");
});
Promise.allSettled([p1, p2]) // ES2020
.then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("成功:", result.value);
} else {
console.log("失败:", result.reason);
}
});
});
Promise.race([p1, p2])
.then(result => {
console.log("最快的请求完成", result);
});
Promise.any([p1, p2]) // ES2021
.then(result => {
console.log("第一个成功的请求", result);
});
6.3 Async/Await(ES2017)
// async 函数
async function fetchUserData(userId) {
try {
const user = await fetch(`/api/user/${userId}`);
const userData = await user.json();
const orders = await fetch(`/api/orders/${userData.id}`);
const ordersData = await orders.json();
return { user: userData, orders: ordersData };
} catch (error) {
console.error("请求失败:", error);
throw error; // 可继续向上抛出
}
}
// 使用
fetchUserData(123)
.then(data => console.log(data))
.catch(err => console.error(err));
// 并行等待
async function parallelRequests() {
const [userRes, ordersRes] = await Promise.all([
fetch("/api/user"),
fetch("/api/orders")
]);
const user = await userRes.json();
const orders = await ordersRes.json();
return { user, orders };
}
// 循环中的异步
async function processItems(items) {
// 顺序执行
for (const item of items) {
await processItem(item);
}
// 并行执行
await Promise.all(items.map(item => processItem(item)));
}
6.4 事件循环与微任务
console.log("1: 同步代码");
setTimeout(() => {
console.log("2: 宏任务 - setTimeout");
}, 0);
Promise.resolve()
.then(() => {
console.log("3: 微任务 - Promise.then");
})
.then(() => {
console.log("4: 微任务 - 第二个 then");
});
console.log("5: 同步代码结束");
// 输出顺序:
// 1: 同步代码
// 5: 同步代码结束
// 3: 微任务 - Promise.then
// 4: 微任务 - 第二个 then
// 2: 宏任务 - setTimeout
// 事件循环优先级:
// 同步代码 > 微任务(Promise, MutationObserver, queueMicrotask)> 宏任务(setTimeout, setInterval, I/O)