这句代码是怎么实现深拷贝的?

请教个问题:
这句代码:var newObject = JSON.parse(JSON.stringify(oldObject));

可以简单的实现深复制,但是有几个点我不明白,
首先:JSON.stringify() 方法可以将任意的 JavaScript 值序列化成 JSON 字符串,
也就是可以把传入的数组,object对象等序列化成JSON字符串,
然后:JSON.parse() 方法可以将一个 JSON 字符串解析成为一个 JavaScript 值。
在解析过程中还可以选择性的篡改某些属性的原始解析值,也就是把刚序列化成JSON字符串的又在解析成一次并赋给newObject

不明白的是这其中的过程是怎么实现深复制了?

我了解的浅复制是两个对象都指向同一块内存地址,当修改a时,b也会自动发生变化。
而深复制是重新开辟出一块内存地址,在实现深复制后,会把a对象的各个属性全部复制到新的内存地址中,对于引用类型的变量,需要用递归来使对象的所有属性都被复制到,此后a与b没有关联了,修改a但是b不会改变

可是用JSON.parse(JSON.stringify(oldObject))这个方法实现深复制的过程还是看不懂,麻烦解释下可以吗?

下面是测试的代码:

function cloneObject(src) {
    var clone = JSON.parse(JSON.stringify(src));
    return clone;
 }
// 测试用例:
var srcObj = {
    a: 1,
    b: {
        b1: ["hello", "hi"],
        b2: "JavaScript"
    }
};
var abObj = srcObj;
var tarObj = cloneObject(srcObj);
srcObj.a = 2;
srcObj.b.b1[0] = "Hello";
//浅复制
console.log(abObj.a);      //2
console.log(abObj.b.b1[0]);    //hello

//深复制
console.log(tarObj.a);      // 1
console.log(tarObj.b.b1[0]);    // "hello"
console.log(tarObj.b.b1[1]);    //'hi'

console.log( typeof(tarObj.a) );   //Number

另外,对这句代码SF上的评论
上面这种方法好处是非常简单易用,但是坏处也显而易见,这会抛弃对象的constructor,也就是深复制之后,无论这个对象原本的构造函数是什么,在深复制之后都会变成Object。另外诸如RegExp对象是无法通过这种方式深复制的。@jerryzou

对显而易见的坏处,还是看不明白 =_=!! 可以详细解释下吗?

然后,在GitHub上看到的实现深复制的代码:

function cloneObject(src) {
    var clone = src;

    // 对于Date,String,Boolean等引用类型的数据,需要考虑调用构造函数重新构造,
    //直接赋值依然会有引用问题(不是真正的clone引用变量)
    // 对于 Date
    if (src instanceof Date) {
        clone = new Date(src.getDate());
        return clone;
    }

    // 对于Object和Array的遍历,可以使用for in,
    //这样可以保证在在Array对象上扩展的属性也可以正确复制
    // 对于 数组
    if (src instanceof Array) {
        clone = [];
        for (var key in src) {
            clone[key] = cloneObject(src[key]);
        }
        return clone;
    }

    // 对于 Object
    if (src instanceof Object) {
        clone = {};
        for (var key in src) {
            if (src.hasOwnProperty(key)) {       // 忽略掉继承属性
                clone[key] = cloneObject(src[key]);
            }
        }
        return clone;
    }

    // 对于 数字 字符串 布尔 null undefined
    return src;
}

我个人觉得比较完善,不过对使用 hasOwnProperty() 方法,排除继承的属性。这是为什么?

阅读 6.6k
3 个回答

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

var a = JSON.stringify(oldObject)  
// a是一个oldObject序列化后得到的字符串 和 oldObject没任何关系的新对象
var b = JSON.parse(a); 
// b 是从字符串a反序列化为一个全新的对象 所以 不但和a没关系 和oldObject更没关系
 var obj_stringify=JSON.stringify(oldObject);

此时的obj_stringify和oldObject对象指向2个不同的地址

var newObject=JSON.parse(obj_stringify);

将字符串反序列化为一个对象,此时的newObject和oldObject指向2个不同的地址

就像

var currentDate=new Date();
var currentDateTime= currentDate.getTime();
var copiedDate=new Date(currentDateTime);

currentDate和copiedDate表示的同一个时间,但是是2个不同的对象

另外

JSON.stringify() converts a value to JSON notation representing it:

  1. Properties of non-array objects are not guaranteed to be stringified in any particular order. Do not rely on ordering of properties within the same object within the stringification.

  2. Boolean, Number, and String objects are converted to the corresponding primitive values during stringification, in accord with the traditional conversion semantics.

  3. If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array).

  4. All symbol-keyed properties will be completely ignored, even when using the replacer function.

  5. Non-enumerable properties will be ignored

使用JSON.stringify序列化一个对象时

  1. 序列化后的属性出现的顺序是不定的,除了数组中的元素-其按数组中位置顺序序列化

  2. Boolean/Number/String对象转成其原始值

  3. 如果属性值为undefined、函数对象,symbol(ES6中新类型),那么这个属性要么在序列化的时候被忽略,要么被转成null(当在数组中出现时)

  4. 以symbol类型为属性key的属性被完全忽略

  5. 不可枚举的属性也被忽略

所以JSON.stringify并不能被用户来深度复制对象,这种情况只使用于属性值为基本类型或数组(元素项也为基本类型)的情形

JSON.parse(JSON.stringify(oldObject))

这样做 是把一个对象 变成json字符串格式 就像 {"xxx":"xcv"} 被变成了 '{"xxx":"xcv"}'
然后再把后者的字符串编译成一个object 因为他是一个符合json格式的字符串。

这样做通常是用来朝http 发请求的时候做为报文传递使用。

深度copy的时候 不适合的原因是 一个对象 上可能还有很多其他的无法被转为json string的东西
这一点你可以载入一个jquery用console.log 打印 $(document) 看看 里面有很多东西,但是他还是一个对象来的。你深度copy 它的时候 就不可以用前面的方法。

当然,简单规则的数据还是可以用前一种方法粗暴处理的,如果你和你的同伙儿都清楚自己在干什么的话。

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