工做中会常常遇到操做数组、对象的状况,你确定会将原数组、对象进行‘备份’
当真正对其操做时发现备份的也发生改变,此时你一脸懵逼,到时是为啥,不是已经备份了么,怎么备份的数组、对象也会发生变化。
若是你对拷贝原理理解的不透彻,此文或许能提供一点帮助。javascript
string、number、null、undefined、boolean、symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值
基本数据类型不存在拷贝,比如如说你没法修改数值1的值java
Object Function RegExp Math Date 值为对象,存放在堆内存中
在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址。
当访问引用类型的时候,要先从栈中取出该对象的地址指针,而后再从堆内存中取得所需的数据git
为何备份的数组对象也会发生变化,这里就涉及到你用的‘备份’实际上是一种浅拷贝github
var a = [1,2,3,4]; var b = a; a[0] = 0; console.log(a,b);
能够看到数组a直接赋值给b,a、b引用的实际上是一个对象地址,只要地址值发生变化,a、b栈内存指针指向的堆地址也会发生变化,这种引用拷贝只是新增了一个变量栈内存的指针,意义不大数组
一样的例子浏览器
var a = [1,2,3,4]; var b = a.concat(); a[0] = 0; console.log(a,b);
此时数组a[0]值变成0,b数组依然保持不变,有同窗就问了,这不就是深拷贝吗。函数
对,也不对, Array.prototype.slice 和 Array.prototype.concat 看似深拷贝,其实质上仍是浅拷贝测试
var a = [1,2,[3,4],{name:'ccy'}]; var b = a.concat(); a[3].name = 'hs'; console.log(a[3],b[3]);
当数组a中包含对象时, Array.prototype.slice 和 Array.prototype.cancat 拷贝出来数组中的对象仍是共享同一内存地址,因此本质上归属浅拷贝优化
Object.assign 原理也是同样的(对于对象的属性都为基本类型能够当成深拷贝)spa
var a = {age:18,name:'ccy',info:{address:'wuhan',interest:'playCards'}}; var b = Object.assign(a); a.info.address = 'shenzhen'; console.log(a.info,b.info);
那怎样才能对对象进行深拷贝呢,请扶好眼镜。
自定义深拷贝函数
var clone = function(obj){ var construct = Object.prototype.toString.call(obj).slice(8,-1); var res; if(construct === 'Array'){ res = []; }else if(construct === 'Object'){ res = {} } for(var item in obj){ if(typeof obj[item] === 'object'){ res[item] = clone(obj[item]); }else{ res[item] = obj[item]; } } return res; };
乍一看好像能处理对象的属性为对象的问题,能够循环遍历直至属性为基本类型;
可是仔细想,若是遇到对象的属性存在相互引用的话会出现死循环的状况。能够再加一次判断,对象的属性若是引用对象指针则跳出当前循环。
深拷贝是能够完美的解决浅拷贝的弊端,从新开辟一块地址,深拷贝出来的属性的基本类型值都是相同的。
JSON 对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法能够将JSON字符串反序列化成JS对象,stringify方法能够将JS对象序列化成JSON字符串,借助这两个方法,也能够实现对象的深拷贝
var a = {age:1,name:'ccy',info:{address:'wuhan',interest:'playCards'}}; var b = JSON.parse(JSON.stringify(a)); a.info.address = 'shenzhen'; console.log(a.info,b.info);
JSON 可处理通常的对象进行深拷贝,可是不能处理函数、正则等对象
咱们能够对自定义的拷贝函数再进行优化
var clone = function(obj){ function getType(obj){ return Object.prototype.toString.call(obj).slice(8,-1); } function getReg(a){ var c = a.lastIndexOf('/'); var reg = a.substring(1,c); var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', '\w': '\\w', '\s': '\\s', '\d': '\\d'}; for(var i in escMap){ if(reg.indexOf(i)){ reg.replace(i,escMap[i]); } } var attr = a.substring(c+1); return new RegExp(reg, attr); } var construct = getType(obj); var res; if(construct === 'Array'){ res = []; }else if(construct === 'Object'){ res = {} } for(var item in obj){ if(obj[item] === obj) continue;//存在引用则跳出当前循环 if(getType(obj[item]) === 'Function'){ res[item] = new Function("return "+obj[item].toString())(); }else if(getType(obj[item]) === 'RegExp'){ res[item] = getReg(obj[item].toString()); }else if(getType(obj[item]) === 'Object'){ res[item] = clone(obj[item]); }else{ res[item] = obj[item]; } } return res; };
基本能够实现函数、正则对象的深拷贝,在本地只作了简单的测试,若是存在问题,请及时评论指出。
固然像函数库 lodash 的 _.cloneDeep、 JQuery 的 $.extend都实现了深拷贝,有兴趣的同窗可自行看下源码。
https://developer.mozilla.org/zh-CN/search?q=%E6%B7%B1%E6%8B%B7%E8%B4%9D