js深度复制一个对象使用JSON.stringify是最好的办法吗?

深度复制一个对象,看到很多种方法,最简单的是:

var newObject = JSON.parse(JSON.stringify(oldObject));

这样写有什么弊端吗?

阅读 33.5k
9 个回答

oldObject = {a: 1, b: function() {}}

更新:
JSON接口在于:无法处理function,无法处理Reg,无法处理循环引用对象,完美的实现深度copy是非常非常麻烦的,生产上建议使用lodash,https://www.npmjs.com/package...
如果你的应用场景处理的就是服务器返回json数据,或者即将以json传递给服务器的,用JSON是最方便的,没必要引入这么复杂的clone库。

原答案:
使用JSON接口有弊端,使用Object.create不会复制对象本身, 而是用对象的constructor重新构造一个对象。
所以可以考虑使用Object.assign

let old_obj = [{a:1},{b:2}];
let new_obj = old_obj.map((ele)=>{
    return Object.assign({},ele);
});
old_obj[0].a=99;
console.log(new_obj); // "[{a:1},{b:2}]" 

一般情况下通过 JSON 来复制挺好的,代码写起来也方便——不过并不是所有环境都实现了 JSON,这个需要考虑下。

通过 deep clone 一般都是有限定复制层次的,一般情况下不会无限层的复制下去。如果使用 JSON 方式来复制,通常不能控制层次。

深拷贝: JSON.parse()和JSON.stringify() 问题: 对象里的函数无法被拷贝,原型链里的属性无法被拷贝

最简单的深拷贝:

b = JSON.parse( JSON.stringify(a) )

有一下问题:
1.循环引用会报错

const x = {};
const y = {x};
x.y = y;

console.log(JSON.parse(JSON.stringify(x)));
// TypeError: Converting circular structure to JSON

2.某些属性无法拷贝

const obj = {
    a: '1',
    arr: [1, 2 ,3],
    obj1: {
        o: 1,
    },
    b: undefined,
    c:  Symbol(),
    date: new Date(),
    reg: /a/ig,
    set: new Set([1, 2, 3]),
    foo: () => {
        console.log('foo');
    }
}
console.log(JSON.parse(JSON.stringify(obj)));
// { a: '1',
//   arr: [ 1, 2, 3 ],
//   obj1: { o: 1 },
//   date: '2019-04-18T08:11:32.866Z',
//   reg: {},
//   set: {} 
// }

2.1 如果被拷贝的对象中有functionSymbolundefined, 则拷贝之后的对象就会丢失
2.2 如果被拷贝的对象中有正则表达式, Set,则拷贝之后的对象正则表达式会变成空对象
2.3. 然而date对象成了字符串

为什么有些属性无法被拷贝呢, 主要是JSON.stringify()的问题, 那么问题就变成了为什么有些属性无法被stringify呢?
因为 JSON 是一个通用的文本格式,和语言无关。设想如果将函数定义也 stringify 的话,如何判断是哪种语言,并且通过合适的方式将其呈现出来将会变得特别复杂。特别是和语言相关的一些特性,比如 JavaScript 中的 Symbol。

深拷贝不就好了么。

https://github.com/XadillaX/nbut-online-judge-v2/blob/master/util/functions.js#L7-L28

/**
 * Deepin clone an object
 * @param obj
 * @returns {*}
 */
exports.cloneObject = function(obj) {
    if(typeof obj === "object") {
        if(util.isArray(obj)) {
            var newArr = [];
            for(var i = 0; i < obj.length; i++) newArr.push(obj[i]);
            return newArr;
        } else {
            var newObj = {};
            for(var key in obj) {
                newObj[key] = this.cloneObject(obj[key]);
            }
            return newObj;
        }
    } else {
        return obj;
    }
};
var newObject = Object.create(oldObject);

1.循环引用会报错,
2.functionSymbolundefined, 则拷贝之后的对象就会丢失,
3.如果被拷贝的对象中有正则表达式, Set则拷贝之后的对象正则表达式会变成空对象,
4.Date对象成了字符串.

以上四个问题,可以通过stringify第二个参数replacer解决,replacer本身是个含有key、value参数的可处理返回值函数,而parse函数中也有含有key、value的reviver函数,可以在parse对象时处理。

但是判断这四种情况写出代码的复杂程度不低于手动写一个深拷贝的递归函数。比如类似underscore(lodash)的_.clone。

使用JSON方法的原因是处理后端一般json数据很方便,
其次原生的JSON.stringify和parse性能很高,高于手动递归或_.clone之类的方法。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
1 篇内容引用
推荐问题