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

1 概述

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

2 数据类型

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

3 深浅拷贝

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

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

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

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

深浅拷贝的示意图如下图:
详细讲解js中的深拷贝与浅拷贝_第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)

执行结果如下:
详细讲解js中的深拷贝与浅拷贝_第3张图片
类似的还有…扩展运算符、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))

详细讲解js中的深拷贝与浅拷贝_第4张图片
实际项目开发过程中,我使用的这种方式最多,除此之外,还可以使用

  • _.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: ``
        })
        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: ``
        })
        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从入门到项目实战》书籍
    最后特别感谢评论区的小伙伴,感谢他们指出了我的问题。

你可能感兴趣的:(js,javascript,深拷贝,浅拷贝,Array,Object)