javascript複製物件 在JavaScript中深度克隆對象的最有效方法是什麼?




javascript複製物件 (24)

按性能深度複製:從最佳到最差排名

  • 重新分配“=”(字符串數組,數字數組 - 僅)
  • 切片(字符串數組,數字數組 - 僅)
  • 連接(字符串數組,數字數組 - 僅)
  • 自定義函數:for循環或遞歸複製
  • jQuery的$ .extend
  • JSON.parse(字符串數組,數字數組,對像數組 - 僅)
  • Underscore.js的_.clone(字符串數組,僅數字數組)
  • Lo-Dash的_.cloneDeep

深層複製一個字符串或數字數組(一個級別 - 沒有引用指針):

當數組包含數字和字符串時 - 函數如.slice(),. concat(),. splice(),賦值運算符“=”和Underscore.js的克隆函數; 將製作數組元素的深層副本。

重新分配的地方表現最快:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

並且.slice()的性能優於.concat(), http://jsperf.com/duplicate-array-slice-vs-concat/3 //jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

深層複製一個對像數組(兩個或多個級別 - 引用指針):

var arr1 = [{object:'a'}, {object:'b'}];

編寫自定義函數(具有比$ .extend()或JSON.parse更快的性能):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

使用第三方實用程序功能:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

jQuery的$ .extend具有更好的性能:

克隆JavaScript對象的最有效方法是什麼? 我見過obj = eval(uneval(o)); 正在使用,但這是非標準的,只有Firefox支持

我做過像obj = JSON.parse(JSON.stringify(o)); 但質疑效率。

我也看到了具有各種缺陷的遞歸複製功能。
我很驚訝沒有規範的解決方案。


var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

結構化克隆

HTML5定義了一種內部“結構化”克隆算法 ,可以創建對象的深層克隆。 它仍然局限於某些內置類型,但除了JSON支持的少數類型之外,它還支持日期,RegExps,地圖,集合,Blob,FileLists,ImageDatas,稀疏數組, 類型數組 ,以及未來可能更多。 它還保留了克隆數據中的引用,允許它支持會導致JSON錯誤的循環和遞歸結構。

瀏覽器的直接支持:即將推出? 🙂

瀏覽器目前不提供結構化克隆算法的直接接口,但是gitHub上的whatwg / html#793正在積極討論全局的structuredClone()函數,可能即將推出! 正如目前提出的那樣,在大多數情況下使用它將如下所示:

const clone = structuredClone(original);

在此之前,瀏覽器的結構化克隆實現僅間接暴露。

異步解決方法:可用。 😕

使用現有API創建結構化克隆的低開銷方法是通過MessageChannels一個端口發布數據。 另一個端口將使用附加的.data的結構化克隆發出message事件。 不幸的是,監聽這些事件必然是異步的,並且同步替代方案不太實用。

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();

同步解決方法:太可怕了! 🤢

同步創建結構化克隆沒有好的選擇。 以下是一些不切實際的黑客行為。

history.pushState()history.replaceState()都創建了第一個參數的結構化克隆,並將該值賦給history.state 。 您可以使用它來創建任何對象的結構化克隆,如下所示:

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

使用示例:

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();

雖然是同步的,但這可能非常慢。 它會產生與操縱瀏覽器歷史記錄相關的所有開銷。 反複調用此方法可能會導致Chrome暫時無響應。

Notification構造函數創建其關聯數據的結構化克隆。 它還會嘗試向用戶顯示瀏覽器通知,但除非您已請求通知權限,否則它將無聲地失敗。 如果您有其他用途的許可,我們將立即關閉我們創建的通知。

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

使用示例:

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();


假設您的對像中只有變量而不是任何函數,您可以使用:

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

淺拷貝單行(ECMAScript第5版):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

淺拷貝單行(ECMAScript第6版,2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

這是一個可以克隆任何JavaScript對象的綜合clone()方法。它幾乎處理所有情況:

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};

Lodash有一個很好的lodash.com/docs#cloneDeep方法:

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

對於類似數組的對象,似乎還沒有理想的深度克隆運算符。如下面的代碼所示,John Resig的jQuery克隆器將具有非數字屬性的數組轉換為非數組的對象,RegDwight的JSON克隆器刪除非數字屬性。以下測試在多個瀏覽器上說明了這些要點:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

有一個庫(稱為“克隆”) ,這樣做很好。 它提供了我所知道的最完整的遞歸克隆/複製任意對象。 它還支持循環引用,但尚未涵蓋其他答案。

你也可以在npm找到它 。 它可以用於瀏覽器以及Node.js.

以下是如何使用它的示例:

安裝它

npm install clone

