JavaScript에서 객체를 딥 복제하는 가장 효율적인 방법은 무엇입니까?


5180

JavaScript 객체를 복제하는 가장 효율적인 방법은 무엇입니까? obj = eval(uneval(o));사용 된 것을 보았지만 비표준이며 Firefox에서만 지원됩니다 .

나는 같은 일을 obj = JSON.parse(JSON.stringify(o));했지만 효율성에 의문을 제기했다.

또한 다양한 결함이있는 재귀 복사 기능을 보았습니다.
정식 해결책이 없다는 것에 놀랐습니다.


566
평가는 악이 아니다. eval을 잘못 사용하는 것입니다. 부작용을 두려워하면 잘못 사용하고 있습니다. 당신이 두려워하는 부작용은 그것을 사용해야하는 이유입니다. 그런데 실제로 귀하의 질문에 대답 한 사람이 있습니까?
James

15
객체 복제는 까다로운 비즈니스입니다. 특히 임의 컬렉션의 사용자 정의 객체가 있습니다. 아마도 그것을 즉시 사용할 수있는 방법이없는 이유는 무엇입니까?
b01

12
eval()를 통해 설정되는 변수를 다룰 때 많은 Javascript 엔진 최적화 프로그램을 꺼야eval 하기 때문에 일반적으로 나쁜 생각 입니다. eval()코드 만 있으면 성능이 저하 될 수 있습니다.
user56reinstatemonica8


12
JSON메소드는 JSON과 동등한 Javascript 유형을 잃어 버립니다. 예 : JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))will 생성{a: null, b: null, c: null, g: false}
oriadam

답변:


4731

네이티브 딥 클로닝

이를 "구조적 복제"라고하며 노드 11 이상에서 실험적으로 작동하며 브라우저에 들어가기를 희망합니다. 자세한 내용은 이 답변 을 참조하십시오.

데이터 손실로 빠른 복제-JSON.parse / stringify

사용하지 않는 경우 Date의, 기능 undefined, Infinity개체 내 regexps '에,지도, 세트, 물방울,하는 파일 목록, ImageDatas, 스파 스 배열, 형식화 된 배열 또는 다른 복잡한 유형을, 깊은 복제에 대한 아주 간단한 하나 라이너는 것을 목적으로한다 :

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'
  re: /.*/,  // lost
}
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()

벤치 마크에 대해서는 Corban의 답변 을 참조하십시오 .

라이브러리를 사용한 안정적인 복제

객체 복제는 사소한 것이 아니기 때문에 (복잡한 유형, 순환 참조, 함수 등) 대부분의 주요 라이브러리는 객체를 복제하는 기능을 제공합니다. 바퀴를 재발 명하지 마십시오. 이미 라이브러리를 사용하고 있다면 객체 복제 기능이 있는지 확인하십시오. 예를 들어

ES6

: 완성도를 들어, 주 ES6는 두 개의 얕은 복사 메커니즘을 제공하는 Object.assign()확산 구문을 . 열거 가능한 모든 자체 속성 값을 한 개체에서 다른 개체로 복사합니다. 예를 들면 다음과 같습니다.

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js 276 번째 줄 (다른 방법을 수행하는 코드가 있지만 "JS에서이 작업을 수행하는 방법"에 대한 코드는 다음과 같습니다)
Rune FS

7
다음은 관심있는 모든 사람들을위한 jQuery 딥 카피의 JS 코드입니다. github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
우와! 그냥 슈퍼 명확합니다 :이 응답은 정답으로 고른 이유를 아무 생각이 아래 주어진 응답에 대한 응답 없었다 stackoverflow.com/a/122190/6524 추천 한 ( .clone()할 수있는 권한 코드가 아닙니다, 이 맥락에서 사용). 불행히도이 질문은 원래의 토론이 더 이상 명백하지 않은 많은 수정을 거쳤습니다! 속도에 관심이 있다면 Corban의 조언을 따르고 루프를 작성하거나 속성을 새 객체에 직접 복사하십시오. 또는 직접 테스트 해보십시오!
John Resig

9
이것은 JavaScript 질문입니다 (jQuery에 대한 언급은 없습니다).
gphilip

60
jQuery를 사용하지 않고 어떻게 할 수 있습니까?
Awesomeness01

2264

이 벤치 마크를 확인하십시오 : http://jsben.ch/#/bWfk9

속도가 주요 관심사였던 이전 테스트에서

JSON.parse(JSON.stringify(obj))

객체를 딥 복제하는 가장 느린 방법 입니다 ( 플래그가 10-20 %로 설정된 jQuery.extend 보다 느립니다 deep).

