JS深拷贝及面试时手搓源码

前言

在JavaScript中,深拷贝是指在拷贝对象的同时,创建一个新的对象,并且递归地复制原对象中的所有嵌套对象和引用类型。这样,新对象和原对象的所有层级都是相互独立的,修改一个对象的属性不会影响另一个对象。在上篇文章中我们讲了浅拷贝,今天我们来聊聊JavaScript中的深拷贝,看看它与浅拷贝有什么不同,并且我们来手搓一个深拷贝源码,在前端面试中容易被问到。

深拷贝和浅拷贝是两种不同的对象复制方式,主要区别在于复制对象时是否递归复制嵌套对象及其引用。

  1. 浅拷贝:

    • 只复制对象的第一层结构,不会递归复制嵌套对象,嵌套对象仍然是原对象和拷贝对象共享的。
    • 浅拷贝方法包括 Object.assign、数组的 slice、concat 等。
    • 修改拷贝对象的第一层属性不会影响原对象,但修改嵌套对象的属性会影响原对象和拷贝对象。

    示例代码:

    js 复制代码
    let obj = { a: 1, b: { c: 2 } };
    let shallowCopy = Object.assign({}, obj);
    
    console.log(shallowCopy); // { a: 1, b: { c: 2 } }
    shallowCopy.b.c = 3;
    console.log(obj.b.c); // 3
  2. 深拷贝:

    • 递归复制整个对象结构,包括嵌套对象及其引用,生成一个完全独立的新对象,修改拷贝对象不会影响原对象。

    • 深拷贝的实现通常使用递归和循环来处理对象的每一层,防止循环引用导致的无限递归。

    • 深拷贝的实现可能相对复杂,但可以确保生成的对象与原对象完全独立。

在JS中,因为浅拷贝只复制对象的第一层结构,而不会递归复制嵌套对象并且还是引用嵌套对象,当我们修改嵌套对象中的属性值时,例如此题中的shallowCopy.b.c = 3,修改新对象时,原对象中的属性值还是会改变。

而深拷贝递归复制整个对象结构,包括嵌套对象及其引用,生成一个完全独立的新对象,也就是说,如果原对象的对象里面还有嵌套的对象,但是深拷贝都会将嵌套的对象复制过去,而不是像浅拷贝一样去引用嵌套的对象,假设shallowCopy是我们深拷贝obj创造出的对象,当我们像这样shallowCopy.b.c = 3去修改对象中嵌套的对象的属性时,对应的对象还是不会改变,这就是深拷贝。

深拷贝实现

通过上篇文章,我们知道了常见的实现浅拷贝的方法,如slice concat assign等等,那js有没有提供实现深拷贝的方法呢?

其实js是有提供实现深拷贝的方法的,那么就是JSON.parse(JSON.stringify(obj)),在讲这个方法之前,我们先来搞懂JSON.stringifyJSON.parse

JSON.stringifyJSON.parse 是 JavaScript 中用于处理 JSON 数据的两个核心方法。

  1. JSON.stringify:

    • 作用:将 JavaScript 对象或值转换为 JSON 字符串。

    • 语法:JSON.stringify(value[, replacer[, space]])

    • 参数:

      • value: 要转换为 JSON 字符串的值。
      • replacer (可选):用于转换结果的函数或数组。
      • space (可选):用于控制缩进的字符串或数字。
    • 返回值:表示给定值的 JSON 字符串。

    js 复制代码
    let obj = { name: 'John', age: 30 };
    let jsonString = JSON.stringify(obj);
    console.log(jsonString); // '{"name":"John","age":30}'
  2. JSON.parse:

    • 作用:将 JSON 字符串解析为 JavaScript 对象。

    • 语法:JSON.parse(text[, reviver])

    • 参数:

      • text: 要解析的 JSON 字符串。
      • reviver (可选):用于在解析期间对结果执行转换的函数。
    • 返回值:解析得到的 JavaScript 对象。

    js 复制代码
    let jsonString = '{"name":"John","age":30}';
    let parsedObj = JSON.parse(jsonString);
    console.log(parsedObj); // { name: 'John', age: 30 }

