Javascript 객체를 어떻게 심층 복제합니까?
내가 좋아하는 프레임 워크를 기반으로 다양한 기능이 알고 JSON.parse(JSON.stringify(o))
하고 $.extend(true, {}, o)
있지만, 그와 같은 프레임 워크를 사용하지 않습니다.
딥 클론을 생성하는 가장 우아하고 효율적인 방법은 무엇입니까?
우리는 어레이 복제와 같은 엣지 케이스에 관심이 있습니다. 프로토 타입 체인을 깨지 않고 자체 참조를 처리합니다.
우리는 DOM 객체의 복사를 지원하는 데 관심이 없습니다 .cloneNode
. 그 이유 때문에 존재하기 때문입니다.
주로 node.js
V8 엔진의 ES5 기능 을 사용할 때 딥 클론을 사용하고 싶기 때문에 허용됩니다.
[편집하다]
누군가 제안하기 전에 객체에서 프로토 타입으로 상속하여 복제본을 생성하는 것과 복제 하는 것 사이에는 뚜렷한 차이점이 있습니다. 전자는 프로토 타입 체인을 엉망으로 만듭니다.
[추가 편집]
귀하의 답변을 읽은 후 전체 개체를 복제하는 것이 매우 위험하고 어려운 게임이라는 성가신 발견에 도달했습니다. 예를 들어 다음 클로저 기반 객체를 살펴보십시오.
var o = (function() {
var magic = 42;
var magicContainer = function() {
this.get = function() { return magic; };
this.set = function(i) { magic = i; };
}
return new magicContainer;
}());
var n = clone(o); // how to implement clone to support closures
객체를 복제하고 복제 시점에 동일한 상태를 가지지 만 o
JS에서 JS 파서를 작성하지 않고 는 상태를 변경할 수없는 복제 함수를 작성하는 방법이 있습니까?
더 이상 그러한 기능이 실제로 필요하지 않아야합니다. 이것은 단순한 학문적 관심사입니다.
답변
복제하려는 항목에 따라 다릅니다. 이것은 진정한 JSON 객체입니까 아니면 JavaScript의 객체입니까? 복제를 수행하려면 문제가 발생할 수 있습니다. 어떤 문제? 아래에서 설명하겠습니다. 먼저 객체 리터럴, 프리미티브, 배열 및 DOM 노드를 복제하는 코드 예제입니다.
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
var copy = clone({
one : {
'one-one' : new String("hello"),
'one-two' : [
"one", "two", true, "four"
]
},
two : document.createElement("div"),
three : [
{
name : "three-one",
number : new Number("100"),
obj : new function() {
this.name = "Object test";
}
}
]
})
이제 REAL 개체 복제를 시작할 때 발생할 수있는 문제에 대해 이야기하겠습니다. 나는 지금 당신이 다음과 같은 것을함으로써 창조하는 물체에 대해 이야기하고 있습니다.
var User = function(){}
var newuser = new User();
물론 그것들을 복제 할 수 있습니다. 그것은 문제가되지 않습니다. 모든 객체는 생성자 속성을 노출하고 객체를 복제하는 데 사용할 수 있지만 항상 작동하지는 않습니다. for in
이 개체에 대해 간단하게 할 수도 있지만 같은 방향으로 진행됩니다-문제. 또한 코드 내에 복제 기능을 포함 시켰지만 if( false )
문에 의해 제외되었습니다 .
그렇다면 복제가 왜 고통 스러울 수 있습니까? 우선 모든 객체 / 인스턴스에는 상태가있을 수 있습니다. 예를 들어 객체에 개인 변수가 없는지 확신 할 수 없으며,이 경우 객체를 복제하면 상태가 깨집니다.
상태가 없다고 상상해보세요. 괜찮습니다. 그렇다면 여전히 또 다른 문제가 있습니다. “생성자”방법을 통한 복제는 또 다른 장애물을 제공합니다. 인수 종속성입니다. 이 개체를 만든 사람이 어떤 종류의
new User({
bike : someBikeInstance
});
이 경우 운이 좋지 않은 경우 someBikeInstance가 일부 컨텍스트에서 생성되었을 수 있으며 해당 컨텍스트는 복제 메서드에 대해 알 수 없습니다.
그래서 뭘 할건데? 당신은 여전히 for in
솔루션을 할 수 있고 그러한 객체를 일반 객체 리터럴처럼 취급 할 수 있지만, 그러한 객체를 전혀 복제하지 않고 단지이 객체의 참조를 전달하는 것이 아이디어일까요?
또 다른 해결책은 복제해야하는 모든 객체가이 부분을 스스로 구현하고 적절한 API 메서드 (예 : cloneObject)를 제공해야한다는 규칙을 설정할 수 있다는 것입니다. 무엇 뭔가 cloneNode
DOM에 대해하고있다.
당신이 결정합니다.
답변
아주 간단한 방법, 어쩌면 너무 간단 할 수도 있습니다.
var cloned = JSON.parse(JSON.stringify(objectToClone));
답변
JSON.parse(JSON.stringify())
딥 카피 자바 스크립트 객체 의 조합은 JSON 데이터를위한 것이기 때문에 비효율적 인 해킹입니다.undefined
또는의 값을 지원하지 않으며 Javascript 객체를 JSON으로 “문자열 화”(마샬링) 할 때function () {}
단순히 무시합니다null
.
더 나은 해결책은 전체 복사 기능을 사용하는 것입니다. 아래 함수는 객체를 딥 복사하며 타사 라이브러리 (jQuery, LoDash 등)가 필요하지 않습니다.
function copy(aObject) {
if (!aObject) {
return aObject;
}
let v;
let bObject = Array.isArray(aObject) ? [] : {};
for (const k in aObject) {
v = aObject[k];
bObject[k] = (typeof v === "object") ? copy(v) : v;
}
return bObject;
}
답변
다음은 순환 참조가있는 객체에서도 작동하는 ES6 함수입니다.
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj; // primitives
if (hash.has(obj)) return hash.get(obj); // cyclic reference
const result = obj instanceof Set ? new Set(obj) // See note about this!
: obj instanceof Map ? new Map(Array.from(obj, ([key, val]) =>
[key, deepClone(val, hash)]))
: obj instanceof Date ? new Date(obj)
: obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
// ... add here any specific treatment for other classes ...
// and finally a catch-all:
: obj.constructor ? new obj.constructor()
: Object.create(null);
hash.set(obj, result);
return Object.assign(result, ...Object.keys(obj).map(
key => ({ [key]: deepClone(obj[key], hash) }) ));
}
// Sample data
var p = {
data: 1,
children: [{
data: 2,
parent: null
}]
};
p.children[0].parent = p;
var q = deepClone(p);
console.log(q.children[0].parent.data); // 1
세트 및 맵에 대한 참고 사항
세트 및 맵의 키를 다루는 방법은 논쟁의 여지가 있습니다. 이러한 키는 종종 원시적이지만 (이 경우 논쟁이 없음) 객체가 될 수도 있습니다. 이 경우 질문은 다음과 같습니다. 해당 키를 복제해야합니까?
이 작업을 수행해야한다고 주장 할 수 있으므로 해당 개체가 복사본에서 변경되면 원본의 개체는 영향을받지 않으며 그 반대의 경우도 마찬가지입니다.
반면에 has
키를 설정 / 매핑 하는 경우 원본과 사본 모두에서 이것이 사실이어야합니다. 적어도 둘 중 하나에 변경이 이루어지기 전에 말입니다 . 복사본이 이전에 발생하지 않은 키가있는 Set / Map이면 이상 할 수 있습니다 (복제 프로세스 중에 생성 되었기 때문에). 이는 주어진 객체가 어떤 코드인지 알아야하는 코드에는별로 유용하지 않습니다. Set / Map을 입력하거나 입력하지 않습니다.
아시다시피 저는 두 번째 의견에 가깝습니다. 세트 및 맵의 키 는 동일하게 유지되어야하는 값 ( 참고 문헌 일 수 있음 )입니다.
이러한 선택은 종종 다른 (사용자 정의) 개체와 함께 표시됩니다. 복제 된 개체가 특정 경우에 어떻게 작동할지에 따라 달라지는 일반적인 해결책은 없습니다.
답변
Underscore.js있는 contrib 라이브러리 라이브러리라는 함수가 스냅 샷을 깊은 클론 오브젝트 그
소스의 스 니펫 :
snapshot: function(obj) {
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor();
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = _.snapshot(obj[key]);
}
}
return temp;
}
라이브러리가 프로젝트에 연결되면 간단히 다음을 사용하여 함수를 호출합니다.
_.snapshot(object);
답변
이것이 제가 사용하는 딥 클로닝 방법입니다. 좋습니다. 제안 해 주시기 바랍니다.
function deepClone (obj) {
var _out = new obj.constructor;
var getType = function (n) {
return Object.prototype.toString.call(n).slice(8, -1);
}
for (var _key in obj) {
if (obj.hasOwnProperty(_key)) {
_out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
}
}
return _out;
}
답변
다른 사람들이 이것과 유사한 질문에 대해 언급했듯이, 일반적인 의미에서 “객체”를 복제하는 것은 JavaScript에서 모호합니다.
그러나 내가 “데이터”객체라고 부르는 객체 클래스가 있습니다. 즉, 단순히 { ... }
리터럴 및 / 또는 간단한 속성 할당으로 생성되거나 복제하려는 것이 합당한 JSON에서 역 직렬화 된 객체 입니다. 오늘은 대규모 데이터 세트에 대해 어떤 일이 발생하는지 테스트하기 위해 서버에서 수신 한 데이터를 인위적으로 5 배 확장하고 싶었지만, 사물 (배열)과 그 자식은 사물이 제대로 작동하려면 별개의 개체 여야했습니다. 복제를 통해이 작업을 수행하여 데이터 세트를 늘릴 수있었습니다.
return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));
내가 종종 데이터 개체를 복제하는 다른 곳은 데이터를 보내기 전에 데이터 모델의 개체에서 상태 필드를 제거하려는 호스트에 데이터를 다시 제출하는 것입니다. 예를 들어, 복제 될 때 개체에서 “_”로 시작하는 모든 필드를 제거 할 수 있습니다.
다음은 복제 할 멤버를 선택하는 선택기와 지원 배열 (컨텍스트를 결정하기 위해 “경로”문자열을 사용함)을 포함하여 일반적으로이 작업을 수행하기 위해 작성한 코드입니다.
function clone(obj,sel) {
return (obj ? _clone("",obj,sel) : obj);
}
function _clone(pth,src,sel) {
var ret=(src instanceof Array ? [] : {});
for(var key in src) {
if(!src.hasOwnProperty(key)) { continue; }
var val=src[key], sub;
if(sel) {
sub+=pth+"/"+key;
if(!sel(sub,key,val)) { continue; }
}
if(val && typeof(val)=='object') {
if (val instanceof Boolean) { val=Boolean(val); }
else if(val instanceof Number ) { val=Number (val); }
else if(val instanceof String ) { val=String (val); }
else { val=_clone(sub,val,sel); }
}
ret[key]=val;
}
return ret;
}
Null이 아닌 루트 개체를 가정하고 멤버 선택이 없다고 가정하는 가장 간단하고 합리적인 딥 클론 솔루션은 다음과 같습니다.
function clone(src) {
var ret=(src instanceof Array ? [] : {});
for(var key in src) {
if(!src.hasOwnProperty(key)) { continue; }
var val=src[key];
if(val && typeof(val)=='object') { val=clone(val); }
ret[key]=val;
}
return ret;
}