deep플래그가 false(얕은 복제)로 설정 되면 jQuery.extend는 매우 빠릅니다 . 형식 유효성 검사를위한 몇 가지 추가 논리가 포함되어 있고 정의되지 않은 속성 등을 복사하지 않기 때문에 좋은 옵션이지만 약간 느려질 수도 있습니다.

복제하려는 객체의 구조를 알고 있거나 깊은 중첩 배열을 피할 수있는 경우 for (var i in obj)hasOwnProperty를 확인하면서 객체를 복제 하는 간단한 루프를 작성할 수 있으며 jQuery보다 훨씬 빠릅니다.

마지막으로 핫 루프에서 알려진 객체 구조를 복제하려는 경우 단순히 복제 프로 시저를 인라인하고 수동으로 객체를 구성하여 훨씬 더 많은 성능을 얻을 수 있습니다.

JavaScript 추적 엔진은 for..in루프 를 최적화 하고 hasOwnProperty를 확인하면 속도가 느려집니다. 속도가 절대적인 경우 수동 복제.

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

객체 에서 JSON.parse(JSON.stringify(obj))메소드 사용에주의 -ISO 형식으로 날짜의 문자열 표현을 반환하며 Date객체 로 다시 변환 되지 않습니다 . 자세한 내용은이 답변을 참조하십시오 .JSON.stringify(new Date())JSON.parse() Date

또한 Chrome 65에서는 최소한 기본 복제가 불가능합니다. JSPerf에 따르면, 새로운 기능을 만들어 네이티브 복제를 수행 하는 것은 JSON.stringify를 사용하는 것보다 거의 800 배 느리게 진행됩니다.

ES6 업데이트

Javascript ES6을 사용하는 경우 복제 또는 단순 복사를 위해이 기본 방법을 시도하십시오.

Object.assign({}, obj);

4
@trysis Object.create는 객체를 복제하지 않고 프로토 타입 객체를 사용하고 있습니다 ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
이 방법은 또한 제거됩니다 keys당신의에서 object,이 functions(가) 때문에, 그 값으로 JSON기능을 지원하지 않습니다.
Karlen Kishmiryan

39
또한 JSON.parse(JSON.stringify(obj))Date Objects 를 사용 하면 ISO8601 형식 의 문자열 표현으로 날짜를 UTC 로 다시 변환 합니다.
dnlgmzddr

31
JSON 방식은 순환 참조를 질식시킵니다.
rich remer

28
@velop, Object.assign ({}, objToClone)은 얕은 복제본처럼 보이지만 개발자 도구 콘솔에서 재생하는 동안 객체 복제본을 사용하면 복제 된 객체의 참조를 가리 킵니다. 그래서 나는 그것이 실제로 여기에 적용되지 않는다고 생각합니다.
개렛 심슨

473

객체에 함수가 아닌 변수 만 있다고 가정하면 다음을 사용할 수 있습니다.

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

86
방금 찾은이 방법의
단점

31
@Jason,이 방법이 얕은 복사 (깊은 대상)보다 느린 이유는이 방법이 정의상 깊은 복사이기 때문입니다. 그러나 JSON기본 코드 (대부분의 브라우저에서)로 구현되므로 다른 자바 스크립트 기반 딥 카피 솔루션을 사용하는 것보다 훨씬 빠르며 때로는 자바 스크립트 기반 얕은 카피 기술보다 빠를 수 있습니다 ( jsperf.com/cloning 참조) -an-object / 79 ).
MiJyn

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer

32
이 기술은 Date객체 내부에 저장된 모든 객체 를 파괴 하여 문자열 형태로 변환합니다.
fstab

13
JSON 스펙 ( json.org ) 의 일부가 아닌 것은 복사하지 못합니다.
cdmckay

397

구조화 된 복제

HTML 표준에는 개체의 깊은 복제본을 만들 수 있는 내부 구조화 된 복제 / 직렬화 알고리즘 이 포함되어 있습니다. 여전히 특정 내장 유형으로 제한되지만 JSON에서 지원하는 몇 가지 유형 외에도 날짜, RegExps, 맵, 세트, ​​Blob, FileLists, ImageDatas, 스파 스 배열, 유형 배열 등을 지원합니다. . 또한 복제 된 데이터 내에서 참조를 유지하므로 JSON에 오류가 발생할 수있는 순환 및 재귀 구조를 지원할 수 있습니다.

Node.js 지원 : 실험적 🙂

v8(노드 11 등) 현재 Node.js를의 모듈은 직접 구조 직렬화 API를 노출 하지만,이 기능은 여전히 "실험", 미래의 버전에서 변경 또는 제거 대상으로 표시됩니다. 호환되는 버전을 사용하는 경우 객체 복제는 다음과 같이 간단합니다.

const v8 = require('v8');

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

