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

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

相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC5 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble7 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长8 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能