게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌25 - JavaScript - 8년 전 등록 / 4년 전 수정

객체의 복사

안녕하세요, 이번 시간에는 객체의 복사에 대해서 알아보겠습니다.

일반 문자열, 숫자, 불린 같은 경우는

var string = 'hello';
var copy = string;
console.log(copy); // 'hello'

하면 바로 복사가 되는데요. 객체(배열, 일반 객체, 함수)도 마찬가지이긴 합니다만, 복사된 값을 조작할 때 차이가 있습니다. 다시 위의 예를 보면

var string = 'hello';
var copy = string;
console.log(copy); // 'hello'
copy = 'hi';
console.log(string); // 'hello'

이번엔 copy 값을 hi로 바꿔봤습니다. 기존 string 값은 변화가 없겠죠? copy에 값만 복사해 줬을 뿐 더이상 연관이 없으니까요. 객체의 경우를 볼까요?

var array = ['a', 'b', 'c'];
var ref = array;
ref[0] = 'd';
console.log(array); // ['d', 'b', 'c']

ref변수에 array 배열을 대입했는데요. ref의 첫 번째 항목을 변경했더니 array의 첫 번째 항목도 같이 변경되었습니다. 이상하죠? 이게 객체의 특징입니다. 문자열, 숫자, 불린을 제외한 객체는 다른 변수에 대입할 때 을 복사하는 게 아니라 참조(메모리의 주소)를 복사합니다.

변수는 모두 메모리에 저장됩니다. 그리고 대입을 하면 변수의 이름은 저장된 메모리의 주소를 가리키게 됩니다. 메모리 어딘가에 ['a', 'b', 'c']를 저장한 게 있다면 그것을 array와 ref변수 두 개가 가리키고 있는겁니다. 값은 하나인데 변수는 여러 개일 수 있는거죠. 이걸 참조라고 부릅니다. 따라서 ref변수가 바뀌면 array 값도 같이 바뀝니다. 이걸 방지하려면 메모리에 ['a', 'b', 'c'] 두 개를 만들어서 따로따로 운영되게 만들어주어야 합니다.

var array = ['a', 'b', 'c'];
var copy = Array.prototype.slice.call(array);
copy[0] = 'd';
console.log(array); // ['a', 'b', 'c']

좀 독특한 방법인데요. 지난 시간에 배운 call 함수를 사용했습니다. copy(복사)라고 하며, 이제는 copy변수가 바뀌어도 array 변수에 영향을 미치지 않습니다. slice함수는 배열을 자르는 함수인데 어떻게 복사가 되냐고요? array.slice(0)이라고 생각하면 됩니다. array를 자르는 데 0개만큼 자르니까 결국 그대로 반환하는거죠.

위의 경우는 Array.prototype.slice.call(array) 대신 array.slice(0)을 해도 되지만, arguments 같은 것(유사배열이라서 배열의 메소드를 사용할 수 없습니다)을 복사할 때를 생각하면 Array.prototype.slice.call로 통일하는 게 좋습니다.

copy에는 두 가지가 있습니다. shallow copy(얕은 복사)와 deep copy(깊은 복사)인데요. shallow copy는 가장 상위 객체만 새로 생성되고 내부 객체들은 참조 관계인 경우를 의미합니다. deep copy는 내부 객체까지 모두 새로 생성된 것을 의미합니다. 위에 copy 변수는 내부에 객체가 없기 때문에 shallow인지 deep인지 정하기 애매합니다.

var array = [{ name: 'a' }, { name: 'b' }, { name: 'c' }];
var shallow = Array.prototype.slice.call(array);
shallow[0].name = 'd';
shallow[1] = 'e';
console.log(array); // [{ name: 'd' }, { name: 'b' }, { name: 'c' }]

위의 예제에서는 shallow[0].name은 array에 적용되지만, shallow[1]은 적용되지 않습니다. 바로 이게 얕은 복사입니다. 가장 상위 객체에 직접 변경하는 것은 적용되지 않지만 내부 객체들을 참조로 이어져있기 때문에 적용되는 겁니다. 쉽게 외우시려면 가장 바깥 껍데기만 복사되는 복사가 얕은 복사라고 생각하시면 됩니다.

배열을 깊은 복사하는 방법은 아래의 객체 깊은 복사에서 함께 다룹니다.

일반 객체를 복사하는 것에도 shallow copy와 deep copy가 있습니다. shallow는 그냥 대입만 해주면 되니까 deep copy하는 방법을 살펴보겠습니다. 상속이 없는 일반 객체만 해당됩니다.