브라우저에서 직접 지원 : 어쩌면 결국? 😐

브라우저는 현재 구조적 클로닝 알고리즘을위한 직접적인 인터페이스를 제공하지 않지만 GitHub의 whatwg / html # 793structuredClone() 에서 전역 함수에 대해 논의했습니다 . 현재 제안했듯이 대부분의 목적으로 사용하는 것은 다음과 같이 간단합니다.

const clone = structuredClone(original);

이것이 제공되지 않으면 브라우저의 구조화 된 클론 구현은 간접적으로 만 노출됩니다.

비동기 해결 방법 : 사용 가능 😕

기존 API로 구조화 된 복제본을 만드는 오버 헤드가 낮은 방법은 MessageChannels의 한 포트를 통해 데이터를 게시하는 것 입니다. 다른 포트는 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;
};

사용 예 :

동기식이지만 매우 느릴 수 있습니다. 브라우저 기록 조작과 관련된 모든 오버 헤드가 발생합니다. 이 메소드를 반복해서 호출하면 Chrome이 일시적으로 응답하지 않을 수 있습니다.

Notification생성자는 그와 연관된 데이터의 구조화 된 클론을 생성한다. 또한 사용자에게 브라우저 알림을 표시하려고 시도하지만 알림 권한을 요청하지 않으면 자동으로 실패합니다. 다른 목적으로 권한을 보유한 경우 Google이 생성 한 알림을 즉시 닫습니다.

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

사용 예 :


3
@rynah 방금 스펙을 다시 살펴 보았고 맞습니다. history.pushState()and history.replaceState()메소드는 동 기적으로 history.state첫 번째 인수의 구조적 클론으로 설정 되었습니다. 조금 이상하지만 작동합니다. 지금 답변을 업데이트하고 있습니다.
Jeremy Banks

40
이것은 너무 잘못입니다! 이 API는 이런 식으로 사용되지 않습니다.
Fardin K.

209
Firefox에서 pushState를 구현 한 사람은이 핵에 대한 자부심과 반감을 혼동하는 느낌이 듭니다. 잘 했어.
Justin L.

기능과 같은 일부 객체 유형에서는 pushState 또는 Notification hack이 작동하지 않습니다
Shishir Arora

323

내장 된 것이 없다면 다음을 시도해보십시오.

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

20
JQuery 솔루션은 DOM 요소에 대해서만 작동하지만 객체는 아닙니다. Mootools의 제한은 동일합니다. 모든 객체에 대해 일반적인 "복제본"이 있었으면 좋겠다 ... 재귀 솔루션은 무엇이든 작동해야합니다. 아마도 갈 길이야.
jschrab

5
복제되는 객체에 매개 변수가 필요한 생성자가 있으면이 함수가 중단됩니다. "var temp = new Object ()"로 변경할 수 있고 모든 경우에 작동합니까?
Andrew Arnott

3
Andrew, var temp = new Object ()로 변경하면 복제본에 원본 객체와 동일한 프로토 타입이 없습니다. 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder

1
limscoder의 답변과 비슷하게 생성자를 호출하지 않고이를 수행하는 방법에 대한 아래 답변을 참조하십시오. stackoverflow.com/a/13333781/560114
Matt Browne

3
하위 부분에 대한 참조가 포함 된 개체 (예 : 개체 네트워크)의 경우 작동하지 않습니다. 두 개의 참조가 동일한 하위 개체를 가리키는 경우 복사본에는 서로 다른 두 개의 복사본이 포함됩니다. 재귀 참조가있는 경우 함수는 절대 종료되지 않습니다 (적어도 원하는 방식은 아닙니다 :-) 이러한 일반적인 경우 이미 복사 된 객체의 사전을 추가하고 이미 복사했는지 확인해야합니다 ... 간단한 언어를 사용하면 프로그래밍이 복잡해집니다
virtualnobi

153

한 줄의 코드로 객체를 복제 (심층 복제 아님)하는 효율적인 방법

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

82
이것은 재귀 적으로 복사하지 않으므로 객체 복제 문제에 대한 해결책을 제공하지 않습니다.
mwhite

5
이 방법은 몇 가지를 테스트했지만 _.extend ({}, (obj))는 BYFAR이 가장 빠릅니다. 예를 들어 JSON.parse보다 20 배 빠르며 Object.assign보다 60 % 빠릅니다. 모든 하위 오브젝트를 아주 잘 복사합니다.
Nico

11
@mwhite는 클론 클론과 딥 클론간에 차이가 있습니다. 이 답변은 실제로 복제하지만 딥 복제는 아닙니다.
Meirion Hughes 2016 년

57
작전은 딥 클론을 요구했다. 이것은 딥 클론을하지 않습니다.
user566245

