etc./StackOverFlow

JavaScript 객체를 올바르게 복제하려면 어떻게 해야 합니까?

청렴결백한 만능 재주꾼 2021. 10. 7. 22:04
반응형

질문자 :soundly_typed


나는 개체 x 있습니다. 나는 개체로 복사 할 y 변경하도록, y 수정하지 않는 x . 내장 JavaScript 객체에서 파생된 객체를 복사하면 원치 않는 추가 속성이 생성된다는 것을 깨달았습니다. 내 자신의 리터럴 생성 객체 중 하나를 복사하기 때문에 이것은 문제가 되지 않습니다.

JavaScript 객체를 올바르게 복제하려면 어떻게 해야 합니까?



JavaScript의 모든 객체에 대해 이 작업을 수행하는 것은 간단하거나 간단하지 않습니다. 프로토타입에 남아 있어야 하고 새 인스턴스에 복사되지 않아야 하는 속성을 개체의 프로토타입에서 잘못 선택하는 문제가 발생합니다. 예를 들어 Object.prototype clone 메서드를 추가하는 경우 일부 답변에서 설명하는 것처럼 해당 속성을 명시적으로 건너뛸 필요가 있습니다. Object.prototype 또는 다른 중간 프로토타입에 추가된 다른 추가 메서드가 있는 경우에는 어떻게 될까요? hasOwnProperty 메서드를 사용하여 예상치 못한 비로컬 속성을 감지해야 합니다.

열거할 수 없는 속성 외에도 숨겨진 속성이 있는 개체를 복사하려고 하면 더 어려운 문제가 발생합니다. 예를 들어 prototype 은 함수의 숨겨진 속성입니다. 또한 객체의 프로토타입은 __proto__ 속성으로 참조되며 이 속성도 숨겨져 있으며 소스 객체의 속성을 반복하는 for/in 루프에 의해 복사되지 않습니다. 내 생각에 __proto__ 는 Firefox의 JavaScript 인터프리터에만 해당될 수 있고 다른 브라우저에서는 다를 수 있지만 그림은 알 수 있습니다. 모든 것이 셀 수 있는 것은 아닙니다. 이름을 알고 있으면 숨겨진 속성을 복사할 수 있지만 자동으로 찾을 수 있는 방법을 모르겠습니다.

우아한 솔루션을 찾는 과정에서 또 다른 걸림돌은 프로토타입 상속을 올바르게 설정하는 문제입니다. 소스 객체의 프로토타입이 Object {} 를 사용하여 새로운 일반 객체를 생성하는 것만으로도 작동하지만, 소스의 프로토타입이 Object 일부 자손인 경우 해당 프로토타입에서 추가 멤버를 놓치게 됩니다. hasOwnProperty 필터, 또는 프로토타입에 있었지만 처음부터 열거할 수 없었습니다. 한 가지 해결책은 원본 개체의 constructor 속성을 호출하여 초기 복사 개체를 가져온 다음 속성을 복사하는 것입니다. 그러면 여전히 열거할 수 없는 속성을 얻지는 못합니다. 예를 들어 Date 객체는 데이터를 숨겨진 멤버로 저장합니다.

 function clone(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } var d1 = new Date(); /* Executes function after 5 seconds. */ setTimeout(function(){ var d2 = clone(d1); alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString()); }, 5000);

d1 의 날짜 문자열은 d2 의 날짜 문자열보다 5초 d2 . 하나의 Date setTime 메서드를 호출하는 Date 클래스에만 해당됩니다. 나는 이 문제에 대한 방탄 일반 솔루션이 없다고 생각하지만, 틀렸다면 기쁠 것입니다!

일반 딥 복사를 구현해야 했을 때 일반 Object , Array , Date , String , Number 또는 Boolean 만 복사하면 된다고 가정함으로써 타협하게 되었습니다. 마지막 3가지 유형은 변경할 수 없으므로 얕은 복사를 수행하고 변경에 대해 걱정할 필요가 없습니다. Object 또는 Array 포함된 모든 요소는 해당 목록의 6가지 단순 유형 중 하나일 것이라고 가정했습니다. 이는 다음과 같은 코드로 수행할 수 있습니다.

 function clone(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = clone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }

위의 함수는 객체와 배열의 데이터가 트리 구조를 형성하는 한 내가 언급한 6가지 단순 유형에 대해 적절하게 작동합니다. 즉, 개체의 동일한 데이터에 대한 참조가 두 개 이상 없습니다. 예를 들어:

 // This would be cloneable: var tree = { "left" : { "left" : null, "right" : null, "data" : 3 }, "right" : null, "data" : 8 }; // This would kind-of work, but you would get 2 copies of the // inner node instead of 2 references to the same copy var directedAcylicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; directedAcyclicGraph["right"] = directedAcyclicGraph["left"]; // Cloning this would cause a stack overflow due to infinite recursion: var cyclicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; cyclicGraph["right"] = cyclicGraph;

어떤 JavaScript 객체도 처리할 수 없지만, 여러분이 던지는 모든 것에 대해 작동할 것이라고 가정하지 않는 한 많은 목적에 충분할 수 있습니다.


A. Levy

