-
복사란 무엇인가?
JavaScript에서 다음과 같은 방법으로 데이터를 복사할 수 있다.
let aaa = "철수" let bbb = aaa console.log(aaa) // 철수 console.log(bbb) // 철수
이런 경우 aaa는 원본, bbb는 복사본이 된다.
bbb = "영희" console.log(aaa) // 철수 console.log(bbb) // 영희
복사본의 값을 변경해서 재할당을 하면
원본인 aaa의 값은 변하지 않고,
복사본인 bbb의 값만 변한 것을 확인할 수 있다.
객체 복사
let child1 = { name: "철수", age: 8, school: "다람쥐초등학교" } let child2 = child1 child2 // {name: '철수', age: 8, school: '다람쥐초등학교'}
객체를 만들어보고, 이렇게 객체안에 key와 value가 있다고 가정해보자.
child2.name = "영희" child1 // {name: '영희', age: 8, school: '다람쥐초등학교'} child2 // {name: '영희', age: 8, school: '다람쥐초등학교'}
복사본인 child2의 name값을 변경하면 원본값도 변경되는 것을 알 수 있다.
이것은 자바스크립트의 데이터 타입 특징 때문에 생기는 문제이다!
객체를 복사한 뒤 값을 변경할 때, 사실 복사는 문제 없이 잘 됐지만
child1에 할당된 객체의 주소 값이 child2에도 동일하게 들어갔다.
왜냐하면 String, Number, Boolean 타입 데이터의 경우 변수에 값을 할당하면 `값 자체`가 저장되는데
객체와 배열의 경우, 그 값 자체가 아닌 ‘주소’가 저장되기 때문이다.
두 객체가 같은 주소를 가리키고 있기 때문에, child2의 값을 변경하면 child1의 값도 동일하게 변경된다.
그렇다면 어떻게 이러한 문제 없이 객체를 복사할 수 있을까?
결론부터 말하자면 `객체 복사라는 것은 존재하지 않는다`
`원본 객체와 같은 값을 가진 객체를 새로 만들 수 있을 뿐` 이라고 생각하면 쉽다.// child3에 child2 복사 let child3 = { name: child2.name, age: child2.age, school: child2.school } child3 // {name: '영희', age: 8, school: '다람쥐초등학교'}
// child3의 name 변경 child3.name = "훈이" child3 // {name: '훈이', age: 8, school: '다람쥐초등학교'} child2 // {name: '영희', age: 8, school: '다람쥐초등학교'}
위와 같이 child2 객체의 각 값을 꺼내서 child3의 각 key에 할당해준다.
그렇게 하면 결과적으로 child2를 child3에 복사한 것과 같은 모습이 되는데
엄밀히 말해서 복사는 아니지만, 우리는 이러한 것을 객체 복사라고 말한다.
객체 복사를 수행한 뒤, 복사본인 child3의 name 값을 변경해도
원본인 child2의 name 값이 변경되지 않고 유지되는 것을 확인할 수 있다.
그래서 나온것이 스프레드 연산자! 스프레드 연산자는 무엇일까?
스프레드 연산자! 데이터를 `복사`할때 쓴다!
스프레드 연산자! 여러데이터를 `객체`로 묶을때 쓴다!
객체를 복사할 때마다 원본 객체의 모든 값을 따로따로 가져오는 것은 번거로운 작업이다.
객체에 들어가 있는 데이터의 양이 많아지면 실질적으로 불가능에 가깝다....!
마침표 세 개를 연속해서 찍어주면, 해당 객체 내의 모든 값을 개별 요소로 분리할 수 있다.
스프레드 연산자를 이용해 객체를 복사하면, 복사본의 값을 변경해도 원본 값이 변경되지 않는다.
let child4 = { ...child2 } child4 // {name: '영희', age: 8, school: '다람쥐초등학교'}
중첩 객체 복사 with 스프레드 연산자
스프레드 연산자를 이용한 복사도 만능은 아니다.
객체 안에 객체가 값으로 들어가 있는 경우에는 제대로 복사가 되지 않는다.
let profile1 = { name: "철수", age: 8, school: "공룡초등학교", hobby: { first: "수영", second: "프로그래밍" } } let profile2 = { ...profile1 }
profile1.name = "영희" profile1 // {name: '영희', age: 8, school: '공룡초등학교', hobby: {…}} profile2 // {name: '철수', age: 8, school: '공룡초등학교', hobby: {…}}
스프레드 연산자를 이용해 객체 복사를 수행한 뒤 profile1의 name 값을 변경해주었지만
profile1의 name은 변경되지만 profile2의 name은 변경되지 않는다.
profile1.hobby.first = "축구"
여기서 영희의 첫번째 취미를 변경해 보았다.
profile1, profile2의 hobby를 보면 복사가 제대로 되지 않았다는 사실을 알 수 있다.
영희의 첫 번째 취미만 변경해주었는데 철수의 첫 번째 취미까지 변경되어 버린 문제가 생겼다.
결국 hobby라는 key에 대한 값도 `주소` 값으로 들어가 있기 때문에 발생하는 문제이다.
이것처럼 스프레드 연산자를 이용한 복사를*`얕은 복사(Shallow-Copy)`라고 한다.
얕은 복사는 depth 1의 깊이를 가진 데이터까지는 복사할 수 있지만,
depth 2 이상의 깊이를 가진 데이터는 복사하지 못한다.
깊은 복사
그렇다면 어떻게 depth 2 이상의 깊이를 가진 데이터를 복사하여 새로운 객체를 만들 수 있을까?
객체를 문자열의 형태로 바꾸고, 그 문자열을 다시 객체로 바꾸어 새로운 변수에 담아주면 됩니다.
`JSON.stringify`와 `JSON.parse`라는 메소드를 이용하면
객체/배열을 문자열로, 그리고 문자열을 객체/배열로 바꾸어 줄 수 있다.💡JSON이란?
JavaScript Object Notation의 약자.
Javascript 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 기반의 표준 포맷이다.// JSON.stringify JSON.stringify(profile1) // '{"name":"철수","age":8,"school":"공룡초등학교","hobby":{"first":"수영","second":"프로그래밍"}}'
// JSON.parse JSON.parse(JSON.stringify(profile1)) // {name: '철수', age: 8, school: '공룡초등학교', hobby: {first: '수영', second: '프로그래밍'}}
결과는 이렇게 나온다.
lodash
객체를 복사할 때마다 깊은 복사를 따로 해주기는 번거롭고 느릴 수 있는데
관련 작업을 도와주는 라이브러리가 많이 나와있다.
가장 많이 사용되는 기능 모음 라이브러리 중 하나인 lodash가 있다.
lodash
Lodash modular utilities.. Latest version: 4.17.21, last published: 4 years ago. Start using lodash in your project by running `npm i lodash`. There are 194840 other projects in the npm registry using lodash.
www.npmjs.com
lodash에서 제공하는 _.cloneDeep(value)을 사용하면 손쉽게 깊은 복사를 할 수 있다!
배열 복사
지금까지 객체 복사에 대해 설명했는데, 배열도 객체와 같은 방식으로 복사가 가능하다.
const aaa = ["철수", "영희", "훈이"] const bbb= [...aaa]
'📚 이론정리 > JS & TS' 카테고리의 다른 글
✏️ 인증, 인가, JWT토큰 (0) 2024.03.09 ✏️ HTTP 통신 (0) 2024.01.24 ✏️ Instanceof 연산자 (0) 2024.01.19 ✏️ 객체 동적 접근 대괄호 표기법([]) (0) 2024.01.17 ✏️ Reduce 함수 (1) 2024.01.03 댓글