9
이 방법 은 DEEP 복사본이 아닌 SHALLOW 복사본을 만듭니다 ! 이 때문에 그것은 완전히 잘못된 대답입니다 !
Bharata

97

암호:

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

3
무엇에 대한 var obj = {}obj.a = obj
neaumusic

5
이 기능을 이해하지 못합니다. 가정하자가 from.constructor있다 Date예를 들어. if두 번째 if테스트가 성공하고 함수가 다시 시작될 때 세 번째 테스트에 어떻게 도달 Date != Object && Date != Array합니까?
Adam McKee

1
@AdamMcKee javascript 인수 전달과 변수 할당이 까다로워 지기 때문 입니다. 이 접근법은 날짜 (실제로 두 번째 테스트로 처리됨)를 포함하여 훌륭하게 작동합니다 -jsfiddle.net/zqv9q9c6 .
brichins

1
@ NickSweeting : 시도-작동 할 수 있습니다. 그렇지 않은 경우 수정하고 답변을 업데이트하십시오. 그것이 커뮤니티에서 작동하는 방식입니다.)
Kamarey

1
이 함수는 테스트에서 정규 표현식을 복제하지 않으며 조건 "from.constructor! = Object && from.constructor! = Array"는 Number, Date 등과 같은 다른 생성자에 대해 항상 true를 반환합니다.
aMarCruz

95

이것이 내가 사용하는 것입니다 :

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

8
이것은 옳지 않은 것 같습니다. cloneObject({ name: null })=>{"name":{}}
Niyaz

13
이것은 자바 스크립트의 또 다른 바보 같은 문제 typeof null > "object"이지만 Object.keys(null) > TypeError: Requested keys of a value that is not an object.조건을 다음과 같이 변경하십시오.if(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us

상속 된 열거 가능한 obj 속성을 복제본에 직접 할당 하고 obj 가 일반 Object 라고 가정합니다 .
RobG

또한 숫자 키를 사용하여 객체로 변환되는 배열을 엉망으로 만듭니다.
블레이드

null을 사용하지 않으면 문제가되지 않습니다.
Jorge Bucaran

78

성능 별 딥 카피 : 최고에서 최고로 순위가 매겨 짐

  • 재 할당 "="(문자열 배열, 숫자 배열-전용)
  • 슬라이스 (문자열 배열, 숫자 배열-전용)
  • 연결 (문자열 배열, 숫자 배열-전용)
  • 사용자 정의 기능 : for-loop 또는 recursive copy
  • 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 보다 성능이 뛰어납니다.

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

객체 배열 딥 카피 (2 개 이상의 레벨-참조 포인터) :

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가 더 나은 성능을 제공하는 경우 :


나는 몇 가지를 테스트했고 _.extend ({}, (obj))는 BYFAR이 가장 빠르다. 예를 들어 JSON.parse보다 20 배 빠르며 Object.assign보다 60 % 빠릅니다. 모든 하위 오브젝트를 아주 잘 복사합니다.
Nico

4
모든 예는 얕고 한 수준입니다. 이것은 좋은 대답이 아닙니다. 문제는 클로닝, 즉 적어도 두 가지 수준 에 관한 것이었다 .
Karl Morrison

1
딥 카피는 다른 객체에 대한 참조 포인터를 사용하지 않고 객체 전체를 복사하는 경우입니다. jQuery.extend ()와 같은 "객체 배열의 딥 카피"섹션의 기술과 "재귀적인"사용자 정의 함수는 "적어도 두 레벨"인 객체를 복사합니다. 따라서 모든 예제가 "한 레벨"사본 인 것은 아닙니다.
tfmontague

1
사용자 지정 복사 기능이 마음에 들지만 null 값을 제외해야합니다. 그렇지 않으면 모든 null 값이 개체로 변환됩니다.out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi

2
@HossamMourad-버그는 2 월 1 일 Josi에 의해 수정되었으며 (위의 설명에서) 대답을 올바르게 업데이트하지 못했습니다. 이 버그로 인해 코드베이스가 리 팩터되었습니다.
tfmontague

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

좋은 대답이지만 순환 참조에서는 실패합니다.
Luke

59

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

JSBEN.CH 성능 벤치마킹 놀이터 1 ~ 3 http://jsben.ch/KVQLd JavaScript에서 성능 딥 카피 객체


5
Object.assign()딥 카피를 수행하지 않습니다
Roymunson

1
이에 대한 벤치 마크를 추가해야합니다. 그것은 매우 도움이 될 것입니다
jcollum

배열을 포함하는 객체에서 "만든 메소드"를 사용할 때 pop () 또는 splice ()를 사용할 수 없었는데 그 이유를 이해할 수 없습니까? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();그것은 던져 : TypeError: tmp.title.pop is not a function(물론 pop () 그냥 내가 경우 잘 작동 do let tmp = data하지만 데이터에 영향을주지 않고 tmp를 수정할 수 없습니다)
hugogogo

이봐, 마지막 예가 틀렸어 제 생각에는 잘못된 예에는 _cloneDeep이 아닌 _clone을 사용해야합니다.
kenanyildiz '12

이 생성 된 메소드 (2.)는 배열에서 작동하지 않습니다.
Toivo Säwén

57

있다 ( "복제"라고합니다) 라이브러리 아주 잘이 작업을 수행합니다. 내가 아는 임의의 객체에 대한 가장 완벽한 재귀 복제 / 복사를 제공합니다. 또한 다른 답변으로는 다루지 않는 순환 참조를 지원합니다.

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

(면책 조항 : 나는 도서관의 저자입니다.)


3
npm clone은 임의로 중첩 된 객체를 복제하는 데 매우 중요합니다. 이것이 정답입니다.
Andy Ray

말한 것에 비해 lib의 성능은 무엇 JSON.parse(JSON.stringify(obj))입니까?
pkyeck

다음 은 더 빠른 옵션이 있음을 나타내는 라이브러리 입니다. 그래도 테스트하지 않았습니다.
pvorb

좋은 해결책이며 이것은 JSON 참조와 달리 순환 참조를 지원합니다.
Luke

55

Cloning 객체는 JS에서 항상 관심사가되었지만 ES6 이전의 모든 것이 었습니다. 아래에 JavaScript로 객체를 복사하는 다른 방법을 나열하고 아래에 객체가 있고 그 사본을 원한다고 상상해보십시오.

var obj = {a:1, b:2, c:3, d:4};

원점을 변경하지 않고이 객체를 복사하는 방법은 몇 가지가 있습니다.

1) ES5 +, 간단한 기능으로 복사하기 :

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 +, JSON.parse 및 JSON.stringify를 사용합니다.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs :

