浅谈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;
}
相关推荐
Revolution618 小时前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试
用户2181697049309 小时前
Flutter(四)Dart语法 空安全 运算符 流程控制
前端
许彰午9 小时前
政务督办的分合模式:主办协办的并发审批
前端·javascript·政务
用户69371750013849 小时前
AI时代,程序员该往哪走?
前端·后端
ttwuai9 小时前
GoFrame 后台日志清空失败:无 WHERE 删除为什么被拦住
前端·golang
易筋紫容9 小时前
创建型模式:对象的诞生艺术
开发语言·前端·javascript
半句唐诗10 小时前
我是如何通过 Access Token 成功发布第一个 npm 包的
前端·npm·node.js
程序员黑豆10 小时前
鸿蒙应用开发:@Provider 与 @Consumer 跨组件双向同步详解
前端·harmonyos
paopaokaka_luck10 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts
牧艺10 小时前
cos-design PhotoAlbum:用 CSS 3D 做一个「能翻页」的实体相册
前端·css·交互设计