Date s, functions, undefined, regExp 또는 Infinity를 사용하지 않는 경우 JSON.parse(JSON.stringify(object)) .

 const a = { string: 'string', number: 123, bool: false, nul: null, date: new Date(), // stringified undef: undefined, // lost inf: Infinity, // forced to 'null' } console.log(a); console.log(typeof a.date); // Date object const clone = JSON.parse(JSON.stringify(a)); console.log(clone); console.log(typeof clone.date); // result of .toISOString()

이것은 객체, 배열, 문자열, 부울 및 숫자를 포함하는 모든 종류의 객체에 대해 작동합니다.

작업자에게 메시지를 게시하거나 작업자로부터 메시지를 게시할 때 사용되는 브라우저의 구조화된 복제 알고리즘에 대한 이 문서 도 참조하십시오. 또한 deep cloning을 위한 기능도 포함되어 있습니다.


heinob

jQuery를 사용하면 확장을 사용 하여 얕은 복사 를 할 수 있습니다.

 var copiedObject = jQuery.extend({}, originalObject)

copiedObject 에 대한 후속 변경 originalObject 영향을 미치지 않으며 그 반대의 경우도 마찬가지입니다.

또는 전체 복사본 을 만들려면 다음을 수행합니다.

 var copiedObject = jQuery.extend(true, {}, originalObject)

Pascal

ECMAScript 6에는 하나의 개체에서 다른 개체로 모든 열거 가능한 자체 속성 값을 복사하는 Object.assign 메서드가 있습니다. 예를 들어:

 var x = {myProp: "value"}; var y = Object.assign({}, x);

그러나 이것은 얕은 복사입니다 . 중첩된 개체는 여전히 참조로 복사됩니다.


Vitalii Fedorenko

MDN 당:

  • 얕은 복사를 원하면 Object.assign({}, a)
  • "깊은" 복사의 경우 JSON.parse(JSON.stringify(a))

외부 라이브러리는 필요 없지만 먼저 브라우저 호환성 을 확인해야 합니다.


Tareq

많은 답변이 있지만 ECMAScript 5의 Object.create 를 언급하는 답변은 없습니다. 정확한 사본을 제공하지는 않지만 소스를 새 개체의 프로토타입으로 설정합니다.

따라서 이것은 질문에 대한 정확한 답변은 아니지만 한 줄 솔루션이므로 우아합니다. 그리고 2가지 경우에 가장 잘 작동합니다.

  1. 그러한 상속이 유용한 곳 (duh!)
  2. 소스 개체가 수정되지 않으므로 두 개체 간의 관계가 문제가 되지 않습니다.

예시:

 var foo = { a : 1 }; var bar = Object.create(foo); foo.a; // 1 bar.a; // 1 foo.a = 2; bar.a; // 2 - prototype changed bar.a = 3; foo.a; // Still 2, since setting bar.a makes it an "own" property

이 솔루션이 더 우수하다고 생각하는 이유는 무엇입니까? 네이티브이므로 루핑이나 재귀가 없습니다. 그러나 이전 브라우저에는 폴리필이 필요합니다.


itpastorn

한 줄의 코드로 Javascript 객체를 복제하는 우아한 방법

Object.assign 메서드는 ECMAScript 2015(ES6) 표준의 일부이며 필요한 작업을 정확히 수행합니다.

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

Object.assign() 메서드는 하나 이상의 소스 개체에서 대상 개체로 모든 열거 가능한 자체 속성 값을 복사하는 데 사용됩니다.

더 읽기...

이전 브라우저를 지원하는 폴리필:

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

Eugene Tiurin

대부분의 인터넷 솔루션에는 몇 가지 문제가 있습니다. 그래서 나는 받아들인 대답이 받아들여지면 안 되는 이유를 포함하는 후속 조치를 취하기로 결정했습니다.

시작 상황

모든 자식과 자식 등을 포함하는 Javascript Object 를 딥 복사 하고 싶습니다. 하지만 저는 평범한 개발자가 아니기 때문에 내 Object 에는 일반 properties , circular structuresnested objects 있습니다.

먼저 circular structurenested object 생성해 보겠습니다.

 function Circ() { this.me = this; } function Nested(y) { this.y = y; }

에 함께하자 가지고 모든 Object 라는 이름의 a .

 var a = { x: 'a', circ: new Circ(), nested: new Nested('a') };

다음으로 우리 b 라는 변수 a 그것을 변경하려고 합니다.

 var b = a; bx = 'b'; b.nested.y = 'b';

여기에서 무슨 일이 일어 났는지 알고 있습니다. 그렇지 않으면이 위대한 질문에 도달하지도 않을 것이기 때문입니다.

 console.log(a, b); a --> Object { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } b --> Object { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } }

이제 해결책을 찾아봅시다.

JSON

내가 시도한 첫 번째 시도는 JSON 을 사용하는 것이었습니다.

 var b = JSON.parse( JSON.stringify( a ) ); bx = 'b'; b.nested.y = 'b';

너무 많은 시간을 낭비하지 마십시오. TypeError: Converting circular structure to JSON 됩니다.

재귀 사본 (허용되는 "답변")

수락 된 답변을 살펴 보겠습니다.

 function cloneSO(obj) { // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = cloneSO(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }

좋아보이죠? 이것은 객체의 재귀 복사본이며 Date 와 같은 다른 유형도 처리하지만 요구 사항은 아닙니다.

 var b = cloneSO(a); bx = 'b'; b.nested.y = 'b';

재귀 및 circular structures 가 잘 작동하지 않습니다... RangeError: Maximum call stack size exceeded

네이티브 솔루션

직장 동료와 말다툼을 한 후 상사가 무슨 일이냐고 물었고 구글링 끝에 간단한 해결책을 찾았습니다. Object.create 라고 합니다.

 var b = Object.create(a); bx = 'b'; b.nested.y = 'b';

이 솔루션은 얼마 전에 Javascript에 추가되었으며 circular structure 도 처리합니다.

 console.log(a, b); a --> Object { x: "a", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } b --> Object { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } }

... 그리고 알다시피, 내부의 중첩 구조에서는 작동하지 않았습니다.

기본 솔루션용 폴리필

Object.create 에 대한 폴리필이 있습니다. Mozilla에서 권장하는 것과 같으며 물론 완벽하지 않고 기본 솔루션 과 동일한 문제가 발생합니다.

 function F() {}; function clonePF(o) { F.prototype = o; return new F(); } var b = clonePF(a); bx = 'b'; b.nested.y = 'b';

instanceof 알려주는 내용을 볼 수 있도록 F 범위 외부에 두었습니다.

 console.log(a, b); a --> Object { x: "a", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } b --> F { x: "b", circ: Circ { me: Circ { ... } }, nested: Nested { y: "b" } } console.log(typeof a, typeof b); a --> object b --> object console.log(a instanceof Object, b instanceof Object); a --> true b --> true console.log(a instanceof F, b instanceof F); a --> false b --> true