var  deepCopyObj = angular.copy(obj);

4) jQuery :

var deepCopyObj = jQuery.extend(true, {}, obj);

5) 밑줄 Js & Loadash :

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

도움이 되길 바랍니다 ...


2
밑줄의 클론은 현재 버전에서 딥 클론이 아닙니다
Rogelio

감사. yes를 밑줄에 대한 새 문서로 사용 ... clone_.clone (object) 제공된 일반 객체의 얕게 복사 된 복제본을 만듭니다. 중첩 된 객체 또는 배열은 복제되지 않고 참조로 복사됩니다. _.clone ({이름 : 'moe'}); => {이름 : 'moe'};
Alireza

59
Object.assign딥 카피 하지 않습니다 . 예 : var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". 이것이 깊은 사본 y.a.b이라면 여전히 c그렇습니다 d. 그러나 지금 입니다.
kba

8
Object.assign ()은 첫 번째 수준의 속성 만 복제합니다!
haemse

5
cloneSO () 함수 란 무엇입니까?
pastorello

53

나는 이것이 오래된 게시물이라는 것을 알고 있지만 이것이 넘어 질 다음 사람에게 도움이 될 것이라고 생각했다.

객체를 객체에 할당하지 않는 한 메모리에서 참조를 유지하지 않습니다. 따라서 다른 객체와 공유하려는 객체를 만들려면 다음과 같이 팩토리를 만들어야합니다.

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

16
이 대답은 실제로 다음과 같은 질문과 관련이 있기 때문에 실제로는 관련이 없습니다. 팩토리를 사용하고 싶지 않은 이유는 인스턴스화 후 b가 추가 데이터 (예 : 사용자 입력)로 초기화되었을 수 있기 때문입니다.
Noel Abrahams

12
이것이 실제로 질문에 대한 답변이 아니라는 것이 사실이지만, 여기에 오는 많은 사람들이 실제로 묻는 것이 의심되는 질문에 대한 답변이기 때문에 여기에있는 것이 중요하다고 생각합니다.
세미콜론

8
죄송합니다, 왜 그렇게 많은 투표를했는지 이해가되지 않습니다. 객체 복제는 매우 명확한 개념이며, 다른 객체에서 객체를 원추하며 팩토리 패턴으로 새 객체를 만드는 것과는 별개입니다.
opensas

2
이는 사전 정의 된 객체에 적용되지만이 방식으로 "복제"하면 원래 객체에 추가 된 새 속성이 인식되지 않습니다. a를 만들면 a에 새 속성을 추가 한 다음 b를 만듭니다. b에는 새로운 재산이 없습니다. 기본적으로 팩토리 패턴은 새로운 특성으로 변경할 수 없습니다. 이것은 패러다임 복제가 아닙니다. 참조 : jsfiddle.net/jzumbrun/42xejnbx
Jon