或者用Ender打包。

ender build clone [...]

您也可以手動下載源代碼。

然後您可以在源代碼中使用它。

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(免責聲明:我是圖書館的作者。)


查看此基準: http://jsben.ch/#/bWfk9http://jsben.ch/#/bWfk9

在我之前的測試中,速度是我發現的一個主要問題

JSON.parse(JSON.stringify(obj))

成為深度克隆對象的最快方法(它將jQuery.extend與深度標誌設置為10-20%)。

當深度標誌設置為false(淺層克隆)時,jQuery.extend非常快。 這是一個很好的選擇,因為它包含一些額外的類型驗證邏輯,不會復制未定義的屬性等,但這也會讓你慢下來。

如果您知道要嘗試克隆的對象的結構,或者可以避免深層嵌套數組,則可以在檢查hasOwnProperty時編寫一個簡單的for (var i in obj)循環來克隆對象,它將比jQuery快得多。

最後,如果您嘗試在熱循環中克隆已知對象結構,只需嵌入克隆過程並手動構建對象,即可獲得更多性能。

JavaScript跟踪引擎在優化for..in循環時很for..in並且檢查hasOwnProperty也會減慢你的速度。 當速度是絕對必須時手動克隆。

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

注意在DateJSON.stringify(new Date())上使用JSON.parse(JSON.stringify(obj))方法 - JSON.stringify(new Date())返回ISO格式的日期的字符串表示形式, JSON.parse() 轉換回到Date對象。 有關詳細信息,請參閱此答案

此外,請注意,至少在Chrome 65中,本機克隆是不可取的。 根據這個JSPerf ,通過創建一個新函數來執行本機克隆比使用JSON.stringify慢了近800倍,而JSON.stringify在整個過程中都非常快。


我here以最大的選票不同意答案。一個遞歸深克隆要快得多JSON.parse(JSON.stringify(OBJ))中提到的方法。

這是快速參考的功能:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

如果您正在使用它, Underscore.js庫有一個clone方法。

var newObject = _.clone(oldObject);

// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

Crockford建議(我更喜歡)使用此功能:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

這很簡潔,按預期工作,你不需要圖書館。

編輯:

這是一個polyfill Object.create,所以你也可以使用它。

var newObject = Object.create(oldObject);

注意:如果您使用其中一些,您可能會遇到使用某些迭代的問題hasOwnProperty。因為,create創建繼承的新空對象oldObject。但它對於克隆對象仍然有用且實用。

例如,如果 oldObject.a = 5;

newObject.a; // is 5

但:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

碼:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

測試:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

僅僅因為我沒有看到AngularJS提到並認為人們可能想知道......

angular.copy 還提供了深度複製對象和數組的方法。


在JavaScript中深度複製對象(我認為最好和最簡單)

1.使用JSON.parse(JSON.stringify(object));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.使用創建的方法

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3.使用Lo-Dash的_.cloneDeep鏈接lodash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4.使用Object.assign()方法

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

但是錯了

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.採用Underscore.js _.clone鏈接Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

但是錯了

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

參考medium.com

JSBEN.CH Performance Benchmarking Playground 1~3 http://jsben.ch/KVQLd


這就是我正在使用的:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

AngularJS

好吧,如果你使用角度,你也可以做到這一點

var newObject = angular.copy(oldObject);

注意:這是對另一個答案的回复,而不是對此問題的正確回答。 如果您希望快速克隆對象,請在回答此問題時遵循Corban的建議 。

我想要注意jQuery中的.clone()方法只能克隆DOM元素。 為了克隆JavaScript對象,您可以:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

可以在jQuery文檔中找到更多信息。

我還要注意,深層副本實際上比上面顯示的更加智能 - 它可以避免許多陷阱(例如,嘗試深度擴展DOM元素)。 它經常在jQuery核心和插件中使用,效果很好。


在一行代碼中克隆(而不是深度克隆)對象的有效方法

Object.assign方法是ECMAScript 2015(ES6)標準的一部分,可以完全滿足您的需求。

var clone = Object.assign({}, obj);

Object.assign()方法用於將所有可枚舉的自有屬性的值從一個或多個源對象複製到目標對象。

Object.assign

polyfill支持舊版瀏覽器:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

這通常不是最有效的解決方案,但它可以滿足我的需求。簡單的測試用例如下......

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

循環陣列測試......

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

功能測試...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

我知道這是一個老帖子,但我認為這可能對下一個偶然發現的人有所幫助。

只要您不將對象分配給任何內容,它就不會在內存中保留任何引用。 因此,要創建一個您想要在其他對象之間共享的對象,您必須創建一個像這樣的工廠:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);




clone