Learning Record

2026年2月10日学习记录

记录于:2026-02-10

今日踩坑记录

项目中有个需求:需要动态替换某个域名到另一个域名,因此使用正则去做匹配。域名列表是一个字符串数组。


问题代码

const domains = ["domain1.oldsite.com", "domain2.oldsite.com", "domain3.example.com"];

"https://domain1.oldsite.com/api/list".indexOf("https:") > -1 && domains.forEach((i) => (i = i.replace("oldsite", "newsite")));

console.log(domains, "domains");
// 输出:['domain1.oldsite.com', 'domain2.oldsite.com', 'domain3.example.com']
// 期望:['domain1.newsite.com', 'domain2.newsite.com', 'domain3.example.com']

问题:我期望 forEach 方法能够修改原数组,但实际上原数组 domains 并没有发生任何变化


原因分析

这里涉及到两个核心概念:

1. forEach 回调中对参数重新赋值无法修改原数组

Array.prototype.forEach() 虽然会遍历数组,但回调函数的参数 i 只是数组元素的一个局部副本。对 i 重新赋值,只是修改了这个局部变量,原数组中的元素不会受到影响。

const arr = [1, 2, 3];
arr.forEach((i) => (i = i * 2)); // 只是修改了局部变量 i
console.log(arr); // [1, 2, 3] - 原数组未变

2. forEach 和 map 的区别

  • forEach:遍历数组,没有返回值(返回 undefined),通常用于执行副作用操作
  • map:遍历数组,返回一个新数组,不会修改原数组

但无论使用哪个方法,在回调中通过 i = xxx 重新赋值参数,都无法修改原数组元素,因为参数只是元素的副本。

const arr = [1, 2, 3];

// forEach:对参数赋值无效
arr.forEach((i) => (i = i + 1));
console.log(arr); // [1, 2, 3] - 原数组未变

// map:同样无法修改原数组,但至少能拿到返回值
const newArr = arr.map((i) => i + 1);
console.log(arr); // [1, 2, 3] - 原数组未变
console.log(newArr); // [2, 3, 4] - 新数组

3. 基本类型 vs 引用类型

即使我们能修改 i,由于字符串和数字都是基本类型(primitive type),它们是按值传递的,修改局部变量不会影响原数组。

let num = 10;
function modify(n) {
  n = 20; // 只修改了局部副本
  return n;
}
modify(num);
console.log(num); // 10 - 原变量未变

正确的解决方案

方案 1:使用 map 的返回值(推荐)

const domains = ["domain1.oldsite.com", "domain2.oldsite.com", "domain3.example.com"];

if ("https://domain1.oldsite.com/api/list".indexOf("https:") > -1) {
  const newDomains = domains.map((domain) => domain.replace("oldsite", "newsite"));
  console.log(newDomains); // ['domain1.newsite.com', 'domain2.newsite.com', 'domain3.example.com']
}

// 或者直接重新赋值
let domains = ["domain1.oldsite.com", "domain2.oldsite.com", "domain3.example.com"];
domains = domains.map((domain) => domain.replace("oldsite", "newsite"));
console.log(domains); // ['domain1.newsite.com', 'domain2.newsite.com', 'domain3.example.com']

方案 2:使用 forEach 修改数组元素(需要下标)

如果确实需要修改原数组(不推荐),可以使用 forEach 配合索引:

const domains = ["domain1.oldsite.com", "domain2.oldsite.com", "domain3.example.com"];

domains.forEach((domain, index) => {
  domains[index] = domain.replace("oldsite", "newsite");
});

console.log(domains); // ['domain1.newsite.com', 'domain2.newsite.com', 'domain3.example.com']

方案 3:使用传统 for 循环

const domains = ["domain1.oldsite.com", "domain2.oldsite.com", "domain3.example.com"];

for (let i = 0; i < domains.length; i++) {
  domains[i] = domains[i].replace("oldsite", "newsite");
}

console.log(domains); // ['domain1.newsite.com', 'domain2.newsite.com', 'domain3.example.com']

相似的易错点汇总

1. filter 和 map 不会修改原数组

const arr = [1, 2, 3, 4, 5];

// ❌ 错误:期望修改原数组
arr.filter((i) => i > 2);
console.log(arr); // [1, 2, 3, 4, 5] - 原数组未变

// ✅ 正确:接收返回值
const filtered = arr.filter((i) => i > 2);
console.log(filtered); // [3, 4, 5]

2. slice 返回新数组,splice 修改原数组

const arr = [1, 2, 3, 4, 5];

// slice 不修改原数组
const sliced = arr.slice(1, 3);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(sliced); // [2, 3]

// splice 修改原数组
const spliced = arr.splice(1, 2);
console.log(arr); // [1, 4, 5] - 原数组被修改
console.log(spliced); // [2, 3]

3. concat 不修改原数组

const arr1 = [1, 2];
const arr2 = [3, 4];

// ❌ 错误
arr1.concat(arr2);
console.log(arr1); // [1, 2] - 原数组未变

// ✅ 正确
const combined = arr1.concat(arr2);
console.log(combined); // [1, 2, 3, 4]

4. sort 和 reverse 会修改原数组

const arr = [3, 1, 2];

// ⚠️ 注意:sort 会修改原数组
arr.sort();
console.log(arr); // [1, 2, 3] - 原数组被修改

// 如果不想修改原数组,先复制
const original = [3, 1, 2];
const sorted = [...original].sort();
console.log(original); // [3, 1, 2]
console.log(sorted); // [1, 2, 3]

5. 对象数组中修改对象属性会影响原数组

const users = [
  { name: "Alice", age: 20 },
  { name: "Bob", age: 25 },
];

// ⚠️ 注意:这会修改原数组中的对象
users.map((user) => (user.age = user.age + 1));
console.log(users); // [{ name: 'Alice', age: 21 }, { name: 'Bob', age: 26 }]

// 如果不想修改原数组,需要创建新对象
const updated = users.map((user) => ({ ...user, age: user.age + 1 }));
console.log(users); // [{ name: 'Alice', age: 20 }, { name: 'Bob', age: 25 }]
console.log(updated); // [{ name: 'Alice', age: 21 }, { name: 'Bob', age: 26 }]

6. 字符串方法不会修改原字符串

const str = "hello";

// ❌ 错误:字符串是不可变的
str.toUpperCase();
console.log(str); // 'hello' - 原字符串未变

// ✅ 正确
const upper = str.toUpperCase();
console.log(upper); // 'HELLO'

总结

核心要点

  1. map/filter/concat/slice 等方法都返回新数组,不会修改原数组
  2. sort/reverse/splice/push/pop/shift/unshift 等方法会修改原数组
  3. 基本类型(string、number、boolean)是按值传递,赋值不影响原变量
  4. 引用类型(object、array)是按引用传递,修改属性会影响原对象
  5. 养成习惯:使用数组方法时,明确该方法是否修改原数组

最佳实践

  • 优先使用纯函数:接收返回值,不修改原数组,代码更可预测
  • 需要修改原数组时:使用明确会修改的方法(splice、sort 等)或通过索引直接赋值
  • 处理引用类型时:注意深拷贝 vs 浅拷贝的区别

参考资源