1
나는 이것을 사용하는 대신 const defaultFoo = { a: { b: 123 } };갈 수 const defaultFoo = () => ({ a: { b: 123 } };있고 문제가 해결 되기 때문에 일반적으로 좋은 조언이라고 생각합니다 . 그러나 그것은 실제로 질문에 대한 답변이 아닙니다. 전체 답변이 아니라 질문에 대한 의견으로 이해하는 것이 좋습니다.
Qaribou에서 Josh

48

사용중인 경우 Underscore.js 라이브러리에는 복제 방법이 있습니다.

var newObject = _.clone(oldObject);

24
lodash는 cloneDeep 메소드를 가지고 있으며,이를 복제하기위한 또 다른 매개 변수를 지원합니다. lodash.com/docs#clonelodash.com/docs#cloneDeep
17

12
@opensas는 동의했다. Lodash는 일반적으로 밑줄보다 우수합니다
nha

7
유틸리티 라이브러리의 .clone(...)메소드에 대한 단 한 줄의 참조 인이 답변과 다른 모든 답변을 삭제하는 것이 좋습니다. 모든 주요 도서관에는 해당 도서관이 있으며, 해당 도서관을 사용하지 않는 대부분의 방문객에게는 반복되는 간략한 상세 답변이 유용하지 않습니다.
Jeremy Banks

41

다음은 생성자가 매개 변수를 요구 한 경우에도 작동하는 ConroyP의 답변 버전입니다.

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

이 기능은 내 simpleoo 라이브러리 에서도 사용할 수 있습니다.

편집하다:

보다 강력한 버전이 있습니다 (Justin McCandless 덕분에 이제 순환 참조도 지원함).

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

30

다음은 동일한 객체의 두 인스턴스를 만듭니다. 나는 그것을 발견하고 현재 그것을 사용하고 있습니다. 간단하고 사용하기 쉽습니다.

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

이 답변에 문제가 있습니까? 독립형 솔루션이지만 더 간단하지만 더 유용합니다. 그러나 jQuery 솔루션이 더 유명합니다. 왜 그런 겁니까?
ceremcem

예, 알려주십시오. 의도 한대로 작동하는 것 같습니다. 어딘가에 숨겨진 파손이 있으면 다른 솔루션을 사용해야합니다.
nathan rogers

4
간단한 객체의 경우 Chrome에서 주어진 답변보다 약 6 배 느리고 객체의 복잡성이 커짐에 따라 훨씬 느려집니다. 확장 성이 뛰어나고 응용 프로그램을 매우 빠르게 병목시킬 수 있습니다.
tic

1
데이터가 필요하지 않고 진행 상황을 이해하기 만하면됩니다. 이 복제 기술은 전체 오브젝트를 문자열로 직렬화 한 다음 해당 문자열 직렬화를 구문 분석하여 오브젝트를 빌드합니다. 본질적으로 그것은 단지 약간의 메모리를 재배 열하는 것 (보다 정교한 클론이하는 것)보다 훨씬 느릴 것입니다. 그러나 그렇게 말하면, "중간 규모"의 정의에 따라 중소 규모 프로젝트의 경우 1000 배나 효율성이 떨어지는 사람은 누구입니까? 객체가 작고 객체를 복제하지 않으면 실제로 아무것도 1000 톤이 아닙니다.
machineghost

3
또한이 방법은 메소드 (또는 JSON에서 허용되지 않는 모든 항목)를 잃습니다. 또한 JSON.stringify는 Date 객체를 문자열로 변환합니다 ...
Mr MT

22

Crockford는이 기능을 사용하도록 제안하고 선호합니다.

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

var newObject = object(oldObject);

간결하고 예상대로 작동하며 라이브러리가 필요하지 않습니다.


편집하다:

에 대한 폴리 필 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

9
내가 틀렸다면 나를 교정하되, Crockford가 프로토 타입 상속을위한 기능을 얻지 않습니까? 클론에 어떻게 적용됩니까?
Alex Nolasco

3
그렇습니다. 저는이 논의가 두려웠습니다. 복제, 복사 및 프로토 타입 상속의 실제 차이점은 무엇입니까? 각각을 사용해야 할 때와이 페이지에서 실제로 어떤 기능을 수행하고 있습니까? "javascript copy object"를 인터넷 검색하여이 SO 페이지를 찾았습니다. 내가 실제로 찾고 있던 것은 위의 기능이므로 다시 공유했습니다. 내 추측은 asker도 이것을 찾고 있었다.
Chris Broski

51
복제 / 복사와 상속의 차이점은 oldObject의 속성을 변경하면 newObject에서도 속성이 변경된다는 것입니다. 복사하면 newObject를 변경하지 않고 oldObject로 원하는 것을 수행 할 수 있습니다.
Ridcully

13
이로 인해 hasOwnProperty 검사가 중단되어 개체를 복제하는 매우 해킹 된 방법으로 예기치 않은 결과가 발생합니다.
Corban Brook

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody

22

Lodash에는 멋진 _.cloneDeep (value) 메소드가 있습니다.

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

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

5
유틸리티 라이브러리의 .clone(...)메소드에 대한 단 한 줄의 참조 인이 답변과 다른 모든 답변을 삭제하는 것이 좋습니다. 모든 주요 도서관에는 해당 도서관이 있으며, 해당 도서관을 사용하지 않는 대부분의 방문객에게는 반복되는 간략한 상세 답변이 유용하지 않습니다.
Jeremy Banks

더 쉬운 방법은을 사용하는 것 _.merge({}, objA)입니다. lodash 만 처음부터 객체를 변경하지 않으면 clone함수가 필요하지 않습니다.
Rebs 2019

7
Google은 JS 객체 복제를 검색합니다. Lodash를 사용하고 있으므로이 답변은 나와 관련이 있습니다. 답변에 "wikipedia deletedist"를 모두 보내지는 마십시오.
Rebs

2
노드 9에서 JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects))는 _.deepClone (arrayOfAbout5KFlatObjects)보다 훨씬 빠릅니다.
Dan Dascalescu

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

