对象拷贝是在js中最基本的对象操作。
浅拷贝
function sallowCopy(source) {
// source 不是对象,而是【原始类型】的情况
// 原始类型说明详见http://www.jianshu.com/p/b161aeecb6d6
if (null == source || "object" != typeof source) return source;
// 其他情况都将 source 当作简单对象来处理
var target = {};
for (var key in source) {
if (source.hasOwnProperty(key)) { // 仅拷贝自身的属性
target[key] = source[key];
}
}
return target;
}
/*
这个浅拷贝会将source对象上的所有[可枚举属性](http://www.jianshu.com/p/7b8da1db32b3)都拷贝到target对象上,不包括原型链上的属性。
*/
浅复制仅仅复制嵌套对象的地址:
var outter = {
outter_property:333,
// inner 是嵌套对象
inner: {
inner_property:222
}
};
var copy = sallowCopy(outter);
// copy.inner 与 outter.inner 是同一个对象,它们指向同一个内存地址。
copy.inner.inner_property = 'new value!';
console.log(outter.inner.inner_property); // new value!
//改变了copy.inner对象,也就改变了outter.inner对象
拷贝需要注意的问题有很多:
- 需要拷贝的对象是Array
- 需要拷贝的对象是Number、String、Boolean包装对象
- 需要拷贝的对象是Date、RegExp等特别的内置对象
- 需要拷贝的对象是Function
- 需要拷贝的对象存在环,比如:
var b = {}; a.child = b; b.child = a;
这些问题,上面这个方法都没有考虑,它只适用于简单对象的复制,这个方法仅用于示范浅拷贝的原理。事实上很难写出、也没必要写出一个能够应付所有情况的完美方法。只要根据实际情况选择,往往都能找到一个满足需要的方法。文末会列举现有的、常见的拷贝函数。
深拷贝
function deepClone(obj) {
var copy;
// Handle number, boolean, string, null and undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = deepClone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
深拷贝会复制嵌套的对象:
var outter = {
outter_property:333,
inner: {
inner_property:222
}
};
var copy = deepClone(outter);
// copy.inner 与 outter.inner是不同的两个对象,从此互不干涉
copy.inner.inner_property = 'new value!';
console.log(outter.inner.inner_property); // 222
// 修改copy.inner.inner_property,outter.inner.inner_property不会改变
上面这个深拷贝方法除了可以处理原始类型和简单对象以外,还能处理Date和Array,依然不能处理Number对象、RegExp对象、Function对象等。不过已经比较实用了。
现成的拷贝方法
-
var cloneOfA = JSON.parse(JSON.stringify(a));
可以用于简单对象的深拷贝。这个方法的原理是将对象转换成json字符串以后再将json字符串转换成对象。因此要注意那些转换成json以后无法恢复的类型,最好只用来处理属性值是原始类型的对象和数组,或者它们的嵌套。此外,这个方法也不能处理存在环的对象。 - Object.assign(target, ...sources)是ES6提供的浅拷贝方法,与我们给出的浅拷贝方法作用类似,拷贝对象自身的、可枚举的属性。Object.assign可以传入多个source对象,并且target不要求是空对象。需要注意的是它拷贝streing、number、boolean原始类型的时候,会先将它们装箱,再拷贝这个包装对象:
Object.assign({}, 'abcd')
// Object {0: "a", 1: "b", 2: "c", 3: "d"}
- var copiedObject = jQuery.extend({}, originalObject) 是jQuery提供的方法。默认是浅拷贝,它拷贝自身和原型链上的所有可枚举属性。可以通过设置第一个参数为true来进行深拷贝:
var copiedObject = jQuery.extend(true, {}, originalObject)
extend、assign这些单词的名字的意思是“扩展”、“赋值”,拷贝对象只是它们的用途之一,它们的target参数不一定是要
{}
。 - Underscore的 _.clone(source) 浅拷贝,返回拷贝出的新对象。它拷贝自身和原型链上的所有可枚举属性。
- lodash 的 _.clone(value) 和 _.cloneDeep()能够很好地处理很多内置对象:arrays, array buffers, booleans, date objects, maps, numbers, Object objects, regexes, sets, strings, symbols, and typed arrays,并且能处理存在环的对象,更接近完美。