浅谈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;
}
相关推荐
Jerry说前后端5 分钟前
Android 移动端 UI 设计:前端常用设计原则总结
android·前端·ui
熊猫钓鱼12 分钟前
基于Trae CN与TrendsHub快速实现的热点百事通
前端·trae
LIUENG18 分钟前
Vue3 响应式原理
前端·vue.js
讨厌吃蛋黄酥22 分钟前
前端居中九种方式血泪史:面试官最爱问的送命题,我一次性整明白!
前端·css
bug菌22 分钟前
程序员转型产品经理,是逃避技术还是拥抱未来?Trae可替你回答!
aigc·ai编程·trae
龙在天25 分钟前
🤩 用Babel自动埋点,原来这么简单!
前端
Hierifer25 分钟前
跨端实现之网络库拦截
前端
随笔记27 分钟前
react-router里的两种路由方式有什么不同
前端·react.js
前端李二牛28 分钟前
异步任务并发控制
前端·javascript
你也向往长安城吗1 小时前
推荐一个三维导航库:three-pathfinding-3d
javascript·算法