기본 솔루션 과 동일한 문제이지만 출력이 조금 더 나쁩니다.

더 나은(완벽하지는 않지만) 솔루션

주변을 파헤칠 때 이와 유사한 질문( Javascript에서 깊은 복사를 수행할 때 속성이 "this"이기 때문에 주기를 피하려면 어떻게 해야 하나요? )을 찾았지만 더 나은 솔루션이 있습니다.

 function cloneDR(o) { const gdcc = "__getDeepCircularCopy__"; if (o !== Object(o)) { return o; // primitive value } var set = gdcc in o, cache = o[gdcc], result; if (set && typeof cache == "function") { return cache(); } // else o[gdcc] = function() { return result; }; // overwrite if (o instanceof Array) { result = []; for (var i=0; i<o.length; i++) { result[i] = cloneDR(o[i]); } } else { result = {}; for (var prop in o) if (prop != gdcc) result[prop] = cloneDR(o[prop]); else if (set) result[prop] = cloneDR(cache); } if (set) { o[gdcc] = cache; // reset } else { delete o[gdcc]; // unset again } return result; } var b = cloneDR(a); bx = 'b'; b.nested.y = 'b';

그리고 출력을 보자...

 console.log(a, b); a --> Object { x: "a", circ: Object { me: Object { ... } }, nested: Object { y: "a" } } b --> Object { x: "b", circ: Object { me: Object { ... } }, nested: Object { y: "b" } } console.log(typeof a, typeof b); a --> object b --> object console.log(a instanceof Object, b instanceof Object); a --> true b --> true console.log(a instanceof F, b instanceof F); a --> false b --> false

nestedcirc instanceObject 변경하는 것을 포함하여 여전히 몇 가지 작은 문제가 있습니다.

잎을 공유하는 나무의 구조는 복사되지 않으며 두 개의 독립된 잎이 됩니다.

 [Object] [Object] / \ / \ / \ / \ |/_ _\| |/_ _\| [Object] [Object] ===> [Object] [Object] \ / | | \ / | | _\| |/_ \|/ \|/ [Object] [Object] [Object]

결론

재귀와 캐시를 사용하는 마지막 솔루션은 최선이 아닐 수 있지만 객체의 실제 전체 복사본입니다. 간단한 properties , circular structuresnested object 를 처리하지만 복제하는 동안 해당 인스턴스를 엉망으로 만듭니다.

jsfiddle


Fabio Poloni

얕은 복사가 괜찮다면 underscore.js 라이브러리에 복제 메서드가 있습니다.

 y = _.clone(x);

또는 다음과 같이 확장할 수 있습니다.

 copiedObject = _.extend({},originalObject);

dule

자, 아래에 이 개체가 있고 이를 복제하고 싶다고 상상해 보십시오.

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

또는

 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

Alireza

2020년 7월 6일 업데이트

JavaScript에서 개체를 복제하는 방법에는 세 가지가 있습니다. JavaScript의 객체는 참조 값이므로 =를 사용하여 단순히 복사할 수 없습니다.

방법은 다음과 같습니다.

 const food = { food: 'apple', drink: 'milk' } // 1. Using the "Spread" // ------------------ { ...food } // 2. Using "Object.assign" // ------------------ Object.assign({}, food) // 3. "JSON" // ------------------ JSON.parse(JSON.stringify(food)) // RESULT: // { food: 'apple', drink: 'milk' }

참고 자료로 활용되기를 바랍니다.


Mohsen Alyafei

특히 부적절한 솔루션 중 하나는 JSON 인코딩을 사용하여 멤버 메서드가 없는 객체의 전체 복사본을 만드는 것입니다. 방법론은 대상 객체를 JSON으로 인코딩한 다음 디코딩하여 원하는 사본을 얻는 것입니다. 필요한 만큼 복사본을 만들고 싶은 만큼 여러 번 디코딩할 수 있습니다.

물론 함수는 JSON에 속하지 않으므로 멤버 메서드가 없는 객체에만 적용됩니다.

이 방법론은 키-값 저장소에 JSON blob을 저장하고 JavaScript API에서 개체로 노출될 때 각 개체가 실제로 개체의 원래 상태 복사본을 포함하므로 사용 사례에 완벽했습니다. 호출자가 노출된 개체를 변경한 후 델타를 계산할 수 있습니다.

 var object1 = {key:"value"}; var object2 = object1; object2 = JSON.stringify(object1); object2 = JSON.parse(object2); object2.key = "a change"; console.log(object1);// returns value

Kris Walker

스프레드 속성 을 사용하여 참조 없이 객체를 복사할 수 있습니다. 그러나 주의(주석 참조), '복사'는 가장 낮은 개체/배열 수준에 있습니다. 중첩 속성은 여전히 참조입니다!


완전한 클론:

 let x = {a: 'value1'} let x2 = {...x} // => mutate without references: x2.a = 'value2' console.log(xa) // => 'value1'

두 번째 수준의 참조로 복제:

 const y = {a: {b: 'value3'}} const y2 = {...y} // => nested object is still a references: y2.ab = 'value4' console.log(yab) // => 'value4'

JavaScript는 실제로 기본적으로 딥 클론을 지원하지 않습니다. 유틸리티 기능을 사용합니다. 예를 들어 람다:

http://ramdajs.com/docs/#clone


musemind

AngularJS를 사용하는 사람들을 위해 이 라이브러리의 객체를 복제하거나 확장하는 직접적인 방법도 있습니다.

 var destination = angular.copy(source);

또는

 angular.copy(source, destination);

angular.copy 문서 에서 자세히 ...


Lukas Jelinek

이 기사에서: Brian Huisman의 Javascript에서 배열 및 객체를 복사하는 방법:

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

Calvin

A.Levy의 답변은 거의 완료되었습니다. 여기에 제 작은 기여 가 있습니다. 재귀 참조를 처리하는 방법 이 있습니다. 이 줄을 참조하세요.

if(this[attr]==this) copy[attr] = copy;

객체가 XML DOM 요소인 경우 대신 cloneNode 를 사용해야 합니다.

if(this.cloneNode) return this.cloneNode(true);

A.Levy의 철저한 연구와 Calvin의 프로토타이핑 접근 방식에서 영감을 받아 저는 이 솔루션을 제공합니다.

 Object.prototype.clone = function() { if(this.cloneNode) return this.cloneNode(true); var copy = this instanceof Array ? [] : {}; for(var attr in this) { if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone) copy[attr] = this[attr]; else if(this[attr]==this) copy[attr] = copy; else copy[attr] = this[attr].clone(); } return copy; } Date.prototype.clone = function() { var copy = new Date(); copy.setTime(this.getTime()); return copy; } Number.prototype.clone = Boolean.prototype.clone = String.prototype.clone = function() { return this; }

답변에서 Andy Burke의 메모도 참조하십시오.


Jan Turoň

let objClone = { ...obj };

중첩된 개체 는 여전히 참조로 복사됩니다.


Pavan Garre

다음은 사용할 수 있는 기능입니다.

 function clone(obj) { if(obj == null || typeof(obj) != 'object') return obj; var temp = new obj.constructor(); for(var key in obj) temp[key] = clone(obj[key]); return temp; }

picardo

Lodash 사용:

 var y = _.clone(x, true);

VaZaA

ES-6에서는 단순히 Object.assign(...)을 사용할 수 있습니다. 전:

 let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj);

