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

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

相关推荐
kite01214 小时前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон4 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想6 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘6 小时前
快速部署和启动Vue3项目
java·javascript·vue
小小小小宇6 小时前
一个小小的柯里化函数
前端
灵感__idea6 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇6 小时前
前端双Token机制无感刷新
前端
小小小小宇6 小时前
重提React闭包陷阱
前端
小小小小宇7 小时前
前端XSS和CSRF以及CSP
前端
UFIT7 小时前
NoSQL之redis哨兵
java·前端·算法