查阅资料,看到有关深浅拷贝上面的误区,有人说数组的slice()与concat()方法实现的是深拷贝。对此我做了一些尝试并详细理了一下关于js的深浅拷贝问题。首先我们要知道数据类型的存储方式——
基本类型和引用类型
js中变量分为两类:
基本类型:undefined,null,字符串,数值,布尔
引用类型:统称为object。具体的有Object,Array,Function等
重点是这两种类型的存储方式了:基本类型的数据是存放在栈内存中的,而引用类型的数据是存放在堆内存中的。
基本数据类型,是这个样子的:
引用类型保存在堆中,栈内的是变量的标识符以及对象在堆内存中的存储地址,当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从对应的堆内存中取得所需的数据。
所以对于这两种类型的赋值是有不同的:
当你在复制基本类型的时候,相当于把值也一并复制给了新的变量。修改值的时候也不会影响另一个变量的值。
而在复制引用类型的时候,实际上只是复制了指向堆内存的地址,即原来的变量与复制的新变量指向了同一个东西。
例1.改变a,会影响b
var a = {name:"peri",age:20}; var b = a; console.log(a === b); a.age = 30; console.log(a); console.log(b);
深浅拷贝
对于仅仅是复制了引用(地址),即原来的变量和新的变量指向同一个地址,彼此之间的操作会互相影响,为浅拷贝。
而如果是在堆中重新分配内存,拥有不同的地址,复制后的对象与原来的对象完全隔离,互不影响,为深拷贝。
深浅拷贝的主要区别就是:复制的是引用(地址)还是复制的是实例。
深拷贝的实现
1.递归
对于例1,我们可以通过递归的方式来实现深拷贝,对引用类型进行遍历,一直到是基本类型为止。
function deepClone(source){ if(!source && typeof source !== 'object'){ throw new Error('error arguments', 'shallowClone'); } var targetObj = Array.isArray(source) ? [] : {}; for(var keys in source){ //只遍历对象自身的属性,而不包含继承于原型链上的属性。 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ targetObj[keys] = deepClone(source[keys]); //递归 }else{ targetObj[keys] = source[keys]; } } } return targetObj; } var a = {name:"peri",age:20}; var b = deepClone(a); console.log(a === b); a.age = 18; console.log(a); console.log(b);
2.JSON 对象的 parse 和 stringify
stringify把一个 js 对象序列化为一个 JSON 字符串。parse把 JSON 字符串反序列化为一个 js 对象,这两个方法实现的是深拷贝。
var obj = {name:'pp',age:24,company : { name : 'AAA', address : '深圳'} }; var obj_json = JSON.parse(JSON.stringify(obj)); console.log(obj === obj_json); obj.company.name = "BBB"; obj.name = "peri"; console.log(obj); console.log(obj_json);
3.jQuery中的 extend方法
jQuery的$.extend方法是我们在开发中经常用到的方法,用于合并若干个对象。
可用于深浅拷贝,第一个参数为true深拷贝。为false或者没有为浅拷贝。
例2.深拷贝
var obj = {name:'pp',age:24,company : { name : 'AAA', address : '深圳'} }; var obj_json = $.extend(true, {}, obj); console.log(obj === obj_json); obj.company.name = "BBB"; obj.name = "peri"; console.log(obj); console.log(obj_json);
例2.2浅拷贝
slice()与concat()方法
最后再来讨论一下最初关于数组的slice()与concat()方法是否深拷贝。
例3.slice()方法
var a = [1,2,3]; var b = a.slice(); console.log(b === a); a[0] = 4; console.log(a); console.log(b);
可以看到,改变a并没有影响b!那么就说明slice()是深拷贝了吗?没有那么简单!
例3.1
var a = [[1,2,3],4,5]; var b = a.slice(); console.log(a === b); a[0][0] = 6; console.log(a); console.log(b);
这很明显说明slice()不是深拷贝!
原来Array 的 slice() 和 concat ()方法,对于第一层的值都是深拷贝,而到第二层的时候 Array的slice() 和 concat()方法就是复制引用 。(concat与slice结果相同,这里不再举例)。