function copyObj(obj) {
  var copy = {};
  if (Array.isArray(obj)) {
    copy = obj.slice().map((v) => {
      return copyObj(v);
    });
  } else if (typeof obj === 'object' && obj !== null) {
    for (var attr in obj) {
      if (obj.hasOwnProperty(attr)) {
        copy[attr] = copyObj(obj[attr]);
      }
    }
  } else {
    copy = obj;
  }
  return copy;
}
var obj = { a: 1, b: 2, c: [{ d: null, e: 'f' }] };
var obj2 = copyObj(obj);
obj2.a = 3;
obj2.c[0].d = true;
console.log(obj.a); // 1
console.log(obj.c[0].d); // null

copyObj이란 함수를 새로 만들었습니다. 결과를 보면 obj과 obj2가 따로 놀죠. 여기서 for~inhasOwnProperty를 처음 보실 겁니다. for ~ in 은 obj안의 키를 순서대로 반복합니다. (주의할 점은 키가 숫자면 순서대로 반복되지 않는다는 겁니다. 숫자로된 키가 없을 때 사용하세요) 문제는 prototype에 있는 상속된 객체의 속성도 반복되기 때문에 obj.hasOwnProperty(keyName)메소드로 상속되지 않은 자기의 속성만 반복되도록 제한하는 겁니다. hasOwnProperty 부분을 빼고 복사하면, 왜 그 부분이 필요한지 이유를 알 수 있을 겁니다.

함수는 복사할 때 bind를 하면 됩니다. this를 기존 함수와 같게 하면 똑같게 함수가 복사됩니다.

var func = function () {
  alert('hi');
};
func2 = func.bind(this);
func2(); // 'hi'

이상으로 객체의 복사편을 마치겠습니다. 다음 시간에는 디자인 패턴에 대해서 알아보겠습니다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

20개의 댓글이 있습니다.
3년 전
ㅋㅋㅋ 익명님의 엄청난 분노에 웃고갑니다. 제로초님이 나온 초등학교는 제로초!
3년 전
저 아래 시비거는 애 진짜 잡아다가 애미애비 칼로 사지 찢는 거 눈앞에서 보여주고 싶네 씹병신 개찐따새끼 만나서 반 패죽이고 걍 징역가고싶네 아
3년 전
진정하세요 ㅎㅎ
4년 전
제로초님 배열과 내부 객체들은 어떻게 deep copy하나요? JSON.parse안에 제이손 문자열 변환시키는거까지 했는데도 참조가 계속되네요.
4년 전
참조가 끊겨야 정상입니다. 참조가 계속되는 예제를 적어주세요.
4년 전
const dummy = [
{name: 1, age: 2},
{name: 2, age: 3},
{name: 3, age: 4},
{name: 4, age: 5},
{name: 5, age: 6},
{name: 6, age: 7},
]

const copy = JSON.parse(JSON.stringify(dummy));

이렇게 하면 복사 되는거아닌가요??
4년 전
네 복사됩니다. 그리고 내부객체에 대한 참조는 모두 끊겨있지요.
4년 전
하지만 원래데이터 바꾸니 copy데이터도 똑같이 바뀌더군요...
4년 전
그럴리가 없습니다. 그렇게 바뀌는 예제를 간단히 만들어 올려주세요.
4년 전
const copy_use_appr_arr = JSON.parse(JSON.stringify(use_api_appr_admin));
console.log("copy_use_appr_arr", copy_use_appr_arr);

const use_api_appr_arr = use_api_appr_admin.filter((item) => {
return item.stat === sele_value;
});
이렇게하니깐 참조가되네요.
4년 전
use_api_appr_admin를 복사하신건데 use_api_appr_admin을 수정하는 부분이 없습니다. 원본이 수정이 안 되었으므로 copy_use_appr_arr도 수정되는 것이 없습니다. 또한 예시로 filter를 사용해주셨는데 filter는 원본을 건드리지 않고 새로운 객체를 만들어내는 함수라서 참조 예시와는 관계가 없습니다.
4년 전
아... 제가 react로 하고있는데 setUse_api_appr_admin(use_api_appr_arr ) 이렇게 하니깐 아예 바뀌네요. ㅋㅋ 이렇게하면 바뀔수밖에없으니깐 ㅠㅠ 죄송합니다. 제가 오해를 했었네요.
4년 전
https://www.zerocho.com/category/JavaScript/post/573dbc9370ba9c603052cc9a

Object객체 강의를 다시 보고 왔습니다.

부모의 속성까지 복사할 가능성이 있어서 hasOwnProperty로 '복사하려는Object만의 속성'인지 확인하는 거군요.

감사합니다.
4년 전
안녕하세요?
강의 잘 보고있습니다. 👍👍

참조가 아닌 복사를 하기 위해서
var copy = {}; 새로운 객체를 만들고(새로운 메모리)

