最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 狠狠滴深拷贝

    正文概述 掘金(尼克陈)   2021-05-18   417

    前言

    对象的深浅拷贝,一直是老生常谈的话题,平台上的文章数量可谓是汗牛充栋,要从这块素材里找突破几乎是不可能。索性我就写一篇文章,积累一下自己的学习心得,以便后续复习的时候,能有一个比较清晰的思路。

    定义

    浅拷贝:将数据中所有的数据引用下来,并指向同一个存放地址,拷贝的数据修改之后,会对原数据产生副作用。

    深拷贝:将数据中所有的数据拷贝下来,对拷贝之后的数据进行修改不会对原始数据产生副作用。

    非深拷贝

    业务中,很多时候你做的是浅拷贝,如果不影响业务逻辑,你可能不关心这些东西。

    等号赋值

    引用类型的等号赋值是最常见浅拷贝,如下所示:

    var obj = {
      name: 'Nick'
    }
    
    var newObj = obj
    

    此时你修改 newObj.name = 'Chen',则会使得 obj 也会跟着变化,这是因为声明的 obj 属于引用类型的变量,存在了全局作用域下的堆内存中。赋值给 newObj,只是将内存的地址赋值给了它,所以修改 newObj 的属性,也就是修改了堆内存中数据的属性,从而 obj 也会跟着改变。

    var obj = {
      name: 'Nick'
    }
    
    var newObj = obj
    
    newObj.name = 'Chen'
    
    console.log(obj.name) // 'Chen'
    console.log(newObj.name) // 'Chen'
    

    Object.assign

    你以为 Object.assign 是深拷贝方法,其实不然。它也是浅拷贝,只不过是第一级的原始类型的数据,不受牵连,引用类型还是会被篡改,我们用数据说话:

    var obj = {
      name: 'Nick',
      hobby: ['code', 'movie', 'travel', { a: 1 }]
    }
    
    var newObj = Object.assign({}, obj)
    
    newObj.name = 'Chen'
    newObj.hobby[0] = 'codeing'
    newObj.hobby[3].a = 2
    
    console.log('obj', obj)
    console.log('newObj', newObj)
    

    打印结果如下:

    狠狠滴深拷贝

    绿色箭头代表原始类型,没有被篡改。红色箭头代表的是引用类型,都随着 newObj 的修改而变化。

    ... 扩展运算符

    它比较特殊,如果要拷贝的对象,第一层是原始类型,则为深拷贝。如果是引用类型,则为浅拷贝,不妨做个小实验:

    var obj = {
      name: 'Nick',
      salary: {
      	high: 1,
        mid: 2,
        low: 3
      }
    }
    
    var newObj = { ...obj }
    
    newObj.name = 'Chen'
    newObj.salary.high = 2
    
    console.log(obj)
    console.log(newObj)
    

    狠狠滴深拷贝

    objname 属性没有被改变,salary 中的 high 被改成了 2。

    所以我们如果想用 ... 扩展运算符完成深拷贝,就得这样操作:

    var obj = {
      name: 'Nick',
      salary: {
      	high: 1,
        mid: 2,
        low: 3
      }
    }
    
    var newObj = {
    	...obj,
      salary: {
      	...obj.salary
      }
    }
    

    JSON.parse + JSON.stringify

    很多有志之士,会在代码中使用这种方式去做深拷贝。当然,多数业务场景中,这种方式还是比较香的,但是还是会有那么些情况,会出现大大小小的问题。

    对象中存在函数:

    var obj = {
      name: 'Nick',
      hobby: ['code', 'movie', 'travel', { a: 1 }],
      callback: function() {
        console.log('test')
      }
    }
    
    var newObj = JSON.parse(JSON.stringify(obj))
    
    newObj.name = 'Chen'
    newObj.hobby[0] = 'codeing'
    newObj.hobby[3].a = 2
    
    console.log('obj', obj)
    console.log('newObj', newObj)
    

    狠狠滴深拷贝

    确实没有被关联到,数据已经脱离了控制,但是函数 callback 么的了。

    对象中存在时间对象 Date

    var obj = {
      name: 'Nick',
      date: [new Date(1621259998866), new Date(1621259998866)],
    };
    
    var newObj = JSON.parse(JSON.stringify(obj))
    

    狠狠滴深拷贝

    obj 中的 date 内的时间对象被执行了。

    对象中存在 RegExp、Error

    var obj = {
      name: 'Nick',
      date: new RegExp('\\s+'),
    };
    
    var newObj = JSON.parse(JSON.stringify(obj));
    obj.name = 'Chen'
    

    狠狠滴深拷贝

    拷贝之后,date 变成了一个空值。

    对象中存在 undefined 值

    var obj = {
    	name: undefiend
    }
    
    var newObj = JSON.parse(JSON.stringify(obj));
    

    狠狠滴深拷贝

    undefiend 在拷贝的过程中,被丢失了。

    对象中存在 NaN、Infinity、-Infinity

    var obj = {
      name1: NaN,
      name2: Infinity,
      name3: -Infinity
    }
    
    var newObj = JSON.parse(JSON.stringify(obj))
    

    狠狠滴深拷贝 直接全部变成 null,不跟你嘻嘻哈哈,但是这种情况应该也不多。

    对象中存在通过构造函数生产的对象

    function Animal(name) {
    	this.name = name
    }
    
    var animal = new Animal('dog')
    
    var obj = {
    	test: animal
    }
    
    var newObj = JSON.parse(JSON.stringify(obj))
    

    狠狠滴深拷贝

    直接就把构造函数给丢了,拷贝之后,直接指向了 Object

    狠狠滴深拷贝

    首先,大可以使用 lodash.cloneDeep 这类工具实现深拷贝,有工具不用,哎,放着玩儿?

    这里我要手动写一个深拷贝,从中可以学习到一些小知识点,爱看不看吧,我写给自己看。

    var obj = {
      name: 'Nick',
      date: [new Date(1621261792177)],
      callback: function() { console.log('shadiao') },
      link: undefined
    }
    
    function deepClone(origin) {
      if(origin === null) return null 
      if(typeof origin !== 'object') return origin;
      if(origin.constructor === Date) return new Date(origin); 
    	// 接受两个参数,origin 是原对象
      var _target = origin.constructor() //保持继承链
      // 循环 origin
    	for(var key in origin) {
        //不遍历其原型链上的属性
        if (origin.hasOwnProperty(key)) {
        	// 如果 origin[key] 是一个引用类型的值,则进入递归逻辑
          if (typeof origin[key] === 'object' && origin[key] !== null) {
            // 进入递归,此时原始值就是 origin[key],被赋值的对象是 _target[key]
            // 注意,上述第一次声明的 _target 将会贯穿整个递归,后续所有的赋值,都将会被 return 到 _target
            _target[key] = deepClone(origin[key])
          } else {
            // 如果不是对象或数组,则进入此逻辑,直接赋值给 _target[key]
            _target[key] = origin[key]
          }
        }
      }
      // for...in 循环结束后,return 当前上下文的 _target 值
      return _target
    }
    
    const newObj = deepClone(obj)
    
    
    

    狠狠滴深拷贝

    上述 obj 对象的属性都被完整的拷贝下来了。

    上述代码中,有一个关键步骤,如果理解了它,基本上你就理解为什么可以实现递归赋值,我们来看下面这段代码:

    function test() {
    	var obj = {}
      const _obj = test1(obj)
      console.log('obj', obj)
      console.log('_obj', _obj)
    	console.log(_obj === obj)
    }
    
    function test1(_obj) {
    	_obj.a = 1
      return _obj
    }
    
    test()
    

    狠狠滴深拷贝

    上述代码,在函数 test 内部声明 obj 对象,并将其以参数的形式,传递给 test1 方法。test1 内部的操作是给传进来的 _obj 参数赋值一个 a 属性,并且 return _obj

    此时查看打印结果,obj 被也被添加了 a 属性,并且 _obj 全等于 obj。这说明它们指向了同一个内存地址,就是 test 内的函数作用域。在《JavaScript 高级程序设计》第 86 页,对引用类型在函数之间的传递的知识有详细的分析。

    狠狠滴深拷贝

    利用这个原理,上述 deepClone 方法内部,执行递归的时候,所传进去的 _target[key] ,其实这个 _target 就是第一次执行 deepClone 的引用类型变量,后续递归操作对 _target[key] 的赋值,都将反映到最初的 _target。最后函数执行结束,return _target 便是最终递归深拷贝后的最终值。

    总结

    这个知识点非常细节,我不敢说会在业务开发中大量用到。但至少当你遇到这类问题的时候,你不会一头雾水、伤春悲秋,觉得自己不适合这个行业。再一次强调,基础知识很重要,不要小看这些平时不起眼的知识,真到了拼刺刀的时候,你一无所知。

    往期好文推荐

    打通任督二脉的前端环境变量 — env 点赞数? 228

    Vite 2.0 + React + Ant Design 4.0 搭建开发环境 点赞数? 385

    面不面试的,你都得懂原型和原型链 点赞数? 593

    Vue 3 和 Webpack 5 来了,手动搭建的知识该更新了 点赞数? 521

    换一个角度分析,网页性能优化 点赞数? 200

    你好,谈谈你对前端路由的理解 点赞数? 625

    以前我没得选,现在我只想用 Array.prototype.reduce 点赞数? 588

    无处不在的发布订阅模式 —— 这次一定 点赞数? 164

    聊聊 JSX 和虚拟 DOM 点赞数? 110


    起源地下载网 » 狠狠滴深拷贝

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元