좋은 참고 자료는 다음과 같습니다. https://googlechrome.github.io/samples/object-assign-es6/


João Oliveira

성능

오늘 2020.04.30 저는 MacOs High Sierra v10.13.6의 Chrome v81.0, Safari v13.1 및 Firefox v75.0에서 선택한 솔루션의 테스트를 수행합니다.

나는 복사 DATA(메소드 등이 아닌 단순 유형 필드가 있는 객체)의 속도에 중점을 둡니다. 솔루션 AI는 얕은 복사만 만들 수 있고 솔루션 JU는 깊은 복사를 만들 수 있습니다.

얕은 복사에 대한 결과

  • 솔루션 {...obj} (A)는 크롬과 파이어폭스에서 가장 빠르고 사파리에서는 보통 빠릅니다.
  • Object.assign (B) 기반 솔루션은 모든 브라우저에서 빠릅니다.
  • jQuery(E) 및 lodash(F,G,H) 솔루션은 중간/매우 빠름
  • 솔루션 JSON.parse/stringify (K)가 매우 느립니다.
  • 솔루션 D 및 U는 모든 브라우저에서 느립니다.

여기에 이미지 설명 입력

전체 복사에 대한 결과

  • 솔루션 Q는 모든 브라우저에서 가장 빠릅니다.
  • jQuery(L) 및 lodash(J)는 중간 정도 빠릅니다.
  • 솔루션 JSON.parse/stringify (K)가 매우 느립니다.
  • 솔루션 U는 모든 브라우저에서 가장 느립니다.
  • lodash(J) 및 솔루션 U는 1000레벨 깊이 개체에 대해 Chrome에서 충돌합니다.

여기에 이미지 설명 입력

세부

선택한 솔루션의 경우: A B C(my) D E F G H I J K L M N O P Q R S T U , 4가지 테스트 수행

테스트에 사용된 개체는 아래 스니펫에 표시됩니다.

 let obj_ShallowSmall = { field0: false, field1: true, field2: 1, field3: 0, field4: null, field5: [], field6: {}, field7: "text7", field8: "text8", } let obj_DeepSmall = { level0: { level1: { level2: { level3: { level4: { level5: { level6: { level7: { level8: { level9: [[[[[[[[[['abc']]]]]]]]]], }}}}}}}}}, }; let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,{}); let obj_DeepBig = genDeepObject(1000); // ------------------ // Show objects // ------------------ console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall)); console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall)); console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig)); console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig)); // ------------------ // HELPERS // ------------------ function getField(k) { let i=k%10; if(i==0) return false; if(i==1) return true; if(i==2) return k; if(i==3) return 0; if(i==4) return null; if(i==5) return []; if(i==6) return {}; if(i>=7) return "text"+k; } function genDeepObject(N) { // generate: {level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] }}}...}}} let obj={}; let o=obj; let arr = []; let a=arr; for(let i=0; i<N; i++) { o['level'+i]={}; o=o['level'+i]; let aa=[]; a.push(aa); a=aa; } a[0]='abc'; o['end']=arr; return obj; }