JSON.parse(JSON.stringify(obj))

使用 JSON.parse(JSON.stringify(obj)) 进行深拷贝是一种常见的简单方法。这方法适用于对象中只包含原始类型数据(如字符串、数字、布尔值、null)以及没有循环引用的情况。这种方法的优点是简单易懂,缺点是无法处理包含函数、正则表达式、undefined 等特殊类型的对象。

示例代码:

js 复制代码
let obj = { a: 1, b: { c: 2 } };

let deepCopyObj = JSON.parse(JSON.stringify(obj));

console.log(deepCopyObj); // { a: 1, b: { c: 2 } }
deepCopyObj.b.c = 3;
console.log(obj.b.c); // 2

需要注意的是:

  1. 无法处理特殊类型: JSON.stringify() 会忽略对象属性中的函数、正则表达式、undefined 等,因为这些类型在 JSON 中没有对应的表示。在还原时,这些特殊类型将被忽略或转为 null。
  2. 循环引用问题: 如果对象存在循环引用,即对象属性之间形成一个循环,JSON.stringify() 无法处理,会抛出错误。

手搓源码

js 复制代码
function deepCopy(obj) {
    let objCopy = {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (obj[key] instanceof Object) {   // obj[key]是引用类型
                objCopy[key] = deepCopy(obj[key])
            } else {
                objCopy[key] = obj[key]
            }
       }
    }
    return objCopy
}
  1. deepCopy 函数接收一个对象 obj 作为参数。
  2. let objCopy = {} 创建了一个新的空对象,用于存储深拷贝的结果。
  3. for (let key in obj) 遍历对象 obj 的所有属性。
  4. if (obj.hasOwnProperty(key)) 判断属性是否是对象自身的属性,而不是继承自原型链上的属性。
  5. if (obj[key] instanceof Object) 判断属性值是否是引用类型(Object 类型)。如果是引用类型,就递归调用 deepCopy 对其进行深拷贝,然后将拷贝结果赋值给 objCopy[key]
  6. 如果属性值不是引用类型,直接将其赋值给 objCopy[key]
  7. 最终返回深拷贝的结果 objCopy

总结

1.深拷贝:

  • 递归复制整个对象结构,包括嵌套对象及其引用,生成一个完全独立的新对象,修改拷贝对象不会影响原对象。 - 深拷贝的实现通常使用递归和循环来处理对象的每一层,防止循环引用导致的无限递归。 - 深拷贝的实现可能相对复杂,但可以确保生成的对象与原对象完全独立。
  1. 浅拷贝:

    • 只复制对象的第一层结构,不会递归复制嵌套对象,嵌套对象仍然是原对象和拷贝对象共享的。
    • 浅拷贝方法包括 Object.assign、数组的 slice、concat 等。
    • 修改拷贝对象的第一层属性不会影响原对象,但修改嵌套对象的属性会影响原对象和拷贝对象。
相关推荐
码间舞几秒前
什么是Tearing?为什么React的并发渲染可能会有Tearing?
前端·react.js
gnip12 分钟前
做个交通信号灯特效
前端·javascript
小小小小宇12 分钟前
Webpack optimization
前端
尝尝你的优乐美14 分钟前
前端查缺补漏系列(二)JS数组及其扩展
前端·javascript·面试
咕噜签名分发可爱多16 分钟前
苹果iOS应用ipa文件安装之前?为什么需要签名?不签名能用么?
前端
她说人狗殊途31 分钟前
Ajax笔记
前端·笔记·ajax
JavaGuide40 分钟前
美团OC了,给的挺多!很满意!!
后端·面试
yqcoder40 分钟前
33. css 如何实现一条 0.5 像素的线
前端·css
excel1 小时前
Nuxt 3 + PWA 通知完整实现指南(Web Push)
前端·后端
yuanmenglxb20041 小时前
构建工具和脚手架:从源码到dist
前端·webpack