JS对象的深拷贝和浅拷贝小结

 

零、前言

1 浅拷贝定义

2 浅拷贝例子以及相关实现

1 深拷贝定义

2 深拷贝例子以及相关实现

 

一、浅拷贝定义

1 浅拷贝定义(存在于第一层且是基本类型值的,直接赋值 即传值,但 为引用类型的话 是直接 复制堆地址,即 “传址”!!!)

浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制,也就是说只会赋值目标对象的第一层属性。

对于目标对象第一层为基本数据类型的数据,就是直接赋值,即「传值」; 而对于目标对象第一层为引用数据类型的数据,就是直接赋存于栈内存中的堆内存地址,即「传址」。

 

二、浅拷贝例子以及相关实现

1 最简单的一种浅拷贝 (let obj2 = obj1;) 。直接赋值!!!

2 Object.assign(第一层的是“深拷贝”,深层的是“浅拷贝”)

语法:Object.assign(target, ...sources)

let obj1 = {a:1, b: {c: 1}};
let obj2 = {};
Object.assign(obj2, obj1);

obj2.a = 'a新值'
obj1.b.c = 'c新值';
console.log(obj1, obj2);

 

运行结果结果如下(a不变,但是里面的 c 变了!!!)

 

2 扩展运算符 ... 实现的浅拷,如 let cloneObj = { ...obj }【跟 Object.assign() 类似,“浅层深复制,深层浅复制”】

语法:let cloneObj = { ...obj };

let obj = {a:1,b:{c:1}}
let obj2 = {...obj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

 

三、深拷贝定义(核心:递归拷贝 目标对象的所有属性 !!!)

1  深拷贝不同于浅拷贝,它不只拷贝目标对象的第一层属性,而是递归拷贝目标对象的所有属性。

一般来说,在JavaScript中考虑复合类型的深层复制的时候,往往就是指对于 DateObjectArray 这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。

不过这种方法会存在一个问题,就是 JavaScript 中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后需要考虑原型应不应该被赋予给新对象。那么在遍历的过程中,我们可以考虑使用 hasOwnProperty 方法来判断是否过滤掉那些继承自原型链上的属性。

 

四、深拷贝例子以及相关实现

1 一个最简单的深拷贝(核心:手动“赋值完所有的属性”)

let obj1 = {
    a: {
        b: 1
    },
    c: 1
};
let obj2 = {};

obj2.a = {}
obj2.c = obj1.c
obj2.a.b = obj1.a.b;
console.log(obj1); //{a:{b:1},c:1};
console.log(obj2); //{a:{b:1},c:1};
obj1.a.b = 2;
console.log(obj1); //{a:{b:2},c:1};
console.log(obj2); //{a:{b:1},c:1};

2 JSON.stringify | parse【核心:JSON.stringify 序列化 与 反序列化 parse !!】

JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象

let obj1 = {
    a:1,
    b:[1,2,3]
}
let str = JSON.stringify(obj1)
let obj2 = JSON.parse(str)
console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a = 2
obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}

注意:(JSON去深拷贝需要注意的点。很多属性值可能变成了 null )

  • 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
  • 无法拷贝不可枚举的属性,无法拷贝对象的原型链
  • 拷贝Date引用类型会变成字符串
  • 拷贝RegExp引用类型会变成空对象
  • 对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
  • 无法拷贝对象的循环应用(即obj[key] = obj)

3 递归的深拷贝自我实现(手撕)


function deepClone(obj) {
  let new_obj = {};

  for(let key in obj){
    if(obj[key] instanceof Object){ // 当前遍历的属性值 是对象时
      new_obj[key] = deepClone(obj[key]);
    }else { // 当前遍历的属性值 是基本类型值时
      new_obj[key] = obj[key];
    }
  }
  return new_obj;
}

let obj1 = {
  a:{
    b:1
  }
};
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2); //{a:{b:1}}

注意:(这个仍然存在较多的问题!!!)

  • 首先这个deepClone函数并不能复制不可枚举的属性以及Symbol类型
  • 这里只是针对Object引用类型的值做的循环迭代,而对于Array,Date,RegExp,Error,Function引用类型无法正确拷贝
  • 对象成环,即循环引用 (例如:obj1.a = obj)

4 采用第三方库( jquery 和 lodash )

1 jquery 有提供一个$.extend可以用来做 Deep Copy。

var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);
// false

2 另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

 

五、总结

  • 封装的deepClone方法虽然能实现对ECMAScript原生引用类型的拷贝,但是对于对象来说范围太广了,仍有很多无法准确拷贝的(比如DOM节点),但是在日常开发中一般并不需要拷贝很多特殊的引用类型,深拷贝对象使用JSON.stringify依然是最方便的方法之一(当然也需要了解JSON.stringify的缺点)

  • 实现一个完整的深拷贝是非常复杂的,需要考虑到很多边界情况,这里我也只是对部分的原生的构造函数进行了深拷贝,对于特殊的引用类型有拷贝需求的话,建议还是借助第三方完整的库

  • 对于深入研究深拷贝的原理有助于理解JavaScript引用类型的特点,以及遇到相关特殊的问题也能迎刃而解,对于提高JavaScript的基础还是很有帮助的~~~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值