常用技点 - 拷贝

谈到拷贝,大家都很了解,甚至有人会回答:深浅拷贝是主要技术点,就这一个知识点,恰好是面试提问的问题之一。

下面我们来聊一聊深浅拷贝。

深拷贝:复制一份数据到新地址,修改拷贝后的数据不会对原有的数据造成影响。

浅拷贝:对原始类型来讲,拷贝是的数值;对引用类型来讲,拷贝的是引用地址,虽然拷贝后两份数据在内存中占据不同位置,但是两份数据的属性都指向同一地址。浅拷贝只会复制对象的第一层属性,如果属性内存在嵌套关系,也会对其引用地址复制,而不是值得复制。对此,修改嵌套对象的值,原数据也会收到影响。

深拷贝浅拷贝
修改拷贝后数据对源数据影响
是否是拷贝地址创建新地址

深拷贝方法

1)第三方库 - loadsh

注:第三库的引入可能会对性能造成影响

2)递归

function deepClone(obj) {
    if (typeof obj !== 'object' || obj === null) return obj;

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

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key]);
        }
    }

    return clone;
}

const a = { a: 1};
const b = deepClone(a);

3)JSON.parse(JSON.stringify)

注:对函数、Date、正则,undefined等慎用。可能存在丢失信息。简单的对象和数组拷贝是个不错的选择

const a = {a : 1}
const b = JSON.parse(JSON.stringify(a));

4)jQ的extend方法

const a = {a: 1};
const b = $.extend(true, {}, a);

5)structuredClone

注:2021引入的新概念,属于web API。函数克隆慎用,会抛出错误。

const a = { a: 1 };
const b = structuredClone(a);

6)proxy的set和get

注:使用proxy的set和get陷阱,追踪和复制原数据的属性变化。Date和正则慎用,他们不会被视为引用类型。由此可见,只有属性变化才会被复制。

function deepClone(obj) {
  // 定义一个空对象或数组作为克隆的目标
  const cloneTarget = Array.isArray(obj) ? [] : {};

  // 使用Proxy来拦截目标对象上的操作
  return new Proxy(cloneTarget, {
    get(target, key, receiver) {
      // 返回属性值
      return Reflect.get(target, key, receiver);
    },

    set(target, key, value, receiver) {
      // 在设置属性值时,如果value是个对象,则递归克隆
      if (typeof value === 'object' && value !== null && !(value instanceof Date) && !(value instanceof RegExp)) {
        value = deepCloneWithProxy(value);
      }

      // 设置属性值
      Reflect.set(target, key, value, receiver);

      return true; // 必须返回true以表示设置成功
    }
  });
}

// 示例使用
const a = { a: 1 };
const cloned = deepClone=(a);

7)类和构造器函数

注:创建类和使用构造器函数可以实现定制化的数据拷贝。

8)MessageChannel

注:MessageChannel属于webAPI,不算是拷贝方法,严格来说是一种通信机制,而我们可以变废为宝的利用它的消息通信机制来实现拷贝,在循环递归中使用效果更加。它在序列化和反序列化是会自动处理循环引用,从而实现拷贝。这个拷贝过程是异步的,想同步拷贝的慎用。

function deepCloneWithMessageChannel(obj) {
  return new Promise((resolve) => {
    // 创建一个新的MessageChannel
    const { port1, port2 } = new MessageChannel();

    // 当消息接收到时的处理函数
    port1.onmessage = function(event) {
      resolve(event.data); // 这里event.data就是深拷贝后的对象
    };

    // 向另一个端口发送对象,这会自动进行序列化和反序列化
    port2.postMessage(obj);
  });
}

// 使用示例
async function testDeepClone() {
  const original = { a: 1, b: { c: 2 }, d: [3, 4] };
  original.self = original; // 添加循环引用
  const cloned = await deepCloneWithMessageChannel(original);
  cloned.b.c = 5;
  cloned.d.push(6);

  console.log('Original:', original);
  console.log('Cloned:', cloned);
  // 检验深拷贝效果和循环引用处理
}

testDeepClone();

浅拷贝方法

1)object.assign

let a = { a: 1, b: { x: 10 } };
let b = Object.assign({}, a);

 2)扩展运算符(...)

const a = { aa: 1 };
const b = {...a};

b.aa = 2;

console.log("a", a);
console.log("b", b);

3)循环遍历

const a = { a: 1 };
const b = {};
for(let key in a) {
    if(a.hasOwnProperty(key)) {
        b[key] = a[key];
    }
}

4)jQ中的extend

const a = { a: 1 };
const b = $.extend({}, a);

5)赋值

const a = {a: 1};
const b = a;

6)数组类型的拷贝会多两种方法:cancat和slice。

无论是深拷贝还是浅拷贝,大家都要从实际角度触发,性能方面也要考虑。

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值