浅谈js拷贝问题-解决拷贝数据难题

前言:在js中我们经常需要操作数据,对于一般的数据类型,我们可以直接使用值赋值的方式,创建一个新的变量,而对于一个引用类型变量,如果我们需要保留一份备份,就需要进行对象拷贝,我们如何进行拷贝呢,可以看看本文

  • 是什么?

因为js中的原始类型存在栈中,引用类型存在堆中,再把引用地址存在栈中。所以拷贝一般发生在引用类型上。效果是创建一份新的数据,让新数据拥有和原数据一样的属性值。

  • 特点?

拷贝分为

  • 浅拷贝:遍历原对象的第一层属性,如果属性是原始值,则直接赋值,如果属性是引用类型值,则赋值属性的引用。
  • 深拷贝:遍历对象上的所有属性并赋值给新对象,如果是引用类型,则将递归遍历该属性,直到属性值都为原始值

浅拷贝方法有哪些

  • ...解构赋值,可以适用于数组、对象
TypeScript 复制代码
const a = { a: 1, b: { c: 2 } };

const b = { ...a };

// 因为是浅拷贝,所以第一层的属性可以随便修改,不影响原对象
b.a = 2;
// 而引用类型的属性修改会影响原属性
b.b.c = 3;

console.log(a); // { a: 1, b: { c: 3 } }
console.log(b); // { a: 2, b: { c: 3 } }
  • 数组方法

    • slice浅拷贝数组
    • [].concat[arr]
    • [...arr]
  • 对象方法

    • Object.assign

    TypeScript 复制代码
    const a = { a: 1, b: { c: 2 } };
    
    const b = Object.assign({}, a); // 和 ... 效果一样
    
    // 因为是浅拷贝,所以第一层的属性可以随便修改,不影响原对象
    b.a = 2;
    // 而引用类型的属性修改会影响原属性
    b.b.c = 3;
    
    console.log(a); // { a: 1, b: { c: 3 } }
    console.log(b); // { a: 2, b: { c: 3 } }

如何实现浅拷贝

递归遍历对象的属性,赋值给新对象

TypeScript 复制代码
function shallowCopy(obj) {
  const res = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      res[key] = obj[key];
    }
  }
  return res;
}

深拷贝方法有哪些

  • JSON.parse(JSON.stringify)实现深拷贝

使用JSON序列化的方式存在很多局限性,比如

  • 无法处理undefinedBigIntSymbolfunction() {}、循环引用
  • structuredClonejs新规范中的深拷贝方法

局限性

  • 无法处理Symbolfunction

优点可以处理循环引用

TypeScript 复制代码
const obj = {
  a: 1,
  b: "1313",
  c: false,
  d: null,
  f: undefined,
  g: 123n, // TypeError: Do not know how to serialize a BigInt
  // h: Symbol(0),
  // j: function () {},
  k: {},
  l: {},
};

// 创建循环引用
obj.k.a = obj.l;
obj.l.a = obj.k;

// const newObj = JSON.parse(JSON.stringify(obj))

const newObj = structuredClone(obj)

console.log(newObj);
  • MessageChannel也可以用来做深拷贝,和structuredClone一样,也是无法处理Symbolfunction
TypeScript 复制代码
const obj = {
  a: 1,
  b: "1313",
  c: false,
  d: null,
  f: undefined,
  g: 123n, // TypeError: Do not know how to serialize a BigInt
  // h: Symbol(0),
  // j: function () {},
  k: {},
  l: {},
};

// 创建循环引用
obj.k.a = obj.l;
obj.l.a = obj.k;

function messageChannelClone(obj) {
  return new Promise((resolve, reject) => {
    const { port1, port2 } = new MessageChannel();
    port1.postMessage(obj);
    port2.onmessage = (msg) => {
      resolve(msg.data);
      port1.close()
    };
  });
}

const newObj =  await messageChannelClone(obj);

console.log(newObj);

如何实现深拷贝

简单版本的深拷贝,可以处理原始类型和引用类型中的数组对象、以及循环引用

TypeScript 复制代码
function deepClone(obj, hash = new WeakMap()) {
  // 处理null和非对象 原始值
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 剩余的均为引用类型
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  let res = Array.isArray(obj) ? [] : {};

  // 将所有对象的引用都记录到 WeakMap 中,以便后续如果有重复的引用,可以直接拿出赋值
  hash.set(obj, res);

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 如果是数组,则key就是数组索引
      res[key] = deepClone(obj[key], hash);
    }
  }
  return res;
}
相关推荐
AIBox3659 小时前
openclaw api 配置教程,支持 Claude、Gemini、GPT5.4 等模型
javascript·人工智能·gpt
Rattenking9 小时前
uni-app组件开发----自定义数字键盘组件
前端·javascript·uni-app
前端小张同学9 小时前
AI沉淀出来的一些前端开发rules
前端
小沐°9 小时前
vue3+element-plus 实现动态菜单和动态路由的渲染
前端·javascript·vue.js
蓝黑20209 小时前
Vue导入和注册组件
前端·javascript·vue
ct9789 小时前
Vue3 状态管理方案:Pinia 全指南
javascript·vue.js
见青..9 小时前
DedeCMS织梦5.7--CSRF漏洞复现
前端·网络安全·csrf·漏洞复现
浩星9 小时前
electron系列3:进程模型深度解析:主进程、渲染进程、预加载脚本
前端·electron·前端框架
恋猫de小郭9 小时前
手机直接运行 Codex/OpenCode/Claude Code ,实时管理你的 AI Coding
前端·openai·ai编程
Canace9 小时前
为什么不要让LLM帮我们写文档
前端·人工智能