17
메소드의 문제점은 obj 내에 서브 오브젝트가있는 경우 모든 서브 오브젝트의 값이 아니라 참조가 복제된다는 것입니다.
Kamarey

1
하위 오브젝트가 깊게 복제되도록 재귀 적으로 작성하십시오.
fiatjaf

궁금한 점은 ... 복제 변수가 원래 객체의 속성에 대한 포인터를 가지지 않습니까? 그것은 새로운 메모리 할당 보인다 때문에
Rupesh 파텔

3
예. 이것은 얕은 사본 일 뿐이므로 복제본은 원본 객체가 가리키는 것과 정확히 동일한 객체를 가리 킵니다.
Mark Cidade 2013

이것은 답이 아닙니다. 말 그대로 다른 객체에 대한 참조로 객체를 채우는 것입니다. 소스 객체를 변경하면 "복제본"이 변경됩니다.
Shawn Whinnery

19

얕은 복사본 한 줄짜리 ( 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 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

6
단순한 객체에는 적합하지만 속성 값만 복사합니다. 프로토 타입 체인을 건드리지 않으며 Object.keys이를 사용 하면 열거 할 수없고 상속 된 속성을 건너 뜁니다. 또한 직접 할당을 수행하면 속성 설명자가 손실됩니다.
매트 Bierner

프로토 타입도 복사하면 열거 할 수없는 속성과 속성 설명 자만 누락됩니다. 꽤 좋아요 :)
sam

성능을 제외하고, 이것은 객체를 얕게 복사하는 정말 편리한 방법입니다. 나는 종종 이것을 사용하여 React 컴포넌트의 구조 지정 과제에서 가짜 휴식 속성을 정렬합니다.
mjohnsonengr

17

AngularJS가 언급 하지 않고 사람들이 알고 싶어 할 것이라고 생각 했기 때문에 ...

angular.copy 또한 객체와 배열을 딥 카피하는 방법을 제공합니다.


또는 jQiery extend와 같은 방식으로 사용될 수 있습니다.angular.extend({},obj);
Galvani

2
@Galvani : 주목해야한다 jQuery.extend그리고 angular.extend모두 얕은 복사이다. angular.copy깊은 사본입니다.
Dan Atkinson

16

배열 형 객체에는 아직 이상적인 딥 클론 연산자가없는 것 같습니다. 아래 코드에서 알 수 있듯이 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)

