我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。

如何正确克隆JavaScript对象?


当前回答

我认为,在没有库的情况下,缓存的重复性是最好的。

被低估的WeakMap涉及到循环的问题,其中存储对新旧对象的引用可以帮助我们很容易地重建整个树。

我阻止了DOM元素的深度克隆,可能您不想克隆整个页面:)

function deepCopy(object) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(obj) {
        if (typeof obj !== 'object' ||
            obj === null ||
            obj instanceof HTMLElement
        )
            return obj; // primitive value or HTMLElement

        if (obj instanceof Date) 
            return new Date().setTime(obj.getTime());

        if (obj instanceof RegExp) 
            return new RegExp(obj.source, obj.flags);

        if (cache.has(obj)) 
            return cache.get(obj);

        const result = obj instanceof Array ? [] : {};

        cache.set(obj, result); // store reference to object before the recursive starts

        if (obj instanceof Array) {
            for(const o of obj) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(obj); 

        for (const key of keys)
            result[key] = copy(obj[key]);

        return result;
    }

    return copy(object);
}

一些测试:

// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

注意:我使用常量、for of循环、=>运算符和WeakMaps来创建更重要的代码。当前的浏览器支持此语法(ES6)

其他回答

使用默认值(历史上特定于nodejs,但由于现代JS,现在可以从浏览器中使用):

import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);

我不知道这在哪些情况下不起作用,但它给了我一个数组的副本。我觉得它很可爱:)希望它有帮助

copiedArr = origArr.filter(function(x){return true})

好的,假设你有下面的这个对象,你想克隆它:

let obj = {a:1, b:2, c:3}; //ES6

or

var obj = {a:1, b:2, c:3}; //ES5

答案主要取决于您使用的ECMAscript,在ES6+中,您可以简单地使用Object.assign来执行克隆:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

或使用如下扩展运算符:

let cloned = {...obj}; //new {a:1, b:2, c:3};

但是如果你使用ES5,你可以使用很少的方法,但JSON.stringify,只需确保你没有使用大量数据来复制,但在很多情况下,这可能是一种简单的方法,比如:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

复制最终可能指向自身的对象的问题可以通过简单的检查来解决。每次有复制操作时,添加此复选框。它可能很慢,但应该会起作用。

我使用toType()函数显式返回对象类型。我也有自己的copyObj()函数,它在逻辑上非常相似,可以回答所有三种Object()、Array()和Date()情况。

我在NodeJS中运行它。

尚未测试。

// Returns true, if one of the parent's children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) {
  if (toType(parent) == '[object Object]') {
    for (var name in parent) {
      var curProperty = parent[name];

      // Direct child.
      if (curProperty = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {
        if (isChild(target, curProperty)) return true;
      }
    }
  } else if (toType(parent) == '[object Array]') {
    for (var i=0; i < parent.length; i++) {
      var curItem = parent[i];

      // Direct child.
      if (curItem = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {
        if (isChild(target, curItem)) return true;
      }
    }
  }

  return false;     // Not the target.
}

如果您有一个带有函数的对象,可以使用JSONfn来实现,请参见http://www.eslinstructor.net/jsonfn/.

var obj= {
    name:'Marvin',
    getName :  function(){
      return this.name;
    }
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));