zoukankan      html  css  js  c++  java
  • 简单理解深拷贝和浅拷贝

    在 JS 中数据类型分为值类型和引用类型,对于值类型,变量中存放的是具体的值,而对于引用类型,变量中存放的是地址。

    对于值类型:

    const a = 3;
    let b = a;
    b = 4;
    console.log(a); 
    

    输出结果是 3.

    对于引用类型:

    const obj1 = {
      age: 18
    };
    let obj2 = obj1;
    obj2.age = 20;
    console.log(obj2.age);
    

    输出结果:20.

    上述变量在内存中的存储:

    // 深拷贝代码
    const obj1 = {
      age: 20,
      address: {
        city: 'nanjing'
      },
    };
    // 验证代码
    const obj2 = deepClone(obj1);
    obj2.age = 33;
    console.log('obj2 = ', obj2);
    console.log('obj1 = ', obj1);
    /**
     * 深拷贝
     * @param obj 要拷贝的对象
     * @returns {{}}
     */
    function deepClone(obj = {}) {
      // 判断 obj 是不是数组或对象,这也是递归的终止条件
      if (typeof obj !== 'object' || typeof obj == null) {
        return obj;
      }
      // 初始化返回值
      let result;
      result = obj instanceof Array ? [] : {};
      // 递归拷贝
      for(key in obj) {
        if(obj.hasOwnProperty(key)) {
          result[key] = deepClone(obj[key]);
        }
      }
      return result;
    }
    

    在程序运行过程中,当一个函数被调用时会把调用时的位置等信息保存在堆栈中,这就是通常说的保护现场,当程序执行完成之后再将保存在堆栈中的信息pop出去,恢复现场。

    在上面的深拷贝程序中:

    // 递归拷贝
    for(key in obj) {
      if(obj.hasOwnProperty(key)) {
        result[key] = deepClone(obj[key]);
      }
    }
    

    1) key = age 时

    result[age] = deepClone(20)

    此时调用 deepClone(20) 时,会将调用位置等信息 push 到堆栈中,

    此时堆栈中的内容:

    调用位置 调用信息
    31行 deepClone(20)
    9行 deepClone({
    age: 20,
    address: {
    city: 'nanjing'
    },
    })

    由于 20 不是数组或对象此时在执行

    if (typeof obj !== 'object' || typeof obj == null) {
        return obj;
    }
    

    时,会将 20 返回,此时函数执行结束,堆栈中的 deepClone(20)被 pop 出去。

    此时堆栈中的内容:

    调用位置 调用信息
    9行 deepClone({
    age: 20,
    address: {
    city: 'nanjing'
    },
    })

    此时拷贝的结果 result 内容内容如下:

    result:

    key value
    age 20

    2)当 key = address 时

    result[address] =  deepClone({
      city: 'nanjing'
    })
    

    此时拷贝结果 result 的内容:

    result:

    key value
    age 20
    address deepClone({
    city: 'nanjing'
    })

    此时调用

    deepClone({
      city: 'nanjing'
    })
    

    会将调用位置等信息 push 到堆栈中,

    此时堆栈中的内容:

    调用位置 调用信息
    31行 deepClone({city: 'nanjing'})
    9行 deepClone({
    age: 20,
    address: {
    city: 'nanjing'
    },
    })

    由于 {city: 'nanjing'} 是一个对象,所以会执行初始化返回值

    // 初始化返回值
    let result;
    result = obj instanceof Array ? [] : {};
    

    由于 obj 此时是一个对象,所以 result = {},然后接着执行递归拷贝

    // 递归拷贝
    for(key in obj) {
      if(obj.hasOwnProperty(key)) {
        result[key] = deepClone(obj[key]);
      }
    }
    

    此时 result[city] = deepClone('nanjing')

    此时堆栈中的内容:

    调用位置 调用信息
    31行 deepClone({'nanjing')
    31行 deepClone({city: 'nanjing'})
    9行 deepClone({
    age: 20,
    address: {
    city: 'nanjing'
    },
    })

    由于 'nanjing' 不是一个数组或对象,所以会直接返回 'nanjing'result[city] = deepClone('nanjing')='nanjing'deepClone('nanjing') 执行结束,此时堆栈中的内容:

    调用位置 调用信息
    31行 deepClone({city: 'nanjing'})
    9行 deepClone({
    age: 20,
    address: {
    city: 'nanjing'
    },
    })

    紧接着在 31 行( 因为 deepClone({'nanjing') 就是在31行调用的)接着向下执行执行for循环,由于只有一个city自有属性所以此时不满足for循环了,所以会继续向下执行 return result ,因为上面 result[city] = deepClone('nanjing')='nanjing' 所以 result = { city: 'nanjing'}(再强调一遍这里的 result 和最终返回的 result 不是一个)

    此时堆栈的内容为:

    调用位置 调用信息
    9行 deepClone({
    age: 20,
    address: {
    city: 'nanjing'
    },
    })

    deepClone({city: 'nanjing'}) 被 pop 出去之后,由于是在 31 行被调用的,所以会继续从31行向下执行for循环,由于 obj1 只有 age 和 address 两个属性而且都已经遍历过了,所以会跳出 for 循环向下执行 return result;

    此时 result 的内容为:

    result:

    key value
    age 20
    address { city: 'nanjing' }

    这个时候 deepClone({<br/> age: 20,<br/> address: {<br/> city: 'nanjing'<br/> },<br/>}) 被从堆栈中 pop 出去,堆栈中现在没有函数的调用信息了,程序从第 9 行继续向下执行,验证深拷贝是否成功。

    深拷贝和浅拷贝之间的区别就在于深拷贝中创建了一个 result 来复制原有的对象中的所有属性,浅拷贝仅仅是复制了原有对象的地址。

    有几点需要注意

    1)注意 deepClone 函数刚开始引用类型和值类型的判断,如果是值类型没有必须要再进行for 循环取属性遍历了,直接返回即可。这也是作为递归的终止条件,只要用到递归就必须要设置递归终止条件。

    2)注意判断是数组还是对象,传递进来的就是需要拷贝的,传递进来的是对象就要将拷贝的结果初始化为对象,传递进来的是数组就要将拷贝的结果初始化为数组。注意每次的名字虽然都叫 result,但是并不是同一个,它们的地址都是不同的。

    3)递归,这是深拷贝逻辑上的核心。

  • 相关阅读:
    进度3
    进度2
    进度1
    库存物资管理系统
    课程管理系统
    文件与流作业
    bzoj4027: [HEOI2015]兔子与樱花
    bzoj2067: [Poi2004]SZN
    bzoj2071:[POI2004]山洞迷宫
    bzoj1063: [Noi2008]道路设计
  • 原文地址:https://www.cnblogs.com/zhangguicheng/p/12721219.html
Copyright © 2011-2022 走看看