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

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

相关推荐
Never_Satisfied5 分钟前
在JavaScript / HTML中,div容器在内容过多时不显示超出的部分
开发语言·javascript·html
鬼谷中妖6 分钟前
JavaScript 循环与对象:深入理解 for、for...in、for...of、不可枚举属性与可迭代对象
前端
大厂码农老A11 分钟前
你打的日志,正在拖垮你的系统:从P4小白到P7专家都是怎么打日志的?
java·前端·后端
im_AMBER12 分钟前
CSS 01【基础语法学习】
前端·css·笔记·学习
DokiDoki之父16 分钟前
前端速通—CSS篇
前端·css
pixle019 分钟前
Web大屏适配终极方案:vw/vh + flex + clamp() 完美组合
前端·大屏适配·vw/vh·clamp·终极方案·web大屏
ssf198725 分钟前
前后端分离项目前端页面开发远程调试代理解决跨域问题方法
前端
@PHARAOH25 分钟前
WHAT - 前端性能指标(加载性能指标)
前端
尘世中一位迷途小书童30 分钟前
🎨 SCSS 高级用法完全指南:从入门到精通
前端·css·开源
非凡ghost35 分钟前
火狐浏览器(Firefox)tete009 Firefox 多语便携版
前端·firefox