1.浅复制VS深复制
本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思。另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,undefined,string,number和boolean),这些类型的值本身就存储在栈内存中(string类型的实际值还是存储在堆内存中的,但是js把string当做基本类型来处理 ),不存在引用值的情况。
浅复制和深复制都可以实现在已有对象的基础上再生一份的作用,但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此复制的时候就存在两种情况了:复制引用和复制实例,这也是浅复制和深复制的区别所在。
浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响。
深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复制的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的。
- 由深复制的定义来看,深复制要求如果源对象存在对象属性,那么需要进行递归复制,从而保证复制的对象与源对象完全隔离。
- 然而还有一种可以说处在浅复制和深复制的粒度之间,也是jQuery的extend方法在deep参数为false时所谓的“浅复制”,这种复制只进行一个层级的复制:即如果源对象中存在对象属性,那么复制的对象上也会引用相同的对象。这不符合深复制的要求,但又比简单的复制引用的复制粒度有了加深。
浅复制
本文认为浅复制就是简单的引用复制,这种情况较很简单,通过如下代码简单理解一下:
var src = { name:"src" } //复制一份src对象的应用
var target = src; target.name = "target";
console.log(src.name); //输出target
target对象只是src对象的引用值的复制,因此target的改变也会影响src。
深复制
深复制的情况比较复杂一些,我们先从一些比较简单的情况说起:
1. Array的slice和concat方法
Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用,因此这两个方法并不是真正的深复制,通过以下代码进行理解:
var array = [1,2,3];
var array_shallow = array;
var array_concat = array.concat();
var array_slice = array.slice(0);
console.log(array === array_shallow); //true
console.log(array === array_slice); //false
console.log(array === array_concat); //false
可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。
var array = [1, [1,2,3], {name:"array"}];
var array_concat = array.concat();
var array_slice = array.slice(0);
//改变array_concat中数组元素的值
array_concat[1][0] = 5;
console.log(array[1]); //[5,2,3]
console.log(array_slice[1]); //[5,2,3]
//改变array_slice中对象元素的值
array_slice[2].name = "array_slice";
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice
通过代码的输出可以看出concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用。里面的对象属性是共享的。
2 JSON对象的parse和stringify
JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。
var source = { name:"source", child:{ name:"child" } }
var target = JSON.parse(JSON.stringify(source));
target.name = "target"; //改变target的name属性
console.log(source.name); //source
console.log(target.name); //target
target.child.name = "target child"; //改变target的child
console.log(source.child.name); //child
console.log(target.child.name); //target child
从代码的输出可以看出,复制后的target与source是完全隔离的,二者不会相互影响。
总结: 这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理。
3 jQuery中的extend复制方法
jQuery中的extend方法可以用来扩展对象,这个方法可以传入一个参数:deep(true or false),表示是否执行深复制(如果是深复制则会执行递归复制),我们首先看一下jquery中的源码(1.9.1)
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we‘re merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don‘t bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
这个方法是jQuery中重要的基础方法之一,可以用来扩展jQuery对象及其原型,也是我们编写jQuery插件的关键方法,事实上这个方法基本的思路就是如果碰到array或者object的属性,那么就执行递归复制,这也导致对于Date,Function等引用类型,jQuery的extend也无法支持。
4 Object.assign
5 Object.create
6 自己实现的深复制兼容方法
var cloneObj = function(obj){
var str, newobj = obj.constructor === Array ? [] : {};
if(typeof obj !== 'object'){
return;
} else if(window.JSON){
newobj = JSON.parse(JSON.stringify(obj)), //序列化还原对象
} else {
for(var i in obj){
newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i];
}
}
return newobj;
};
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
if (obj instanceof Object) {
var copy = obj instanceof Array ? [] : {};
for (var key in obj) { //递归
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
}
//测试代码:
var obj = {
"title": "heihei",
"authors": [
"haha",
"heigei"
],
edition: {
"wei": "wei123",
"hong": "123"
},
year: 2011,
releaseDate: new Date(2017, 08, 13),
};
var obj1 = deepClone(obj);
obj1.authors.push("shuzu");
console.log(obj);
console.log(obj1);
var obj2 = deepClone(null);
var obj3 = deepClone(undefined);
console.log(obj2); //null
console.log(obj3); //undefined
链接:http://www.imooc.com/article/11253
https://www.zhihu.com/question/23031215/answer/158703216