什么是最有效的方式来克隆一个JavaScript对象?我已经看到obj = eval(uneval(o));被使用,但它是非标准的,仅支持Firefox.我做了事情,如obj = JSON.parse(JSON.stringify(o));但质疑效率。


当前回答

对于一个微小的副本,有一个很好的,简单的方法引入了ECMAScript2018标准,它涉及使用 Spread Operator :

let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };

let objClone = { ...obj };

我在Chrome浏览器中测试了它,两个对象都存储在不同的位置,所以即时更改儿童值在一个不会改变另一个。

这个技术是非常简单的,直前的,我认为这是一个真正的最佳实践这个问题一次和永远。

其他回答

使用今天的JavaScript来克隆一个对象:ECMAScript 2015(以前被称为ECMAScript 6)

var original = {a: 1};

// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);

// Method 2: New object with spread operator assignment.
var copy2 = {...original};

旧浏览器可能不支持 ECMAScript 2015. 一个常见的解决方案是使用 JavaScript-to-JavaScript 编译器,如 Babel 发行您的 JavaScript 代码的 ECMAScript 5 版本。

正如 @jim-hall 指出的那样,这只是一个微小的副本. 属性属性被复制为参考:更改一个将改变另一个对象/实例的值。

结构化克隆

HTML 标准包含一个内部结构化的克隆/序列化算法,可以创建对象的深度克隆,它仍然仅限于某些内置类型,但除了由 JSON 支持的少数类型之外,它还支持日期、RegExps、地图、套件、Blobs、FileLists、ImageDatas、Sparse Arrays、Typed Arrays等。

结构化Clone 全球功能由 Node 17.0 提供:

const clone = structuredClone(original);

以前版本: Node.js 的 v8 模块(如 Node 11 )直接展示了结构化序列化 API,但此功能仍然被标记为“实验性”,并在未来的版本中可更改或删除。

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

直接支持浏览器:可用于Firefox 94

const clone = structuredClone(original);

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;
    
    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;
    
    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

main();

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

雖然同步,但這可能非常慢。 它發生了所有與操縱瀏覽器歷史相關的頭部. 重複召喚這種方法可能會導致Chrome暫時變成無責任。

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

如果沒有任何內建一個,你可以嘗試:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

使用 Object.create() 获取原型和支持的例子,并使用 for() 圈获取可列的密钥:

function cloneObject(source) {
    var key,value;
    var clone = Object.create(source);

    for (key in source) {
        if (source.hasOwnProperty(key) === true) {
            value = source[key];

            if (value!==null && typeof value==="object") {
                clone[key] = cloneObject(value);
            } else {
                clone[key] = value;
            }
        }
    }

    return clone;
}

假设您只拥有属性,而不是对象中的任何功能,您只能使用:

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