浅谈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;
}
相关推荐
xiaofeichaichai6 小时前
Webpack
前端·webpack·node.js
问心无愧05136 小时前
ctf show web入门111
android·前端·笔记
唐某人丶6 小时前
模型越来越强,我们还需要 Agent 工程吗?—— 从价值重估到 Harness 实践
前端·agent·ai编程
智码看视界6 小时前
现代Web开发基础:全栈工程师的起航点
前端·后端·c5全栈
JS菌7 小时前
手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现
前端·人工智能·后端
excel8 小时前
HLS TS 文件损坏的元凶:Git 提交与拉取
前端
Aphasia3118 小时前
https连接传输流程
前端·面试
徐小夕8 小时前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
threelab8 小时前
Three.js 物理模拟着色器 | 三维可视化 / AI 提示词
开发语言·前端·javascript·人工智能·3d·着色器