14
다른 사람들이 Resig의 답변에 대한 의견에서 지적했듯이 배열과 같은 객체를 복제하려면 확장 호출에서 {}를 []로 변경하십시오 (예 : jQuery.extend (true, [], obj)
Anentropic

15

당신의 목표가 "일반 자바 스크립트 객체를 복제하는 것"인지 아닌지에 따라 두 가지 좋은 답변이 있습니다.

또한 소스 객체에 대한 프로토 타입 참조가없는 완전한 클론을 생성하려는 의도가 있다고 가정 해 봅시다. 완전한 클론에 관심이 없다면 다른 답변 (Crockford의 패턴)에서 제공되는 많은 Object.clone () 루틴을 사용할 수 있습니다.

평범한 오래된 JavaScript 객체의 경우 현대 런타임에서 객체를 복제하는 가장 좋은 방법은 간단합니다.

var clone = JSON.parse(JSON.stringify(obj));

소스 객체는 순수한 JSON 객체 여야합니다. 즉, 모든 중첩 속성은 스칼라 여야합니다 (예 : 부울, 문자열, 배열, 객체 등). RegExp 또는 Date와 같은 기능이나 특수 객체는 복제되지 않습니다.

효율적입니까? 그렇습니다. 우리는 모든 종류의 복제 방법을 시도했으며 이것이 가장 효과적입니다. 일부 닌자가 더 빠른 방법을 사용할 수 있다고 확신합니다. 그러나 나는 우리가 한계 이득에 대해 이야기하고 있다고 생각합니다.

이 방법은 간단하고 구현하기 쉽습니다. 편의 기능으로 감싸고 실제로 게인을 짜야하는 경우 나중에 계속하십시오.

이제 평범하지 않은 JavaScript 객체의 경우 실제로 간단한 대답이 없습니다. 실제로 JavaScript 함수의 동적 특성과 내부 객체 상태로 인해 발생할 수 없습니다. 내부에 함수가있는 JSON 구조를 깊게 복제하려면 해당 함수와 내부 컨텍스트를 다시 작성해야합니다. 그리고 JavaScript에는 단순히 표준화 된 방법이 없습니다.

이 작업을 다시 수행하는 올바른 방법은 코드 내에서 선언하고 재사용하는 편리한 방법을 사용하는 것입니다. 편리한 방법은 자신의 객체에 대한 이해가 주어 지므로 새 객체 내에서 그래프를 올바르게 다시 만들 수 있습니다.

우리는 우리 자신이 작성했지만 내가 본 가장 일반적인 접근 방식은 다음과 같습니다.

http://davidwalsh.name/javascript-clone

이것이 올바른 생각입니다. 저자 (David Walsh)는 일반화 된 기능의 복제에 대해 언급했다. 이것은 사용 사례에 따라 선택할 수 있습니다.

주요 아이디어는 유형별로 함수 (또는 프로토 타입 클래스)의 인스턴스화를 특수하게 처리해야한다는 것입니다. 여기에서는 RegExp 및 Date에 대한 몇 가지 예를 제공했습니다.

이 코드는 간단 할뿐만 아니라 읽기도 쉽습니다. 확장하기가 매우 쉽습니다.

이것이 효율적입니까? 그렇습니다. 목표는 진정한 딥 카피 클론을 생성하는 것이므로 소스 객체 그래프의 멤버를 따라야합니다. 이 접근 방식을 사용하면 처리 할 하위 멤버와 사용자 정의 유형을 수동으로 처리하는 방법을 정확하게 조정할 수 있습니다.

그래서 당신은 간다. 두 가지 접근 방식. 둘 다 내 관점에서 효율적입니다.


13

이것은 일반적으로 가장 효율적인 솔루션은 아니지만 필요한 작업을 수행합니다. 아래의 간단한 테스트 사례 ...

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

11

AngularJS

앵귤러를 사용하는 경우 에도이 작업을 수행 할 수 있습니다

var newObject = angular.copy(oldObject);

11

나는 여기서 가장 큰 표를 얻은 답변에 동의하지 않습니다 . 재귀 깊은 클론 입니다 훨씬 빠르게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
}

2
이 방법을 좋아했지만 날짜를 올바르게 처리하지 못합니다. 뭔가를 추가하는 것을 고려 if(o instanceof Date) return new Date(o.valueOf());`널 (null) 검사 후
루이스

순환 참조에서 충돌이 발생합니다.
Harry

안정적인 최신 Firefox에서는 Jsben.ch 링크의 다른 전략보다 훨씬 더 길어집니다. 그것은 잘못된 방향으로 다른 사람들을 이깁니다.
WBT

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

10

ECMAScript 6 또는 트랜스 필러를 사용할 수있는 경우에만 해당됩니다 .

풍모:

  • 복사하는 동안 게터 / 세터를 트리거하지 않습니다.
  • 게터 / 세터를 유지합니다.
  • 프로토 타입 정보를 유지합니다.
  • 객체 리터럴기능적 OO 쓰기 스타일 모두에서 작동 합니다.

암호:

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

9

다음은 모든 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;
};

프리미티브를 랩퍼 오브젝트로 변환하지만 대부분의 경우 좋은 해결책은 아닙니다.
Danubian Sailor

@DanubianSailor-나는 생각하지 않습니다 ... 시작에서 즉시 프리미티브를 반환하는 것처럼 보이며 반환 된 래퍼 객체로 바꾸는 아무것도하지 않는 것 같습니다.
Jimbo Jonny
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.