JavaScript实现对象的浅拷贝和深拷贝


拷贝指的是将一个对象的内容复制到另一个对象中。在JavaScript中,拷贝指的是将一个对象的内容复制到另一个对象中,拷贝分为浅拷贝和深拷贝且通常只针对引用类型。浅拷贝只拷贝一层对象,它会复制原始值以及引用类型数据的指针。而深拷贝会层层拷贝,它会将所有类型的属性值都会被复制。

浅拷贝

浅拷贝是指通过方法创建一个新对象把某个对象完整拷贝,拷贝后的新对象中的每个元素都是原始对象中相应元素的引用,即原对象的修改会影响新的对象。
在JavaScript中,原始类型的值直接存在调用栈中而引用类型的值是存放在堆中的,引用类型在调用栈里存放的只是堆中的引用地址。所以当对原始类型进行拷贝时,实际上是将原始值从一个变量复制到另一个变量,而不涉及引用。

常见的浅拷贝方法

  • Object.create(obj) // 创建一个对象
let a = {
    name:'张三'
}
let b =Object.create(a)
a.name='李四'

console.log(b.name); //输出: 李四
  • Object.assign({}, obj) // 对象拼接方法
let a ={
    name:'张三',
    info:{
        age:18
    }
}
let b=Object.assign({},a)
a.info.age=20

console.log(b)//输出: { name: '张三', info: { age: 20 } }
  • [].concat(arr) // 数组拼接方法
let arr = [1,2,3,{n:10}]
let newArr = [].concat(arr)
arr[3].n =100

console.log(newArr);
  • 数组解构
let arr = [1,2,3,{n:10}]
let newArr=[...arr]
arr[3].n =100

console.log(newArr);
  • slice() //数组剪切方法
const arr = [1, 2, 3, 4, 5];
const newArr = arr.slice();

console.log(newArr); // [1, 2, 3, 4, 5]

浅拷贝的实现

let obj={
    a:1,
    b:{
        n:2
    },
    c:[1,2,3,4]
}

let obj2 = shallowCopy(obj);

function shallowCopy(obj) {
    let obj2 = {};
    for (let key in obj) {
        if(obj.hasOwnProperty(key)){
            obj2[key] = obj[key];
        }
    } 
    return obj2;
}

obj2.b.n=100
console.log(obj); // 输出为:{ a: 1, b: { n: 100 }, c: [ 1, 2, 3, 4 ] }
console.log(obj2);// 输出为:{ a: 1, b: { n: 100 }, c: [ 1, 2, 3, 4 ] }

深拷贝

深拷贝是指通过方法创建一个新对象把某个对象完整拷贝,拷贝后的新对象中的内容是复制的原始对象的内容,新对象中的每个元素都是原始对象中相应元素的复制。所以修改新对象中的元素不会影响到原始对象中的对应元素。

常见的深拷贝方法

  • JSON.parse(JSON.stringify(obj))

当使用JSON.parse(JSON.stringify(obj)) 对对象进行深拷贝时,会将对象转换为 JSON 字符串,然后再将 JSON 字符串转换回对象,从而实现深层次的拷贝。

let obj = {
    name: "张三",
    info: {
        age: 25,
        sex: "男"
    }
};

// 使用 JSON.parse(JSON.stringify(obj)) 进行深拷贝
let obj2 = JSON.parse(JSON.stringify(obj));

// 修改新对象中的属性的值
obj2.info.age=18;

// 输出原始对象和克隆后的对象
console.log(obj); // 输出: { name: '张三', info: { age: 25, sex: '男' } }
console.log(obj2); // 输出: { name: '张三', info: { age: 18, sex: '男' } }

值得注意的是:

  1. 这个方法不能处理underfined,function,Symbol这些数据类型
  2. 也无法处理循环引用。

这个方法可以使用大部分情况,如果遇到以上两种问题可以使用lodash的深拷贝函数

  • structuredClone(obj)
let obj = {
    a: 1,
    b: {
        n: 2
    },
    c: [1, 2, 3, 4]
}

const obj2=structuredClone(obj) 

structuredClone() 方法是用于复制一个对象,包括其所有属性和嵌套对象,而不会受限于像 JSON.stringify() 和 JSON.parse() 那样只能处理一部分 JavaScript 对象的限制。
值得注意的是structuredClone(obj)无法兼容symbol 和function。

深拷贝的实现

  • 递归
let obj = {
  name: '张三',
  age: 18,
  a: {
    n: 1
  },
  b: undefined,
  c: null,
  d: function () { },
  e: Symbol('hello'),
  f: {
    n: 100
  }
}

function deepCopy(obj) {
  let objCopy = {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      //区分obj[key]是原始类型或者引用类型
      if (obj[key] instanceof Object) {// 不能直接赋值
        objCopy[key] = deepCopy(obj[key]) // 递归调用 
      } else {
        objCopy[key] = obj[key]
      }
    }
  }
  return objCopy
}

let obj2 = deepCopy(obj)
obj.age = 20;

console.log(obj2); 

通过遍历对象的属性,并利用递归来处理嵌套对象,从而实现了对象的深拷贝。

  • MessageChannel()
let obj = {
    a: 1,
    b: {
        n: 2
    },
    c: [1, 2, 3, 4]
}

function deepCLone(obj) {
    return new Promise((resolve) => {
        const { port1, port2 } = new MessageChannel();
        port1.postMessage(obj)
        port2.onmessage = (msg) => {
            resolve(msg.data)
        }
    })
}

deepCLone(obj).then((res) => {
    obj2 = res
    console.log(obj2);
})

在这个函数中,使用 MessageChannel 创建了两个端口 port1 和 port2,然后利用其中一个端口将对象 obj 发送给另一个端口。当另一个端口接收到消息时,将其作为深拷贝后的对象返回。通过 MessageChannel 实现的深拷贝可以处理对象包含循环引用的情况。

总结

需要注意的是,深拷贝会消耗更多的内存和时间,而在处理简单对象结构、不需要深层嵌套对象的情况下,浅拷贝是一个简单而有效的工具。总之,在进行拷贝操作时,需要根据实际情况选择合适的方式来确保程序的正确性和性能。


  • 本文作者:下载幸福
  • 本文链接:https://simply.webkit.top/webdev/js-copy.html
  • 版权申明:除非特别说明,否则均为本站原创文章,转载或复制请注明出处。

如何防止重复请求?附解决方案

jQuery 4.0 beta版本发布

评 论