clone object assign - JavaScriptでオブジェクトを深くクローンする最も効率的な方法は何ですか?





15 Answers

このベンチマークをチェックアウト: http://jsben.ch/#/bWfk9 : http://jsben.ch/#/bWfk9

スピードが主な関心事だった以前のテストでは、

JSON.parse(JSON.stringify(obj))

オブジェクトを深くクローンする最速の方法です(深いフ​​ラグをtrueに設定してjQuery.extendを10〜20% jQuery.extend )。

ディープフラグがfalse(浅いクローン)に設定されていると、jQuery.extendはかなり高速です。 型バリデーションのための余分なロジックが含まれており、未定義のプロパティなどをコピーしないため、これは良い選択ですが、これによっても少し時間がかかります。

クローンを作成しようとしているオブジェクトの構造を知っていたり、深いネストされた配列を避けることができれば、hasOwnPropertyをチェックしながらオブジェクトをクローンするfor (var i in obj)の単純なfor (var i in obj)ループを書くことができ、jQueryよりもはるかに高速です。

最後に、ホットループで既知のオブジェクト構造を複製しようとすると、クローン手順を単純にインライン化してオブジェクトを手作業で構築するだけで、より多くのパフォーマンスを得ることができます。

JavaScriptトレースエンジンは、 for..inループを最適化するfor..inます。また、hasOwnPropertyをチェックすることで、あなたも遅くなります。 速度が絶対必要である場合、手動クローン。

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

DateオブジェクトのJSON.parse(JSON.stringify(obj))メソッドを使用することをJSON.parse(JSON.stringify(obj))くださいJSON.parse(JSON.stringify(obj)) JSON.stringify(new Date())は、 JSON.parse() DateオブジェクトにDateます。 詳細については、この回答を参照してください

さらに、少なくともChrome 65では、ネイティブクローニングは行かないことに注意してください。 このJSPerfによれば、新しい機能を作成してネイティブクローニングを実行することは、JSON.stringifyを使用するよりも800倍近く遅くなります。

代替 js 配列

JavaScriptオブジェクトを複製する最も効率的な方法は何ですか? 私はobj = eval(uneval(o)); 使用されていますが、これは非標準でFirefoxのみでサポートされています。

私はobj = JSON.parse(JSON.stringify(o));ようなことをしましたobj = JSON.parse(JSON.stringify(o)); 効率について疑問を呈します。

私はまた、さまざまな欠陥を持つ再帰的なコピー機能を見てきました。
私は標準的な解決策が存在しないことに驚いている。




構造化クローニング

HTML5はオブジェクトの深いクローンを作成できる内部の「構造化」クローニングアルゴリズムを定義しています。 JSONでサポートされているいくつかのタイプに加えて、Dates、RegExps、Maps、Sets、Blob、FileList、ImageDatas、スパース配列、 型付き配列をサポートしています。 。 クローニングされたデータ内の参照を保持し、JSONのエラーの原因となる周期的な構造と再帰的な構造をサポートできます。

ブラウザーでの直接サポート:近日公開予定ですか? 🙂

ブラウザは現在、構造化クローンアルゴリズムの直接インタフェースを提供していませんが、グローバルstructuredClone()関数はGitHubのwhatwg / html#793で積極的に議論されており、間もなく公開予定です! 現在提案されているように、ほとんどの目的のためにそれを使用するのは簡単です:

const clone = structuredClone(original);

これが出荷されるまで、ブラウザの構造化されたクローン実装は間接的にしか公開されません。

非同期回避策:使用可能です。 😕

既存のAPIを使用して構造化されたクローンを作成するオーバーヘッドが少ない方法は、 MessageChannels 1つのポートを介してデータを送信することです。 もう一方のポートは、添付された.data構造化されたクローンを持つmessageイベントを.dataます。 残念なことに、これらのイベントをリッスンすることは必然的に非同期であり、同期の選択肢はあまり実用的ではありません。

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




1つのコード行でオブジェクトを複製する(ディープクローンではない)効率的な方法

Object.assignメソッドは、ECMAScript 2015(ES6)標準の一部であり、必要なものを正確に実行します。

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

Object.assign()メソッドは、すべての列挙可能なプロパティの値を1つ以上のソースオブジェクトからターゲットオブジェクトにコピーするために使用されます。

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 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;
}



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



これは非常にうまくいくライブラリ(「クローン」と呼ばれます)があります。 私が知っている任意のオブジェクトの最も完全な再帰的クローニング/コピーを提供します。 また、循環参照もサポートしていますが、これは他の回答ではカバーされていません。

あなたは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' } }

(免責事項:私は図書館の著者です。)




それを使用している場合、 Underscore.jsライブラリにはcloneメソッドがあります。

var newObject = _.clone(oldObject);



以下は、同じオブジェクトの2つのインスタンスを作成します。私はそれを見つけ出し、現在それを使用しています。シンプルで使いやすいです。

var objToCreate = JSON.parse(JSON.stringify(cloneThis));



Lodashには素敵なlodash.com/docs#cloneDeepメソッドがあります:

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

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



浅いコピー1ライナー(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

そして浅いコピーの1ライナー(ECMAScript 6th edition、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



配列のようなオブジェクトに対してはまだ理想的な深いクローン演算子がないようです。以下のコードが示すように、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)



これは一般的に最も効率的な解決策ではありませんが、私が必要とするものを実行します。以下の簡単なテストケース...

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



私はhere最高の投票here答えに同意し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
}



ここには、あらゆる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;
};



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 link 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.)

media.comを参照

JSBEN.CHパフォーマンスベンチマークプレイグラウンド1〜3 http://jsben.ch/KVQLd




Related