理解深浅拷贝,首先要明白两种赋值方式: 传值赋值和引用赋值,而传值赋值和引用赋值的区别就是“值”的类型,传值赋值中的值是基本类型,包括六种:string, number, bool, null, undefined, symbol;引用赋值中的值是对象,好比function, object, array等等,而要理解传递赋值和引用赋值的区别,就不得不说下javascript中的堆(heap)和栈(stack)。javascript
js中内存主要分为栈内存和堆内存,一般栈内存存放的是对象的地址,而堆内存存放的是对象的具体内容。对于基本类型来讲,其地址和内容都存在栈内存中,而引用类型其地址存在栈内存中,其内容则存在堆内存中。java
栈内存和堆内存区别。栈内存运行效率比较高,空间相对比较小,堆内存则相反。因此将构造简单的基本类型放在栈内存中,而将复杂的引用类型放在堆内存中。数组
咱们知道了不一样的数据类型存储在不一样的内存区域,那么变量名是如何和该内存地址作一一关联的呢?任何高级语言都有编译的过程,而这个编译的过程就会创建变量名和内存地址的一一对应关系,变量名只是方便咱们使用而已,在编译后,计算机实际使用的是内存地址在操做。(大概原理,编译原理也都忘了)函数
结合上面的堆栈举个栗子code
基本类型对象
var a = "a"; //基本类型,为a分配栈内存地址和存储内容 var b = a; //基本类型,为b分配新的栈内存地址和内容 a = "a1"; //a对应内存地址内容修改成'a1',b对应的内存地址内容不变 console.log(a, b); //a1, a
引用类型递归
var a = {name: 'a'}; //引用类型,为a分配栈内存地址,为对象{name: 'a'}分配堆内存空间,并将内存地址做为a地址的内容 var b = a; //引用类型,为b分配栈内存空间,将a中对象引用的地址存到b地址中 a.name = 'a1'; //根据a用存放的地址引用,修改堆内存中对象的属性 console.log(a.name, b.name, a===b); //此时a和b仍旧同时引用同一个对象, ===直接对比的内存地址
深拷贝和浅拷贝不一样的地方,就是对对象属性操做的不一样。简单来讲,浅拷贝,新生成对象对象属性是源对象的引用,共同指向同一块堆内存;深拷贝,新生成的对象属性是一个新对象,里面属性和源对象中的一致。图片
在操做层面,浅拷贝,只须要拷贝源对象的第一层属性;而深拷贝则不断递归遍历源对象中的对象属性,遇到对象则先新建一个对象,并将源对象中基本类型中赋值给新建对象。ip
举个栗子内存
浅拷贝
var a = {name: 'a', toys: ['dog', 'cat']}; function simpleClone(obj) { var _obj = {}; for(var p in obj) { _obj[p] = obj[p]; //只拷贝第一层 } return _obj; } var b = simpleClone(a); a.toys.push('bird'); console.log(a.toys, b.toys, a.toys===b.toys); //["dog", "cat", "bird"] ["dog", "cat", "bird"] true
从上面能够看出修改了a以后,b也随之改动,说明在浅拷贝中,对象属性是在目标和源之间是同一个引用。
深拷贝
var a = {name: 'a', toys: ['dog', 'cat']}; function simpleDeepClone(obj) { var _obj = {}; for (var p in obj) { //对象类型,递归处理,其余对象类型暂不处理 if(obj[p].constructor === object) { _obj[p] = simpleDeepClone(obj[p]) } else { //基本类型 _obj[p] = ojb[p] } } return _obj }
从上面能够看出深拷贝中对于对象属性总会有一个new的过程,因此新生成的属性和源属性再也不是同一个引用。
浅拷贝
function baseClone(value) { if (!isObject(value)) { //非object和function直接返回,基本数据类型直接返回 return value; } if (isArr) {//数组对象 result = initCloneArray(value); //调用该数组对象的构造函数,构造出等长的新空数组 if (!isDeep) { return copyArray(value, result);//遍历源数组值,依次赋值给新的数组 } } if (tag == objectTag || tag == argsTag || (isFunc && !object)) { result = initCloneObject(isFunc ? {} : value); //函数对象都返回空对象 if (!isDeep) { /**baseAssign就是遍历源对象属性依次赋值给上面新建立的空对象,因为symbol类型做为属性的特殊性,它不能被for...in,for...of,Object.keys(),Object.getOwnPropertyNames()返回,只能经过Object.getOwnPropertySymbols这个特殊获取,因此须要再次单独处理一下**/ return copySymbols(value, baseAssign(result, value)); } } } }
因此lodash浅拷贝的整个流程 1)若是源为基本类型直接赋值(包括symbol),数组对象建立一个内容同样的新数组 2)若是源为对象且原型为object或function,则依次变量其属性,将其赋值给新建立的空对象(这个过程当中基本类型属于直接赋值,对象属性则属于引用赋值)
深拷贝
深拷贝就是遍历执行浅拷贝的流程(浅拷贝遇到对象会生成一个新对象,深拷贝的属性若是是对象,就将该对象做为浅拷贝的源,依次遍历。)
函数参数都是传值赋值,不管对象与否
不改变原数组方法
var a = [{name: 'a'}, 1,2,3] var b = a.slice(0, 2); console.log(a,b) //[ { name: 'a' }, 1, 2, 3 ] [ { name: 'a' }, 1 ] b[0].name = 'b'; console.log(a,b)//[ { name: 'b' }, 1, 2, 3 ] [ { name: 'b' }, 1 ] var c = a.map(function(key) { if(typeof key == 'object') { return key.name = 'c' }else { return key } }) console.log(c, a)//[ { name: 'c' }, 1, 2, 3 ] [ { name: 'c' }, 1, 2, 3 ]