最近在写ES6的文章的时候发现重复遇到关于javascript深拷贝和浅拷贝的问题,而后查找了一些资料,根据资料和本身的理解作了如下笔记,毕竟javascript关于深拷贝和浅拷贝的问题在一些面试的时候有些面试官可能会进行提问,一块儿来看看吧!javascript
在了解浅拷贝和深拷贝以前,咱们先回顾一下javascript中的数据类型,由于在讲浅拷贝和深拷贝的时候就是就是对原始数据类型(基本数据类型)和对象数据类型(引用数据类型)的拷贝html
在javascript中,咱们将数据类型分为两种,原始数据类型(基本数据类型)和对象类型(引用数据类型)java
基本数据类型的值是按值访问的,基本数据类型的值是不可变的面试
常见的基本数据类型:Number,String,Boolean,Undefined,Nulljson
引用类型的值是按引用访问的,引用类型的值是动态可变的数据结构
常见的引用类型:Object,Function,Array函数
因为数据类型的访问方式不一样,它们的比较方式也是不同的,咱们来看一下下面的示例post
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>深拷贝和浅拷贝入门</title> </head> <body> <script type="text/javascript"> var a=100; var b=100; console.log(a===b);//true var c={a:1,b:2}; var d={a:1,b:2}; console.log(c===d);//false </script> </body> </html>
总结学习
鉴于综上两点咱们大概知道所谓的浅拷贝和深拷贝可能就是对于值的拷贝和引用的拷贝(基本数据类型都是对值的拷贝),在这里主要讲解关于引用类型的拷贝测试
浅拷贝是对象共用一个内存地址,对象的变化相互影响。好比常见的赋值引用就是浅拷贝
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>对象的浅拷贝</title> </head> <body> <script type="text/javascript"> var obj1={name:'cat'}; var obj2=obj1; obj2.name='dog'; console.log(obj1);//{name:'dog'} console.log(obj2);//{name:'dog'} </script> </body> </html>
咱们发现当咱们改变obj2的值的时候obj1的值也会发生改变,这里到底发生了什么,请看图解
当咱们将obj2的值赋值给obj1的时候,仅仅只是将obj2的地址给了obj1而不是obj1从新在内存中开辟空间,因此obj1的地址和obj2的地址指向相同,改变obj2的时候obj1也会发生改变。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>使用循环实现浅拷贝</title> </head> <body> <script type="text/javascript"> var person={ name:'tt', age:18, friends:['aa','bb','cc'] } function shallowCopy(source){ if(!source||typeof source!=='object'){ throw new Error('error'); } var targetObj=source.constructor===Array?[]:{}; for(var keys in source){ if(source.hasOwnProperty(keys)){ targetObj[keys]=source[keys] } } return targetObj; } var p1=shallowCopy(person); console.log(p1);//{name:'tt',age:18,friends:['aa','bb','cc']} </script> </body> </html>
在上面的代码中,咱们建立了shallowCopy函数,它接收一个参数也就是被拷贝的对象,步骤分别是
hasOwnProperty
限制循环只在对象自身,将被拷贝对象的每个属性和值添加到建立的对象当中那么看到这里,咱们发现p1拿到了和person同样的对象,那么p1=person又有什么区别了,咱们看下下面的示例
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>简单对象的浅拷贝</title> </head> <body> <script type="text/javascript"> var person={ name:'tt', age:18, friends:['oo','cc','yy'] } function shallowCopy(source){ if(!source||typeof source!=='object'){ throw new Error('error'); } var targetObj=source.constructor===Array?[]:{}; for(var keys in source){ if(source.hasOwnProperty(keys)){ targetObj[keys]=source[keys] } } return targetObj; } var p1=shallowCopy(person); var p2=person; //这个时候咱们修改person的数据 person.name='tadpole'; person.age=19; person.friends.push('tt'); console.log(p2.name);//tadpole console.log(p2.age);//19 console.log(p2.friends);//['oo','cc','yy','tt'] console.log(p1.name);//tt console.log(p1.age);//18 console.log(p1.friends);//['oo','cc','yy','tt'] </script> </body> </html>
上面建立了一个新变量p2,将person的值赋值给p2,而后比较这两个值
和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 | |
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会是原数据一同改变 |
深拷贝是将对象放到一个新的内存中,两个对象的改变不会相互影响或者你能够理解为浅拷贝因为只是复制一层对象的属性,当遇到有子对象的状况时,子对象就会互相影响。因此,深拷贝是对对象以及对象的全部子对象进行拷贝
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>递归实现深拷贝</title> </head> <body> <script type="text/javascript"> var obj1={ name:'cat', show:function(){ console.log('名称:'+this.name); } } var obj2=deepClone(obj1); obj2.name='pig'; obj1.show();//cat obj2.show();//pig function deepClone(obj){ var objClone=Array.isArray(obj)?[]:{}; if(obj&&typeof obj==='object'){ for(key in obj){ if(obj.hasOwnProperty(key)){ //判断obj子元素是否为对象,若是是,递归复制 if(obj[key]&&typeof obj[key]==='object'){ objClone[key]=deepClone(obj[key]) }else{ //若是不是,简单复制 objClone[key]=obj[key] } } } } return objClone; } </script> </body> </html>
对于深拷贝的对象,改变源对象不会对获得的对象有影响。只是在拷贝的过程当中源对象的方法丢失了,这是由于在序列化 JavaScript
对象时,全部函数和原型成员会被有意忽略
JOSN
对象中的 stringify
能够把一个 js
对象序列化为一个 JSON
字符串,parse
能够把 JSON
字符串反序列化为一个 js
对象,经过这两个方法,也能够实现对象的深拷贝
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>利用 JSON 对象中的 parse 和 stringify</title> </head> <body> <script type="text/javascript"> var obj1={ name:'cat', show:function(){ console.log(this.name); } } var obj2=JSON.parse(JSON.stringify(obj1)); obj2.name='dog'; console.log(obj1.name);//cat console.log(obj2.name);//dog obj1.show();//cat obj2.show();//TypeError: obj2.show is not a function </script> </body> </html>
注意:JSON.parse()和JSON.stringify()能正确处理的对象只有Number、String、Array等可以被json表示的数据结构,所以函数这种不能被json表示的类型将不能被正确处理,通过转换以后,function丢失了,所以JSON.parse()和JSON.stringify()仍是须要谨慎使用
这种方法我在javascript学习总结之Object.assign()方法详解有过讲解,可是我在看资料的时候有发现了一点点的问题,因此在这里补充一下
定义:Object.assign() 方法用于将全部可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>使用Object.assgin()方法</title> </head> <body> <script type="text/javascript"> let srcObj = {'name': 'lilei', 'age': '20'}; let copyObj2 = Object.assign({}, srcObj, {'age': '21'}); copyObj2.age = '23'; console.log(srcObj);//{name:'lilei',age:20} console.log(copyObj2);//{name:'lilei',age:23} </script> </body> </html>
看起来好像是深拷贝了,那其实这里let copyObj2 = Object.assign({}, srcObj, {'age': '21'});
咱们把srcObj 给了一个新的空对象。一样目标对象为 {},咱们再来测试下
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>使用Object.assgin()方法</title> </head> <body> <script type="text/javascript"> let srcObj = {'name': 'lilei', 'age': '20'}; let copyObj2 = Object.assign({}, srcObj, {'age': '21'}); copyObj2.age = '23'; console.log(srcObj);//{name:'lilei',age:20} console.log(copyObj2);//{name:'lilei',age:23} srcObj = {'name': '明', grade: {'chi': '50', 'eng': '50'} }; copyObj2 = Object.assign({}, srcObj); copyObj2.name = '红'; copyObj2.grade.chi = '60'; console.log(srcObj);//{name:'红',grade:{chi:60,eng:50}} </script> </body> </html>
从例子中能够看出,改变复制对象的name 和 grade.chi ,源对象的name没有变化,可是grade.chi却被改变了。所以咱们能够看出Object.assign()拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
也就是说,对于Object.assign()而言, 若是对象的属性值为简单类型(string, number),经过Object.assign({},srcObj);
获得的新对象为‘深拷贝’;若是属性值为对象或其它引用类型,那对于这个对象而言实际上是浅拷贝的。这是Object.assign()特别值得注意的地方,补充一句,Object.assig({},src1,src2) 对于scr1和src2之间相同的属性是直接覆盖的,若是属性值为对象,是不会对对象之间的属性进行合并的。
本篇博客主要讲解了数据类型,浅拷贝的实现方式,深拷贝的实现方式,从数据类型的讲解中一步一步引入到关于浅拷贝和深拷贝的实现方式,在这里咱们必须学会关于递归实现深拷贝的实现方式,这个有可能在面试的时候会实现手写代码。