JavaScript中拷贝对象方法总结

对象拷贝是在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,并且能处理存在环的对象,更接近完美。

参考资料

  • http://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object
  • http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html
  • https://www.zhihu.com/question/23031215
  • http://jerryzou.com/posts/dive-into-deep-clone-in-javascript/

你可能感兴趣的:(JavaScript中拷贝对象方法总结)