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 等。
    • 修改拷贝对象的第一层属性不会影响原对象,但修改嵌套对象的属性会影响原对象和拷贝对象。
相关推荐
whisperrr.38 分钟前
【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?
前端·ajax·json
烂蜻蜓2 小时前
前端已死?什么是前端
开发语言·前端·javascript·vue.js·uni-app
Rowrey3 小时前
react+typescript,初始化与项目配置
javascript·react.js·typescript
谢尔登3 小时前
Vue 和 React 的异同点
前端·vue.js·react.js
祈澈菇凉7 小时前
Webpack的基本功能有哪些
前端·javascript·vue.js
小纯洁w7 小时前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
想睡好8 小时前
css文本属性
前端·css
qianmoQ8 小时前
第三章:组件开发实战 - 第五节 - Tailwind CSS 响应式导航栏实现
前端·css
记得早睡~8 小时前
leetcode150-逆波兰表达式求值
javascript·算法·leetcode
zhoupenghui1688 小时前
golang时间相关函数总结
服务器·前端·golang·time