거기에
1. 배열일 때
2. 객체일 때
3. else일 때

map, = 를 이용해서 값을 복사해서 새로 만든 객체에 채워 넣은 후

새로 만든 객체 리턴

이런 과정을 거치는 거군요.


본문 내용 중에 다음 부분이 이해가 안돼서 질문드립니다.

====================================================
문제는 prototype에 있는 상속된 객체의 속성도 반복되기 때문에 obj.hasOwnProperty(keyName)메소드로 상속되지 않은 자기의 속성만 반복되도록 제한하는 겁니다


==============================================================

if문 제거하고 돌려봤는데 잘 모르겠네요. 어떻게 확인하면 좋을까요?

} else if (typeof obj === 'object' && obj !== null) {
for (var attr in obj) {
copy[attr] = copyObj(obj[attr]);

// if (obj.hasOwnProperty(attr)) {
// copy[attr] = copyObj(obj[attr]);
// }
}

감사합니다.
4년 전
안녕하세요?

오타는 아니고
deep copy하는 방법을를 --> 방법을

요거 발견해서 알려드려요.

👍
5년 전
문맥을 잘 이해 못하고 저리 자기는 좀 아는척... 하는 댓글을 올리시는 양반들 간혹 보이더니 여기 왔다 갔네요. 저 밀레니엄 버그 손봤던 때 부터 웹으로 밥벌어먹고 살고있는데요. 얼마전 여기 보고 요즘 쭉~ 그냥 가볍게 한번씩 읽어 넘어가고 있는중인데요. 있잖아요... 그 뭐랄까 내가 미쳐 몰랐던 기초지식 같은 것들도 많더라구요. 쫌 더 앞에 글들을 보고 왔더라면... arguments 같은 유사 배열을 말한다는 것을 딱 알았겠지만, 저분은 그런거 한번을 보지 않고 '너 못났고 나 잘났다'라고 하고 싶어서 댓글을 쓴거일꺼예요. 거기서 Array.prototype.slice.call을 말하는 이유를 전혀 모르고 잘잘못 부터 따지려는 그런 어린이 때문에 맘상 않하셨으면 합니다. ㅎㅎ
5년 전
감사합니다.
5년 전
초짱 괴롭히지말고 보기시르면 딴데가서 공부해라 ㅡㅡ
5년 전
정 이분 블로그가 못미덥거나 하면 mdn 같은거 봐 이것들아...
공부머리없어서 쉽게 주서먹고싶어서 들어오는것들이 뭐가 틀렸다 어쩐다 말이 많아...
5년 전
이해 잘만 되는 구만 이상한 소리 하는 사람들이 있네~
5년 전
누가보면 돈내고 배우는줄 알겠네;
틀린게 있다면 그걸 수정하고 보완하는 과정에서 공부하면되는거지
그렇게 이 홈페이지가 맘에안들면 오지마세요.
5년 전
감사합니다 ㅠㅠ
5년 전
돈주고 파는 물건도 아니고 공유하는 글 가지고 허졉하다느니 문제가 있다니 책임감이 없다니 하는 사람들은 대체 무슨 심보죠? 설령 글에 문제가 있더라도 본인이 그정도 레벨되면 알아서 파악하고 필요한 부분만 거르던지 이런 부분이 잘못되었으니 수정해달라 하던지 해야지 비아냥거리기만 하니 참 기본 예의가 없는 사람들이네요. 블로그 애용하는 사람으로서 보다가 화가나서 한마디 하고 갑니다.
5년 전
감사합니다. 어딜 가나 저런 사람들은 항상 있기 마련이니 그러려니 합니다.
5년 전
허접하네
5년 전
예전부터 봐왔는데 알고리즘(그래프)이나 이런 딥한 부분에서 오류나 설명이 부족한 부분들이 있네요. 글 쓸때 정확히 알아보고 올려주세요. 님이 올리는 정보 때문에 남들이 피해를 볼 수도 있으니까요. array.slice(0) 가 가장 빠른 딥카피 방법인데, 무슨 문제가 있는지 내용이 명확히 있으면 좋겠습니다.
5년 전
알고리즘은 고려대 수업 그대로 옮긴겁니다. 오타가 있을 순 있어도 틀린 건 거의 없습니다. 또한 slice는 쉘로카피입니다.
5년 전
그래프 쪽에 보면 몇가지 경우의 수만 추가해서 실험해봐도 바로 틀리고 오류가 있으니까 그걸로 문제를 풀어보던 직접 한번 돌려보던 확인해 보세요. 그리고 글에 'Array.prototype.slice.call(array) 대신 array.slice(0)을 해도 되지만, arguments 같은 것(유사배열이라서 배열의 메소드를 사용할 수 없습니다)' 라고 되어있는데 아니고 배열 메소드 사용할 수 있던데요.
5년 전
그리고 소름돋는게.. 제가 쉘로카피 딥카피를 잘못 이해했는데 그게 본인글 때문인건 아시죠? var shallow = array; 는 쉘로카피고 var deep = Array.prototype.slice.call(array); 가 딥카피라면서요. 참조 / 쉘로카피 / 딥카피에 대해 이해를 잘못 하시고 글을 쓰셨었나봐요.
5년 전
aruguments는 배열 메서드 못 씁니다. 그래프도 그렇고 직접 틀린 점을 보여주세요. 말로만 틀렸다고 하지 마시고요. 그리고 제 글에 대한 책임은 아무도 지지 않습니다.
5년 전
본인 글에 'slice로 복사한건 유사배열이라서 배열의 메소드를 사용할 수 없다' 라고 되어 있는데 유사 배열 아닙니다. Array.isArray 한번 해보세요. 그리고 님이 올린 다익스트라쪽 코드 참조해서 그래프 문제 풀다보면 틀린 경우의 수가 나옵니다. 대학 수업 그대로라서 틀린게 없다는 논리는 좀 아닌것 같고요... 책임 지라는건 아니지만 이정도 큰 블로그를 운영하시고 책도 쓰시는 분이시면 글 쓸때 책임감은 가지고 최소한 직접 해보고 올리셔야죠.
5년 전
이건 제가 3년 전에 쓴 글인데 제가 왜 책임을 져야하죠? 그리고 제가 언제 slice로 복사한건 유사배열이라고 그랬죠? arguments가 유사 배열이라고 그랬죠. 글을 다시 읽으시고요. 다익스트라는 틀린 예제가 있으면 알려주시면 고칠테니까 틀린 예제를 주세요. 말로만 틀렸다고 하지 마시고요. 저는 지금까지 명확하게 안 되는 예시를 들어주시면 다 수정해드렸습니다.
6년 전
var func = function () {
alert('hi');
};
func2 = func.bind(this);
func2(); // 'hi'

위 예제에서 func2 = func와 func2 = func.bind(this)가 무엇이 다른가요? this를 내부적으로 사용할 경우에만 둘의 차이가 있는 건가요?
6년 전
func2 = func는 복사가 아니라 참조입니다. 즉 func의 속성으로 func.a = 'hi'를 넣으면 func2.a도 'hi'가 됩니다. (함수도 객체이기 때문이죠)
6년 전
객체 deep copy 예제를 돌려보고 있는데요
var obj = {a: 1, b: 2} 이렇게 copy를 하면
obj2는 { a: {}, b: {} } 이렇게 나옵니다.

var obj = {a: 'aaa', b: 'bbb'} 이렇게 copy를 하면
Maximum call stack size exceeded at copyObj 에러가 나와요!

copy[attr] = copyObj(obj[attr]); 이부분을
copy[attr] = obj[attr] 로 고치니까 잘 되네요

저 부분은 왜 에러가 발생하는 거죠??
6년 전
아 감사합니다. 코드에 에러가 있네요. 수정했습니다. copyObj를 다시 하는 것은 재귀를 사용해서 중첩된 객체까지 복사하기 위함입니다.
6년 전
var obj2 = JSON.parse(JSON.stringify(obj));
이 코드도 깊은 복제로 검색하다가 발견했는데요 위의 예제와 차이점이 있을까요?
6년 전
성능상 매우 안 좋습니다! 그리고 눈에 보이지 않는 prototype같은 게 날아가버릴 수 있어요.
6년 전
아하 감사합니다!
7년 전
얕은복사와 깊은복사는 이해했는데요..

복사와 복제는 같은 말인가요???
7년 전
비슷한 뜻일거라 생각합니다
7년 전
잘봤습니다. 한가지 질문이 있는데 혹시 for in문 안의 obj키가 숫자면은 어떻게 해야하나요..? 키를 다 string타입으로 바꿔서 해야하나요?
7년 전
일단 쉬운 방법은 없어보입니다. 단순히 문자열 '3', '2', '1' 이렇게 바꿔도 순서는 항상 1, 2, 3으로 나옵니다. 사실 객체의 키가 1,2,3이라면 처음부터 배열로 하는 게 맞습니다.
7년 전
for in 문이 순서 보장되지 않는걸로 알고있는데 "for ~ in 은 obj안의 키를 순서대로 반복합니다." 이부분에서 순서대로 반복합니다 라는 내용이 오해의 소지가 있는듯 합니다~!
7년 전
for in 문은 속성의 키가 숫자가 아닌 이상은 순서를 보장하긴 합니다. 이 부분 살짝 수정해보겠습니다.