深拷贝

作者: 奔跑的兔子_ | 来源:发表于2019-08-22 11:12 被阅读0次

    来自 # js 什么是深拷贝问题?

    在 JavaScript 中,数据类型包括:

    1、基本数据(值)类型,如 undefined、null、number、boolean、string等

    2、引用类型:如Object、Array、Function等。

    一、什么是值类型?

    var a = 1;
    var b = a;
    
    b = b + 1
    console.log(a, b) // 1, 2
    

    值类型赋值时,会在栈内存空间中分配全新的地址,并得到相应的值。代码中 b 是从 a 创建来的,当b 变化时,a 不会受到影响。这是符合常识的。

    二、什么是引用类型?

    var data =  {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
    
    var newData = data
    
    newData.id = 1;
    console.log(data.id, newData.id) // 1 1
    

    再来看看引用类型(以Object对象为例),对象实际是存在于堆内存空间中。当我们要访问一个对象的时候,实际上是从栈内存中获取引用地址,然后根据这个引用地址再从堆内存中获取所需要的值。

    而引用类型赋值时,实际上是获取其引用地址,而不是直接获取值。所以,值发生改变时,源对象也会随着改变。

    三、使用ES Next新特性带来的 Object.assign 方法 和 扩展运算符

    1.对象

    使用 Object.assign({}, ...)来解决 "单层"对象引用问题

    let data =  {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
    
    let newData = Object.assign({}, data)
    
    newData.id = 1;
    console.log(data.id, newData.id) // 0 1
    

    或者使用对象的扩展运算符

    var data =  {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
    var newData = {title: 'fuck', ...data};
    newData.id = 1;
    console.log(data.id, newData.id) // 0 1
    
    2.数组

    (PS:JavaScript的数组也是引用类型,同理可以使用 Array.concat 方法 和 数组的扩展运算符来解决引用问题,这里就省略了)

    四、Object.assign 方法 和 扩展运算符的 “深入浅出” 问题 —— 浅拷贝

    需要注意的是,使用 Object.assign 及 扩展运算符都属于浅操作。如果往对象的外面再套一层的话,那问题就会浮现出来了。

    var data =  {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false    
        }
    }
    var newData = Object.assign({}, data)
    newData.item.id = 1;
    console.log(data.item.id, newData.item.id) // 1 1
    

    上述的结果表明,原始数据还是被更改了。

    五、解决深拷贝问题常见的三种方法

    1、jQuery的深拷贝工具: $.extend(true, ...)

    let data = {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false
        }
    }
    // 首参必须设置为 true 才算是深拷贝
    let newData = $.extend(true, {}, data);
    newData.item.id = 1
    console.log(data.item.id, newData.item.id) // 0 1
    

    为什么$.extend默认不使用深拷贝,还需要手动传参true才开启呢?

    这是因为深拷贝在实战中是比较消耗性能的,如无必要请使用浅拷贝即可。稍后我们会实现一个自己的深拷贝工具。你可以从中了解详情。

    2、巧用序列化:JSON.parse(JSON.stringify(...))

    let data = {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false
        }
    }
    let newData =  JSON.parse(JSON.stringify(data))
    newData.item.id = 1
    console.log(data.item.id, newData.item.id) // 0 1
    

    原理很简单,就是先将对象转换为字符串,再重新序列化为对象,这样理所当然不会有引用问题。值得一提的是,这些操作对数组也是有效的。

    3、递归拷贝(深拷贝)

    在我们封装一个自己的深拷贝工具之前,我们需要先跳过一些可能的拷贝错误认知 —— “不要单纯的认为,只要是拷贝嵌套对象就会产生深拷贝问题”。

    (1)就算是拷贝嵌套对象,如果操作的只是基本数据类型,不会有影响的。

    let data = {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false
        }
    }
    let newData = Object.assign({}, data);
    newData.title = 'fuck'
    console.log(data.title, newData.title) // 123  fuck
    

    (2)就算是嵌套对象,如果只拷贝其中一层对象,是不会产生问题的。

    let data = {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false
        }
    }
    let newData = Object.assign({}, data.item);
    newData.id = 1;
    console.log(data.item.id, newData.id) // 0 1
    

    有了以上两个认知,实际上我们可以实现手动深拷贝了。

    let data = {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false
        }
    }
    
    // 创建一个空对象
    let newData = {}
    
    // 基本数据类型,可以直接拿
    newData.title = data.title
    
    // data.item对象,其实可以使用 Object.assign 来拿
    // newData.item = Object.assign({}, data.item)
    
    // 但为了体现手动深拷贝细节,还是一步一步来吧。
    // 通过对data.item拆解,item对象被转换为一个个的基本数据类型直接赋值给newData.item的每一项
    // 如果 item 还有对象的话,那就只能依样画葫芦继续拆解。直到变成基本数据类型为止。
    newData.item = {}
    newData.item.id = data.item.id
    newData.item.book = data.item.book
    newData.item.avaliable = data.item.avaliable
    
    newData.item.id = 1
    console.log(data.item.id, newData.item.id) // 0 1
    

    通过对data.item拆解,item对象就转换为一个个的基本数据类型了,所以可以直接赋值给newData.item的每一项了。

    如果 item 还有对象的话,那就只能依样画葫芦继续拆解。直到变成基本数据类型为止。

    知道了如何手动深拷贝,现在我们把他转换为自动化深拷贝,常见的的实现方式是递归拷贝了:

    // 递归深拷贝
    var deepExtend = function(out) {
      out = out || {};
    
      for (var i = 1; i < arguments.length; i++) {
        var obj = arguments[i];
    
        if (!obj)
          continue;
    
        for (var key in obj) {
          if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object')
              out[key] = deepExtend(out[key], obj[key]);
            else
              out[key] = obj[key];
          }
        }
      }
    
      return out;
    };
    
    // demo
    let data = {
        title: '123',
        item: {
            id: 0,
            book: 'learn redux',
            avaliable: false
        }
    }
    var newData = deepExtend({}, data);
    newData.item.id = 1
    console.log(data.item.id, newData.item.id) // 0 1
    

    相关文章

      网友评论

          本文标题:深拷贝

          本文链接:https://www.haomeiwen.com/subject/cezgsctx.html