详细讲解js中的深拷贝与浅拷贝

1 概述

深拷贝与浅拷贝在其它语言中也经常被提及到,在实际项目开发过程中也常常需要区分当前使用的到底是深拷贝还是浅拷贝,有时候在该使用深拷贝的地方,我们使用了浅拷贝,会导致深藏不露的bug。

2 数据类型

在探讨深浅拷贝之前,我们先梳理一下js中的数据类型,js的数据类型分为两类:基本数据类型和引用数据类型,前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中
如下图所示,基本类型如number、string、boolean、Null和undefined等存储在栈内存中,而引用数据类型如Array、Object和函数等则是分别存储数据1的地址、数据2的地址和数据3的地址。
栈内存与堆内存

3 深浅拷贝

3.1 拷贝对象为基本数据类型

js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是深拷贝。
例如,let a = 10 ; b = a , 修改其中一个变量的值,不会影响到另一个变量的值。

3.2 拷贝对象中有引用数据类型

浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了“指向堆内存的地址”。
深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。

深浅拷贝的示意图如下图:
在这里插入图片描述

浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

4、js中的深浅拷贝

4.1 浅拷贝

4.1.1 slice()

    <script>
        let arr1 = [1, 42, [3, 4]]
        let arr1Copy = arr1.slice()
        arr1Copy[0] = 10
        arr1Copy[2][0] = 100
        console.log(arr1) // [1, 42, [100, 4]]
        console.log(arr1Copy) // [10, 42, [100, 4]]
    </script>

arr1中的元素1是基本数据类型,所以arr1Copy能够改变其值,而不影响arr1的值。
而[3, 4]是引用数据类型,arr1和arr1Copy指向同一块堆内存地址,所以这两个变量中3都变成了100。

4.1.2 concat()

 let arr2 = ['cat', 'dog', 'pig', {'name': 'xia', 'age': 18}]
 let arr2Copy = [].concat(arr2)
 arr2Copy[2] = 'big pig'
 arr2Copy[3]['name'] = 'aa'
 console.log(arr2)
 console.log(arr2Copy)

执行结果如下:
执行结果
类似的还有…扩展运算符、Array.from、Object.assign()方法。

4.2 深拷贝

4.2.1 JSON.parse(JSON.stringify(obj))

万能转换器 JSON.parse(JSON.stringify(obj))深拷贝已有对象,它可以深拷贝多层级的,不用担心嵌套问题。

  • JSON.stringfy() 将对象序列化成json对象
  • JSON.parse() 反序列化——将json对象反序列化成js对象
JSON.stingify(obj)将js中的对象转换成JSON字符串
 let jack = {
     name: 'jack'
 }
 console.log(jack)
 console.log(JSON.stringify(jack))

它们在格式上有区别。下图中的第一个是对象,name没有双引号括起来。第二个是json字符串,其中,name用双引号括起来了
在这里插入图片描述

JSON.parse()将json字符串解析成对象
 let obj = {
     name: '静茹秋叶'
 }
 console.log('obj: ', obj)
 console.log('json string: ', JSON.stringify(obj))

 let str = JSON.stringify(obj)
 console.log('--------------')
 console.log(str)
 console.log('str to obj: ', JSON.parse(str))

在这里插入图片描述
实际项目开发过程中,我使用的这种方式最多,除此之外,还可以使用

  • _.cloneDeep()
  • jQuery.extend()
  • 手写循环递归

5、Vue中的浅拷贝与深拷贝

两个button-counter共用同一个jack对象,用同一块地址,当其中一个实例改变时,会影响另一个实例的值。(浅拷贝)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue的data选项</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button-counter></button-counter>
        <button-counter></button-counter>
    </div>

    <script>
        let jack = {
            counter: 0
        }
        // 子组件
        Vue.component('button-counter', {
            data() {
                // 函数类型
                return jack
            },
            template: `<button @click="counter++">click {{counter}} times</button>`
        })
        let vm = new Vue({
            el: '#app' // mount到DOM上
        })
    </script>
</body>
</html>

采用深拷贝,重新创建一块内存。这样,vue的button-counter组件中的counter值互不影响。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue的data选项</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button-counter></button-counter>
        <button-counter></button-counter>
    </div>

    <script>
        let jack = {
            counter: 0
        }
        // 子组件
        Vue.component('button-counter', {
            data() {
                // 函数类型
                return JSON.parse(JSON.stringify(jack))
            },
            template: `<button @click="counter++">click {{counter}} times</button>`
        })
        let vm = new Vue({
            el: '#app' // mount到DOM上
        })
    </script>
</body>
</html>

(例子来源于《Vue.js从入门到项目实战》书籍)


参考资料:

  1. https://vue3js.cn/interview/JavaScript/copy.html#
  2. https://github.com/wesbos/JavaScript30
  3. 《Vue.js从入门到项目实战》书籍
    最后特别感谢评论区的小伙伴,感谢他们指出了我的问题。
  • 59
    点赞
  • 334
    收藏
    觉得还不错? 一键收藏
  • 35
    评论
JavaScript深拷贝浅拷贝是两种常用的对象拷贝方法。 浅拷贝是按位拷贝对象,创建一个新对象,并将原始对象的属性值精确拷贝到新对象。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址。因此,如果其一个对象改变了这个地址,就会影响到另一个对象。\[3\] 深拷贝是将原始对象及其所有嵌套对象的属性值都进行拷贝,创建一个全新的对象。深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝深拷贝相比于浅拷贝速度较慢并且花销较大。\[2\] 在JavaScript,实现深拷贝的方法有以下几种: 1. 通过递归的方式实现深拷贝,可以使用jQuery的extend方法来实现。这种方法会递归地遍历对象的所有属性,并进行拷贝。\[1\] 2. 使用JSON.parse()和JSON.stringify()方法来实现深拷贝。首先将对象转换为JSON字符串,然后再将JSON字符串转换为新的对象。这种方法可以实现简单的深拷贝,但是对于包含函数、正则表达式等特殊类型的对象可能会出现问题。\[1\] 3. 使用第一种方法的递归方式来实现深拷贝。这种方法需要自己编写递归函数来遍历对象的所有属性,并进行拷贝。\[1\] 总结起来,深拷贝是将原始对象及其所有嵌套对象的属性值都进行拷贝,创建一个全新的对象,而浅拷贝只是按位拷贝对象,创建一个新对象,并将原始对象的属性值精确拷贝到新对象。在JavaScript,可以通过递归方式、JSON.parse()和JSON.stringify()方法等来实现深拷贝。 #### 引用[.reference_title] - *1* [JavaScript浅拷贝(三种方法)](https://blog.csdn.net/weixin_46022934/article/details/121415082)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [js实现浅拷贝深拷贝](https://blog.csdn.net/ab_er/article/details/126009166)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值