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

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

相关推荐
姑苏洛言6 分钟前
如何让用户回到上次阅读的位置?——前端视角下的用户体验优化实践
前端
小王码农记10 分钟前
el-select组件与el-tree组件结合实现下拉选择树型结构框
javascript·vue.js·elementui
kovlistudio11 分钟前
红宝书第三十一讲:通俗易懂的包管理器指南:npm 与 Yarn
开发语言·前端·javascript·学习·npm·node.js
我爱吃干果23 分钟前
ZoomCharts使用方法
前端·javascript·笔记·zoom
旧厂街小江33 分钟前
LeetCode 第111题:二叉树的最小深度
前端·算法·程序员
&白帝&34 分钟前
vue实现大转盘抽奖
前端·javascript·vue.js
DataFunTalk36 分钟前
不是劝退,但“BI”基础不佳就先“别搞”ChatBI了!
前端·后端
古德奈特41 分钟前
npm 和 npx 的区别详解
前端
用户33931307053941 分钟前
页面计时器不准确,怎么解决?
javascript
猿榜41 分钟前
深入浅出 Python 面向对象编程
javascript·python