JavaScript 深拷贝与浅拷贝:代码示例

1. 基本类型与引用类型的本质区别

代码示例

javascript 复制代码
// 基本类型(按值传递)
let a = 1;
let b = a;  // b = 1(复制值)
a = 2;      // 修改a的值
console.log(b); // 输出1(b不受影响)

// 引用类型(按引用传递)
let obj = { a: 1 };
let obj2 = obj;  // obj2 指向同一内存地址
obj.a = 2;       // 修改原对象
console.log(obj2.a); // 输出2(共享内存)

原理

  • 基本类型(数字、字符串等)存储在栈内存中,赋值时直接复制值。
  • 引用类型 (对象、数组等)的变量存储的是内存地址,赋值时复制地址而非实际数据。
    总结
    修改基本类型变量不影响其他变量,而修改引用类型对象会影响所有指向该地址的变量。

2. 浅拷贝的陷阱:对象嵌套问题

代码示例

javascript 复制代码
const obj = { 
  a: 1, 
  b: { n: 2 } // 嵌套对象
};
const copy = Object.assign({}, obj); // 浅拷贝

obj.a = 10;    // 修改原对象的第一层属性
obj.b.n = 20;  // 修改嵌套对象属性

console.log(copy); 
// 输出 { a: 1, b: { n: 20 } }

关键点

  • Object.assign 只会复制对象的第一层属性。
  • 嵌套对象(如 b)的地址被复制,新旧对象共享同一子对象。
    结论
    浅拷贝不适用于嵌套结构的对象,子对象修改会相互影响。

3. 深拷贝的常见方法对比

代码示例 1:JSON 序列化法

javascript 复制代码
const obj = {
  name: "John",
  date: new Date(),         // Date对象
  fn: () => console.log(1), // 函数
  sym: Symbol("key"),       // Symbol
  ref: null                 // 循环引用占位符
};
obj.ref = obj; // 循环引用

const copy = JSON.parse(JSON.stringify(obj));
// 输出:{ name: "John", date: "2024-05-15T...", ref: null }
// 丢失:函数、Symbol,Date转为字符串

代码示例 2:structuredClone

javascript 复制代码
const user = {
  like: { game: "Chess" },
  tags: new Set(["A", "B"]) // 支持复杂类型
};
const copy = structuredClone(user);
user.like.game = "Guitar"; 

console.log(copy.like.game); // "Chess"(完全独立)
console.log(copy.tags instanceof Set); // true(保留类型)

对比总结

方法 优点 缺点
JSON 方法 简单快速 丢失函数、Symbol、循环引用报错
structuredClone 支持复杂类型、循环引用 不兼容旧浏览器(如 IE)

4. 原型链与静态方法的本质

代码示例

javascript 复制代码
function Person() {}
// 实例方法(挂载原型)
Person.prototype.run = function() { 
  console.log("跑步"); 
};
// 静态方法(挂载构造函数)
Person.say = function() { 
  console.log("你好"); 
};

const p = new Person();
p.run();      //  输出 "跑步"(通过原型链调用)
Person.say(); //  输出 "你好"(直接调用构造函数方法)
p.say();      //  报错(实例无法访问静态方法)

原理

  • 原型方法 :所有实例共享,通过 prototype 定义。
  • 静态方法 :属于构造函数本身,用于工具函数或类级别操作。
    总结
    静态方法通过类名调用,实例方法通过对象调用,二者作用域不同。

5. 数组去重的正确实现

代码示例

javascript 复制代码
const arr = [1, 2, 2, 3, {n: 4}, {n: 4}];
// 正确去重方法(支持对象)
const unique = (arr) => {
  const seen = new Map(); // 记录唯一值
  return arr.filter(item => {
    const key = typeof item + JSON.stringify(item);
    return seen.has(key) ? false : seen.set(key, true);
  });
};
console.log(unique(arr)); // [1, 2, 3, {n:4}, {n:4}]

关键点

  • 对象无法直接比较({n:4} !== {n:4}),需序列化为字符串。
  • 使用 Map 存储唯一标识,避免重复。
    总结
    去重需根据数据类型选择合适策略,对象需特殊处理。

6. 原型链属性检测的误区

代码示例

javascript 复制代码
Object.prototype.sharedProp = "全局属性"; // 修改原型链
const obj = { ownProp: "自有属性" };

console.log(obj.hasOwnProperty("sharedProp")); // false(不检查原型链)
console.log("sharedProp" in obj);              // true(in检查原型链)

// 正确遍历对象属性
for (let key in obj) {
  if (obj.hasOwnProperty(key)) { // 过滤原型链属性
    console.log(key); // 仅输出 "ownProp"
  }
}

原理

  • hasOwnProperty 仅检查对象自身属性。
  • for...in 会遍历原型链上的可枚举属性。
    总结
    遍历对象时需用 hasOwnProperty 过滤原型链属性,避免意外行为。

总结与实践建议

  1. 拷贝选择

    • 简单对象用浅拷贝(Object.assign、展开运算符)。
    • 嵌套对象用深拷贝(structuredClone 或手动递归)。
  2. 原型链注意点

    • 实例方法定义在 prototype,静态方法直接挂载构造函数。
    • 使用 hasOwnProperty 区分自有属性和继承属性。
  3. 数组操作

    • 去重要考虑对象和特殊值的唯一性判断。
    • 使用 sliceconcat 等方法实现数组浅拷贝。
  4. 兼容性处理

    • 旧项目用 JSON 深拷贝时需规避特殊类型。
    • 现代项目优先使用 structuredClone

理解这些核心机制,可避免代码中的隐蔽问题,提升开发效率。

相关推荐
醉城夜风~42 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学6 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc886 小时前
微软EDGE浏览器功能学习
前端·学习·edge
破z晓6 小时前
javascript 导出excel表
开发语言·javascript·excel