ITPub博客

首页 > > > javascript之深浅拷贝

javascript之深浅拷贝

作者:zzzhangyi 时间:2019-11-27 15:45:51 0 删除 编辑

开笔前的那些事

关于深拷贝和浅拷贝,也是面试中经常被问到的一个高频题目。然而最近在使用redux的过程中,才发现对这一方面的理解不是很到位。

事情来源是这样的:当我在reducer中更新state时,发现initstate也被修改了,被修改的部分是一个数组,基本类型并没有被修改。

举个栗子:

constinitstate = {id:'',list: []}复制代码

当我将接口返回的数据在reducer中进行以下操作时,发现初始值被修改了。

constlistState =(state = initstate, data) =>{constnewState =Object.assign({}, state, data);returnnewState;}复制代码

初步猜想是Object.assign({}, obj);是浅拷贝,于是去网上查阅一番后,证实了猜想。于是整理了一下关于实现深拷贝和浅拷贝的一些方法。

从概念开始吧

关于浅拷贝和深拷贝,区别在于引用类型的拷贝。除了基本类型会直接拷贝属性值外,引用类型在拷贝中是存在两种情况,拷贝实例和拷贝引用,前者就是浅拷贝,后者则属于是深拷贝。

varsource= { num: 123, str:'1234', arr: ['zhangsan','lisi','wangwu'], obj: { age: 12, name:'zhuanzhuan'} };var target =source;target.arr[0] ='张三';target.obj.name ='转转';console.log(target ===source);console.log('---target', target);console.log('---source',source);复制代码

代码中直接将源对象赋值给目标对象,并修改目标对象的数组和对象的某一元素和属性值。下面是打印的日志:

从打印的日志中可以看到,赋值的过程只是将目标对象的指针指向源对象的内存地址,所以当修改目标对象的数组的某一元素或者对象的某一属性值时,源对象也会被修改。这就是所谓的浅拷贝。

那么相反的,深拷贝就是不让目标对象的指针指向源对象的内存地址,而是新开一块内存地址,并且将源对象的数据复制一份到目标对象,两个对象的指针指向不同,那么修改一个时将不会影响另一个。

上面的陈述都是一些最基本的概念,同理就可以知道Object.assign({}, obj);也只是修改了一下指针的指向而没有新开一块内存,那么为什么呢?

浅拷贝

Object.assign({}, obj);

根据MDN中的描述:

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

varsource= { num: 123, str:'1234', arr: ['zhangsan','lisi','wangwu'], obj: { age: 12, name:'zhuanzhuan'} };var target = Object.assign({},source);target.arr[0] ='张三';target.obj.name ='转转';console.log(target ===source);console.log('---source',source);复制代码

所以当我们使用Object.assign({}, obj);拷贝一个对象时,遇到引用类型的属性值,也只是拷贝引用指针,而不是开辟一块新内存,返回的是一个新对象。

解构赋值

浅拷贝中除了直接将对象赋值给一个变量,以及上面说明的Object.assign();方法外,ES6中还提供了一种直接操作对象的方式,就是解构赋值。

varsource= { num: 123, str:'1234', arr: ['zhangsan','lisi','wangwu'], obj: { age: 12, name:'zhuanzhuan'} };var target = {...source, newNum:'456'};target.arr[0] ='张三';target.obj.name ='转转';console.log(target ===source);console.log('---source',source);console.log('---target', target);复制代码

所以解构赋值也是浅拷贝,如果一个键的值是复合类型的值(数组、对象、函数),那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

深拷贝

JSON.parse(JSON.stringify(obj));

说起深拷贝,首先放第一位应该是深拷贝中的黑科技,首先用下面的代码展示一下这一行代码的威力之处:

varsource= { num: 123, str:'1234', arr: ['zhangsan','lisi','wangwu'], obj: { age: 12, name:'zhuanzhuan'}}var target = JSON.parse(JSON.stringify(source));target.arr[0] ='张三';target.obj.name ='转转';console.log(target ===source);console.log('---source',source);复制代码

从结果日志中可以看到,当修改目标对象的某属性值时,源对象并未被影响到。源对象和目标对象也不是指向了同一内存地址,实现了基本的深拷贝。为什么JSON.parse()和JSON.stringify()可以实现深拷贝呢?

stringify()方法可以将一个JS对象序列化一个JSON字符串,parse()方法可以将JSON字符串反序列化为一个JS对象。JSON字符串转化为JS对象时会建一个新对象。

那么问题就来了,如果源对象的属性值是一个Function对象或者undefined 或者null时,会怎么样?

varsource= { num: 123, str:'1234', arr: ['zhangsan','lisi','wangwu'], obj: { age: 12, name:'zhuanzhuan'}, fn:function() {return'fn'}, name: undefined, age: null};var target = JSON.parse(JSON.stringify(source));target.arr[0] ='张三';target.obj.name ='转转';console.log(target ===source);console.log('---source',source);console.log('---target', target);复制代码

从打印的日志中可以看到,原对象的方法和undefined类型的键值在拷贝的过程中丢失了,原因是在序列化JS对象的过程中,所有的函数和undefined类型会被有意的忽略。

通常这种方式也用在请求后端接口时需要过滤掉undefined类型值的时候。

jQuery.extend()

jQuery.extend( [deep ], target, object1 [, objectN ] ),其中deep为Boolean类型,如果是true,则进行深拷贝。以下面两段代码为例:

varsource= { num: 123, str:'1234', arr: ['zhangsan','lisi','wangwu'], obj: { age: 12, name:'zhuanzhuan'}, fn:function() {return'fn'}};var target = $.extend(true,source); // 深拷贝var target = $.extend(source); // 浅拷贝复制代码

总结

浅拷贝的方法:

直接赋值

解构赋值

Object.assign({}, sourceObj)

jQuery.extend(sourceObj)

深拷贝的方法:

JSON.prase(JSON.stringify(obj))

jQuery.extend(true, sourceObj)

or 自己实现一个吧~haha

作者:PQKeepCalm

链接:https://juejin.im/post/5ddbce32f265da7e12197c9d

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/3598/viewspace-3130227/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-08-14

  • 博文量
    133
  • 访问量
    947