아래 스니펫은 테스트된 솔루션을 제시하고 차이점을 보여줍니다.

 function A(obj) { return {...obj} } function B(obj) { return Object.assign({}, obj); } function C(obj) { return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {}) } function D(obj) { let copyOfObject = {}; Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj)); return copyOfObject; } function E(obj) { return jQuery.extend({}, obj) // shallow } function F(obj) { return _.clone(obj); } function G(obj) { return _.clone(obj,true); } function H(obj) { return _.extend({},obj); } function I(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } function J(obj) { return _.cloneDeep(obj,true); } function K(obj) { return JSON.parse(JSON.stringify(obj)); } function L(obj) { return jQuery.extend(true, {}, obj) // deep } function M(obj) { if(obj == null || typeof(obj) != 'object') return obj; var temp = new obj.constructor(); for(var key in obj) temp[key] = M(obj[key]); return temp; } function N(obj) { let EClone = function(obj) { var newObj = (obj instanceof Array) ? [] : {}; for (var i in obj) { if (i == 'EClone') continue; if (obj[i] && typeof obj[i] == "object") { newObj[i] = EClone(obj[i]); } else newObj[i] = obj[i] } return newObj; }; return EClone(obj); }; function O(obj) { if (obj == null || typeof obj != "object") return obj; if (obj.constructor != Object && obj.constructor != Array) return obj; if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function || obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean) return new obj.constructor(obj); let to = new obj.constructor(); for (var name in obj) { to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name]; } return to; } function P(obj) { function clone(target, source){ for(let key in source){ // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter. let descriptor = Object.getOwnPropertyDescriptor(source, key); if(descriptor.value instanceof String){ target[key] = new String(descriptor.value); } else if(descriptor.value instanceof Array){ target[key] = clone([], descriptor.value); } else if(descriptor.value instanceof Object){ let prototype = Reflect.getPrototypeOf(descriptor.value); let cloneObject = clone({}, descriptor.value); Reflect.setPrototypeOf(cloneObject, prototype); target[key] = cloneObject; } else { Object.defineProperty(target, key, descriptor); } } let prototype = Reflect.getPrototypeOf(source); Reflect.setPrototypeOf(target, prototype); return target; } return clone({},obj); } function Q(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = Q(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } function R(obj) { const gdcc = "__getDeepCircularCopy__"; if (obj !== Object(obj)) { return obj; // primitive value } var set = gdcc in obj, cache = obj[gdcc], result; if (set && typeof cache == "function") { return cache(); } // else obj[gdcc] = function() { return result; }; // overwrite if (obj instanceof Array) { result = []; for (var i=0; i<obj.length; i++) { result[i] = R(obj[i]); } } else { result = {}; for (var prop in obj) if (prop != gdcc) result[prop] = R(obj[prop]); else if (set) result[prop] = R(cache); } if (set) { obj[gdcc] = cache; // reset } else { delete obj[gdcc]; // unset again } return result; } function S(obj) { const cache = new WeakMap(); // Map of old - new references function copy(object) { if (typeof object !== 'object' || object === null || object instanceof HTMLElement ) return object; // primitive value or HTMLElement if (object instanceof Date) return new Date().setTime(object.getTime()); if (object instanceof RegExp) return new RegExp(object.source, object.flags); if (cache.has(object)) return cache.get(object); const result = object instanceof Array ? [] : {}; cache.set(object, result); // store reference to object before the recursive starts if (object instanceof Array) { for(const o of object) { result.push(copy(o)); } return result; } const keys = Object.keys(object); for (const key of keys) result[key] = copy(object[key]); return result; } return copy(obj); } function T(obj){ var clonedObjectsArray = []; var originalObjectsArray = []; //used to remove the unique ids when finished var next_objid = 0; function objectId(obj) { if (obj == null) return null; if (obj.__obj_id == undefined){ obj.__obj_id = next_objid++; originalObjectsArray[obj.__obj_id] = obj; } return obj.__obj_id; } function cloneRecursive(obj) { if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0; i < obj.length; ++i) { copy[i] = cloneRecursive(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { if (clonedObjectsArray[objectId(obj)] != undefined) return clonedObjectsArray[objectId(obj)]; var copy; if (obj instanceof Function)//Handle Function copy = function(){return obj.apply(this, arguments);}; else copy = {}; clonedObjectsArray[objectId(obj)] = copy; for (var attr in obj) if (attr != "__obj_id" && obj.hasOwnProperty(attr)) copy[attr] = cloneRecursive(obj[attr]); return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } var cloneObj = cloneRecursive(obj); //remove the unique ids for (var i = 0; i < originalObjectsArray.length; i++) { delete originalObjectsArray[i].__obj_id; }; return cloneObj; } function U(obj) { /* Deep copy objects by value rather than by reference, exception: `Proxy` */ const seen = new WeakMap() return clone(obj) function defineProp(object, key, descriptor = {}, copyFrom = {}) { const { configurable: _configurable, writable: _writable } = Object.getOwnPropertyDescriptor(object, key) || { configurable: true, writable: true } const test = _configurable // Can redefine property && (_writable === undefined || _writable) // Can assign to property if (!test || arguments.length <= 2) return test const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key) || { configurable: true, writable: true } // Custom… || {}; // …or left to native default settings ["get", "set", "value", "writable", "enumerable", "configurable"] .forEach(attr => descriptor[attr] === undefined && (descriptor[attr] = basisDesc[attr]) ) const { get, set, value, writable, enumerable, configurable } = descriptor return Object.defineProperty(object, key, { enumerable, configurable, ...get || set ? { get, set } // Accessor descriptor : { value, writable } // Data descriptor }) } function clone(object) { if (object !== Object(object)) return object /* —— Check if the object belongs to a primitive data type */ if (object instanceof Node) return object.cloneNode(true) /* —— Clone DOM trees */ let _object // The clone of object switch (object.constructor) { case Array: case Object: _object = cloneObject(object) break case Date: _object = new Date(+object) break case Function: const fnStr = String(object) _object = new Function("return " + (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr) ? "function " : "" ) + fnStr )() copyPropDescs(_object, object) break case RegExp: _object = new RegExp(object) break default: switch (Object.prototype.toString.call(object.constructor)) { // // Stem from: case "[object Function]": // `class` case "[object Undefined]": // `Object.create(null)` _object = cloneObject(object) break default: // `Proxy` _object = object } } return _object } function cloneObject(object) { if (seen.has(object)) return seen.get(object) /* —— Handle recursive references (circular structures) */ const _object = Array.isArray(object) ? [] : Object.create(Object.getPrototypeOf(object)) /* —— Assign [[Prototype]] for inheritance */ seen.set(object, _object) /* —— Make `_object` the associative mirror of `object` */ Reflect.ownKeys(object).forEach(key => defineProp(_object, key, { value: clone(object[key]) }, object) ) return _object } function copyPropDescs(target, source) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source) ) } } // ------------------------ // Test properties // ------------------------ console.log(` shallow deep func circ undefined date RegExp bigInt`) log(A); log(B); log(C); log(D); log(E); log(F); log(G); log(H); log(I); log(J); log(K); log(L); log(M); log(N); log(O); log(P); log(Q); log(R); log(S); log(T); log(U); console.log(` shallow deep func circ undefined date RegExp bigInt ---- LEGEND: shallow - solution create shallow copy deep - solution create deep copy func - solution copy functions circ - solution can copy object with circular references undefined - solution copy fields with undefined value date - solution can copy date RegExp - solution can copy fields with regular expressions bigInt - solution can copy BigInt `) // ------------------------ // Helper functions // ------------------------ function deepCompare(obj1,obj2) { return JSON.stringify(obj1)===JSON.stringify(obj2); } function getCase() { // pure data case return { undef: undefined, bool: true, num: 1, str: "txt1", e1: null, e2: [], e3: {}, e4: 0, e5: false, arr: [ false, 2, "txt3", null, [], {}, [ true,4,"txt5",null, [], {}, [true,6,"txt7",null,[],{} ], {bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false} ], {bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false} ], obj: { bool: true, num: 12, str: "txt13", e1: null, e2: [], e3: {}, e4: 0, e5: false, arr: [true,14,"txt15",null,[],{} ], obj: { bool: true, num: 16, str: "txt17", e1: null, e2: [], e3: {}, e4: 0, e5: false, arr: [true,18,"txt19",null,[],{} ], obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false} } } }; } function check(org, copy, field, newValue) { copy[field] = newValue; return deepCompare(org,copy); } function testFunc(f) { let o = { a:1, fun: (i,j)=> i+j }; let c = f(o); let val = false try{ val = c.fun(3,4)==7; } catch(e) { } return val; } function testCirc(f) { function Circ() { this.me = this; } var o = { x: 'a', circ: new Circ(), obj_circ: null, }; o.obj_circ = o; let val = false; try{ let c = f(o); val = (o.obj_circ == o) && (o.circ == o.circ.me); } catch(e) { } return val; } function testRegExp(f) { let o = { re: /a[0-9]+/, }; let val = false; try{ let c = f(o); val = (String(c.re) == String(/a[0-9]+/)); } catch(e) { } return val; } function testDate(f) { let o = { date: new Date(), }; let val = false; try{ let c = f(o); val = (+new Date(c.date) == +new Date(o.date)); } catch(e) { } return val; } function testBigInt(f) { let val = false; try{ let o = { big: 123n, }; let c = f(o); val = o.big == c.big; } catch(e) { } return val; } function log(f) { let o = getCase(); // orginal object let oB = getCase(); // "backup" used for shallow valid test let c1 = f(o); // copy 1 for reference let c2 = f(o); // copy 2 for test shallow values let c3 = f(o); // copy 3 for test deep values let is_proper_copy = deepCompare(c1,o); // shoud be true // shallow changes let testShallow = [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ] .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true ); // should be true (original object shoud not have changed shallow fields) let is_valid = deepCompare(o,oB); // deep test (intruduce some change) if (c3.arr[6]) c3.arr[6][7].num = 777; let diff_shallow = !testShallow; // shoud be true (shallow field was copied) let diff_deep = !deepCompare(c1,c3); // shoud be true (deep field was copied) let can_copy_functions = testFunc(f); let can_copy_circular = testCirc(f); let can_copy_regexp = testRegExp(f); let can_copy_date = testDate(f); let can_copy_bigInt = testBigInt(f); let has_undefined = 'undef' in c1; // field with undefined value is copied? let is_ok = is_valid && is_proper_copy; let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string testFunc(f); if(is_ok) { console.log(`${f.name} ${b(diff_shallow)} ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)} ${b(can_copy_date)} ${b(can_copy_regexp)} ${b(can_copy_bigInt)}`) } else { console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1}) } }
 <script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script> This snippet only presents tested solutions and show differences between them (but it no make performence tests)

다음은 얕은-큰 개체에 대한 Chrome에 대한 예제 결과입니다.

여기에 이미지 설명 입력


Kamil Kiełczewski

간단한 개체 복제에 관심:

JSON.parse(JSON.stringify(json_original));

출처 : 참조가 아닌 새 변수에 JavaScript 객체를 복사하는 방법은 무엇입니까?


Mohammed Akdim

한 줄의 코드를 사용하여 개체를 복제하고 이전 개체에서 참조를 제거할 수 있습니다. 간단하게 다음을 수행하십시오.

 var obj1 = { text: 'moo1' }; var obj2 = Object.create(obj1); // Creates a new clone without references obj2.text = 'moo2'; // Only updates obj2's text property console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

현재 Object.create를 지원하지 않는 브라우저/엔진의 경우 다음 폴리필을 사용할 수 있습니다.

 // Polyfill Object.create if it does not exist if (!Object.create) { Object.create = function (o) { var F = function () {}; F.prototype = o; return new F(); }; }

Rob Evans

오래된 질문에 대한 새로운 답변! Spread Syntax 와 함께 ECMAScript 2016(ES6)을 사용하는 즐거움이 있다면 쉽습니다.

 keepMeTheSame = {first: "Me!", second: "You!"}; cloned = {...keepMeTheSame}

이것은 객체의 얕은 복사본을 위한 깨끗한 방법을 제공합니다. 모든 재귀적으로 중첩된 객체에 있는 모든 값의 새 복사본을 만드는 것을 의미하는 깊은 복사본을 만들려면 위의 더 무거운 솔루션이 필요합니다.

자바스크립트는 계속 발전하고 있습니다.


Charles Merriam

let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

속성 객체뿐만 아니라 클래스 인스턴스 를 (얕게) 복제하려는 경우 ES6 솔루션.


flori

전체 복사 및 복제의 경우 JSON.stringify한 다음 객체를 JSON.parse합니다.

 obj = { a: 0 , b: { c: 0}}; let deepClone = JSON.parse(JSON.stringify(obj)); obj.a = 5; obj.bc = 5; console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

Nishant Dwivedi

간단하고 효과적인 답이 있다고 생각합니다. 깊은 복사에는 두 가지 우려 사항이 있습니다.

  1. 속성을 서로 독립적으로 유지합니다.
  2. 그리고 복제된 개체에서 메서드를 활성 상태로 유지합니다.

따라서 한 가지 간단한 솔루션은 먼저 직렬화 및 역직렬화한 다음 복사 기능에도 할당하는 것입니다.

 let deepCloned = JSON.parse(JSON.stringify(source)); let merged = Object.assign({}, source); Object.assign(merged, deepCloned);

이 질문에 많은 답변이 있지만 이 질문도 도움이 되었으면 합니다.


ConductedClever

lodash _.cloneDeep()를 사용합니다.

얕은 복사: lodash _.clone()

단순히 참조를 복사하여 얕은 복사를 만들 수 있습니다.

 let obj1 = { a: 0, b: { c: 0, e: { f: 0 } } }; let obj3 = _.clone(obj1); obj1.a = 4; obj1.bc = 4; obj1.bef = 100; console.log(JSON.stringify(obj1)); //{"a":4,"b":{"c":4,"e":{"f":100}}} console.log(JSON.stringify(obj3)); //{"a":0,"b":{"c":4,"e":{"f":100}}}

얕은 복사: lodash _.clone()

딥 카피: lodash _.cloneDeep()

필드가 역참조됨: 복사 중인 개체에 대한 참조가 아닌

 let obj1 = { a: 0, b: { c: 0, e: { f: 0 } } }; let obj3 = _.cloneDeep(obj1); obj1.a = 100; obj1.bc = 100; obj1.bef = 100; console.log(JSON.stringify(obj1)); {"a":100,"b":{"c":100,"e":{"f":100}}} console.log(JSON.stringify(obj3)); {"a":0,"b":{"c":0,"e":{"f":0}}}

딥 카피: lodash _.cloneDeep()


Ashok R

(다음은 주로 @ Maciej Bukowski , @ A. Levy , @ Jan Turoň , @ Redu 의 답변 및 @ LeviRoberts , @ RobG 의 의견을 통합한 것입니다. 많은 감사를 드립니다!!!)

딥 카피 ? - 예! (주로);
얕은 사본 ? - 아니요! ( Proxy 제외).

clone() 을 테스트하는 것을 진심으로 환영합니다.
또한 defineProp() 은 모든 유형의 디스크립터를 쉽고 빠르게 (재)정의하거나 복사하도록 설계되었습니다.

기능

 function clone(object) { /* Deep copy objects by value rather than by reference, exception: `Proxy` */ const seen = new WeakMap() return clone(object) function clone(object) { if (object !== Object(object)) return object /* —— Check if the object belongs to a primitive data type */ if (object instanceof Node) return object.cloneNode(true) /* —— Clone DOM trees */ let _object // The clone of object switch (object.constructor) { case Array: case Object: _object = cloneObject(object) break case Date: _object = new Date(+object) break case Function: _object = copyFn(object) break case RegExp: _object = new RegExp(object) break default: switch (Object.prototype.toString.call(object.constructor)) { // // Stem from: case "[object Function]": switch (object[Symbol.toStringTag]) { case undefined: _object = cloneObject(object) // `class` break case "AsyncFunction": case "GeneratorFunction": case "AsyncGeneratorFunction": _object = copyFn(object) break default: _object = object } break case "[object Undefined]": // `Object.create(null)` _object = cloneObject(object) break default: _object = object // `Proxy` } } return _object } function cloneObject(object) { if (seen.has(object)) return seen.get(object) /* —— Handle recursive references (circular structures) */ const _object = Array.isArray(object) ? [] : Object.create(Object.getPrototypeOf(object)) /* —— Assign [[Prototype]] for inheritance */ seen.set(object, _object) /* —— Make `_object` the associative mirror of `object` */ Reflect.ownKeys(object).forEach(key => defineProp(_object, key, { value: clone(object[key]) }, object) ) return _object } } function copyPropDescs(target, source) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source) ) } function convertFnToStr(fn) { let fnStr = String(fn) if (fn.name.startsWith("[")) // isSymbolKey fnStr = fnStr.replace(/\[Symbol\..+?\]/, '') fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr) ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr return fnStr } function copyFn(fn) { const newFn = new Function(`return ${convertFnToStr(fn)}`)() copyPropDescs(newFn, fn) return newFn } function defineProp(object, key, descriptor = {}, copyFrom = {}) { const { configurable: _configurable, writable: _writable } = Object.getOwnPropertyDescriptor(object, key) || { configurable: true, writable: true } const test = _configurable // Can redefine property && (_writable === undefined || _writable) // Can assign to property if (!test || arguments.length <= 2) return test const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key) || { configurable: true, writable: true } // Custom… || {}; // …or left to native default settings ["get", "set", "value", "writable", "enumerable", "configurable"] .forEach(attr => descriptor[attr] === undefined && (descriptor[attr] = basisDesc[attr]) ) const { get, set, value, writable, enumerable, configurable } = descriptor return Object.defineProperty(object, key, { enumerable, configurable, ...get || set ? { get, set } // Accessor descriptor : { value, writable } // Data descriptor }) }

// 테스트

 const obj0 = { u: undefined, nul: null, t: true, num: 9, str: "", sym: Symbol("symbol"), [Symbol("e")]: Math.E, arr: [[0], [1, 2]], d: new Date(), re: /f/g, get g() { return 0 }, o: { n: 0, o: { f: function (...args) { } } }, f: { getAccessorStr(object) { return [] .concat(... Object.values(Object.getOwnPropertyDescriptors(object)) .filter(desc => desc.writable === undefined) .map(desc => Object.values(desc)) ) .filter(prop => typeof prop === "function") .map(String) }, f0: function f0() { }, f1: function () { }, f2: a => a / (a + 1), f3: () => 0, f4(params) { return param => param + params }, f5: (a, b) => ({ c = 0 } = {}) => a + b + c } } defineProp(obj0, "s", { set(v) { this._s = v } }) defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } }) obj0.arr[0].name = "nested array" let obj1 = clone(obj0) obj1.on = 1 obj1.oog = function g(a = 0, b = 0) { return a + b } obj1.arr[1][1] = 3 obj1.d.setTime(+obj0.d + 60 * 1000) obj1.arr.tint.is = "enumerable? no" obj1.arr[0].name = "a nested arr" defineProp(obj1, "s", { set(v) { this._s = v + 1 } }) defineProp(obj1.re, "multiline", { value: true }) console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - Routinely") console.log("obj0:\n ", JSON.stringify(obj0)) console.log("obj1:\n ", JSON.stringify(obj1)) console.log() console.log("obj0:\n ", obj0) console.log("obj1:\n ", obj1) console.log() console.log("obj0\n ", ".arr.tint:", obj0.arr.tint, "\n ", ".arr[0].name:", obj0.arr[0].name ) console.log("obj1\n ", ".arr.tint:", obj1.arr.tint, "\n ", ".arr[0].name:", obj1.arr[0].name ) console.log() console.log("Accessor-type descriptor\n ", "of obj0:", obj0.f.getAccessorStr(obj0), "\n ", "of obj1:", obj1.f.getAccessorStr(obj1), "\n ", "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ", " → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s ) console.log("—— obj0 has not been interfered.") console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - More kinds of functions") const fnsForTest = { f(_) { return _ }, func: _ => _, aFunc: async _ => _, async function() { }, async asyncFunc() { }, aFn: async function () { }, *gen() { }, async *asyncGen() { }, aG1: async function* () { }, aG2: async function* gen() { }, *[Symbol.iterator]() { yield* Object.keys(this) } } console.log(Reflect.ownKeys(fnsForTest).map(k => `${String(k)}: ${fnsForTest[k].name}--> ${String(fnsForTest[k])}` ).join("\n")) const normedFnsStr = `{ f: function f(_) { return _ }, func: _ => _, aFunc: async _ => _, function: async function() { }, asyncFunc: async function asyncFunc() { }, aFn: async function () { }, gen: function* gen() { }, asyncGen: async function* asyncGen() { }, aG1: async function* () { }, aG2: async function* gen() { }, [Symbol.iterator]: function* () { yield* Object.keys(this) } }` const copiedFnsForTest = clone(fnsForTest) console.log("fnsForTest:", fnsForTest) console.log("fnsForTest (copied):", copiedFnsForTest) console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`)) console.log("Comparison of fnsForTest and its clone:", Reflect.ownKeys(fnsForTest).map(k => [k, fnsForTest[k] === copiedFnsForTest[k]] ) ) console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - Circular structures") obj0.or = {} obj0.orrecursion = obj0.o obj0.arr[1] = obj0.arr obj1 = clone(obj0) console.log("obj0:\n ", obj0) console.log("obj1:\n ", obj1) console.log("Clear obj0's recursion:", obj0.orrecursion = null, obj0.arr[1] = 1 ) console.log( "obj0\n ", ".or:", obj0.or, "\n ", ".arr:", obj0.arr ) console.log( "obj1\n ", ".or:", obj1.or, "\n ", ".arr:", obj1.arr ) console.log("—— obj1 has not been interfered.") console.log("\n\n" + "-".repeat(2 ** 6)) console.log(">:>: Test - Classes") class Person { constructor(name) { this.name = name } } class Boy extends Person { } Boy.prototype.sex = "M" const boy0 = new Boy boy0.hobby = { sport: "spaceflight" } const boy1 = clone(boy0) boy1.hobby.sport = "superluminal flight" boy0.name = "one" boy1.name = "neo" console.log("boy0:\n ", boy0) console.log("boy1:\n ", boy1) console.log("boy1's prototype === boy0's:", Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0) )

