什么是浅拷贝和深拷贝
浅拷贝
浅拷贝:将一个对象自身的属性拷贝给另一个对象,如果源对象的属性是基本类型则直接进行值赋值,如果是引用类型则进行引用赋值,也就是说只进行一层赋值。
深拷贝
深拷贝:将一个对象自身的属性拷贝给另一个对象,如果源对象的属性是基本类型则直接进行值赋值,如果是引用类型则复制这个引用类型,使得目标对象拥有一个引用类型且和这个源属性一模一样,而非是一个指针。
也就是说,深拷贝与浅拷贝最主要的区别在引用类型的拷贝上。
注意,引用赋值不是浅拷贝!! 引用赋值仅仅只是赋值个指针,两个变量都指向同一内存区域,而浅拷贝是使得两个变量分别指向不同的内存区域
如果不懂,可以参考这里 一个小姐姐的博客
以上不是重点...
浅拷贝的实现
方法一 使用 for in 遍历
function shallowCopy(source){
var target=source instanceof Array ? [] : {};
for(var i in source){
if(source.hasOwnProperty(i)){
target[i]=source[i];
}
}
return target;
}
// 测试
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar=shallowCopy(obj)
tar.c() // "i am c"
obj.a=5
obj.a // 5
tar.a // 1
obj.b[0]=10
obj.b // [10, 2, 3]
tar.b // [10, 2, 3]
var arr=[1,2,[4,5,6]]
var newArr=shallowCopy(arr)
newArr // [1, 2, [4,5,6]]
arr[0]=10
arr // [10, 2, [4,5,6]]
newArr // [1, 2, [4,5,6]]
arr[2][0]=10
arr // [1, 2, [10,5,6]]
newArr // [1, 2, [10,5,6]]
方法二 使用 Object.assign 或 slice、concat
Object.assign
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar={};
Object.assign(tar,obj);
当然这个方法只适合于对象类型,如果是数组可以使用slice和concat方法
Array.prototype.slice
var arr=[1,2,[3,4]];
var newArr=arr.slice(0);
Array.prototype.concat
var arr=[1,2,[3,4]];
var newArr=arr.concat();
测试同上(assign用对象测试、slice concat用数组测试),结合浅拷贝深拷贝的概念来理解效果更佳
深拷贝的实现
方法一 JSON黑科技
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar=JSON.parse(JSON.stringify(obj));
// 测试
obj.a=5
obj.a // 5
tar.a // 1
obj.b[0]=10
obj.b // [10, 2, 3]
tar.b // [1, 2, 3]
tar.c() // Uncaught TypeError: tar.c is not a function
可以看到,无论是基本类型还是引用类型,两个对象的相同属性(属性名相同的属性)之间并没有关系了,但tar.c()
报错了,我们打印看一下tar有什么console.log(tar) // {a: 1, b: Array(3)}
可以看到c方法没了,这是和JSON的语法有关,JSON 并不支持函数类型的数据。这也就是这种方法的最大缺陷。
仔细一看,这并非黑科技,反而倒是有很大缺陷,不过很好用、效率高,只是在使用前需要稍加注意是否有函数类型的数据罢了。
方法二 递归拷贝
深拷贝与浅拷贝相比不就是多拷贝几层的事嘛,这不就是递归常干的事嘛。所以我们就想,在每次拷贝时都判断下,该属性是否是引用类型,如果是我们再递归调用拷贝方法,否则直接进行值赋值。
function deepCopy(obj,tar){
var tar = tar || {};
for(var i in obj){
if(typeof obj[i] === 'object'){
if(obj[i].constructor === Array){
tar[i] =[];
}else{
tar[i] = {};
}
deepCopy(obj[i],tar[i]);
}
else{
tar[i] = obj[i];
}
}
return tar;
}
// 使用
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}};
var tar={};
deepCopy(obj,tar);
console.log(tar);
测试同上,你会惊喜的发现第一个方法中的函数bug没了
你看出来函数是怎样进行拷贝的了吗?很简单,typeof
运算符的操作对象是一个函数时,得到的是 "function"
所以在循环里第一个if判断那为false 所以走else分支,在tar[i] = obj[i]
这里,函数是进行引用赋值的,如果再造一个相同的函数不是不可以,只是不符合思想罢了,函数占用堆内存,如果可以共用当然是最好的选择。
当然这个方法还是有些许不足之处,不够已经很棒了
方法三 更严谨、更优雅的实现
这就需要去读一些好的源码了,比如Zepto、JQuery中extend方法的实现
现学现卖,如有不足请指出...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。