浅谈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;
}
相关推荐
白水清风16 小时前
关于Js和Ts中类(class)的知识
前端·javascript·面试
前端Hardy16 小时前
只用2行CSS实现响应式布局,比媒体查询更优雅的布局方案
javascript·css·html
小菜全16 小时前
uniapp基础组件概述
前端·css·vue.js·elementui·css3
小天呐16 小时前
qiankun 微前端接入实战
前端·js·微前端
周航宇JoeZhou16 小时前
JP4-7-MyLesson后台前端(五)
java·前端·vue·elementplus·前端项目·mylesson·管理平台
车口16 小时前
滚动加载更多内容的通用解决方案
javascript
Yaavi16 小时前
一个基于markdown的高性能博客模板
前端·开源·源码
艾小码17 小时前
手把手教你实现一个EventEmitter,彻底告别复杂事件管理!
前端·javascript·node.js
兵临天下api17 小时前
微店店铺商品搜索(item_search_shop)接口深度分析及 Python 实现
trae
幸福摩天轮17 小时前
npm发布包
前端