참고문헌

  1. Object.create() | MDN
  2. Object.defineProperties() | MDN
  3. 재산의 열거 가능성 및 소유권 | MDN
  4. TypeError: 순환 개체 값 | MDN

사용된 언어 트릭

  1. 조건부로 개체에 소품 추가

ooo

이것은 함수 및 다중/순환 참조의 복제도 처리하기 위해 A. Levy의 코드를 수정한 것입니다. 즉, 복제된 트리의 두 속성이 동일한 객체의 참조인 경우 복제된 객체 트리에 다음이 포함됩니다. 속성은 참조된 개체의 동일한 복제본을 가리킵니다. 이것은 또한 처리되지 않은 채로 두면 무한 루프로 이어지는 순환 종속성의 경우를 해결합니다. 알고리즘의 복잡성은 O(n)입니다.

 function clone(obj){ var clonedObjectsArray = []; var originalObjectsArray = []; //used to remove the unique ids when finished var next_objid = 0; function objectId(obj) { if (obj == null) return null; if (obj.__obj_id == undefined){ obj.__obj_id = next_objid++; originalObjectsArray[obj.__obj_id] = obj; } return obj.__obj_id; } function cloneRecursive(obj) { if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0; i < obj.length; ++i) { copy[i] = cloneRecursive(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { if (clonedObjectsArray[objectId(obj)] != undefined) return clonedObjectsArray[objectId(obj)]; var copy; if (obj instanceof Function)//Handle Function copy = function(){return obj.apply(this, arguments);}; else copy = {}; clonedObjectsArray[objectId(obj)] = copy; for (var attr in obj) if (attr != "__obj_id" && obj.hasOwnProperty(attr)) copy[attr] = cloneRecursive(obj[attr]); return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } var cloneObj = cloneRecursive(obj); //remove the unique ids for (var i = 0; i < originalObjectsArray.length; i++) { delete originalObjectsArray[i].__obj_id; }; return cloneObj; }

몇 가지 빠른 테스트

 var auxobj = { prop1 : "prop1 aux val", prop2 : ["prop2 item1", "prop2 item2"] }; var obj = new Object(); obj.prop1 = "prop1_value"; obj.prop2 = [auxobj, auxobj, "some extra val", undefined]; obj.nr = 3465; obj.bool = true; obj.f1 = function (){ this.prop1 = "prop1 val changed by f1"; }; objclone = clone(obj); //some tests i've made console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool)); objclone.f1(); console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1')); objclone.f1.prop = 'some prop'; console.log("test function cloning 2: " + (obj.f1.prop == undefined)); objclone.prop2[0].prop1 = "prop1 aux val NEW"; console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1)); console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

Radu Simionescu

출처 : http:www.stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object

반응형