javascript basics #2 오브젝트의 복사와 참조
자바스크립트에서 primitive 타입(number
, string
, boolean
, undefined
, ...)과 오브젝트를 다루다 보면 헷갈리기 쉬운 부분이 바로 복사와 참조이다.
오브젝트는 "참조에 의해(by reference)"
복사되고 저장된다. 반면 primitive 값들은 string
, number
, boolean
은 "값으로서(by value)"
할당되고 복사된다.
let primitiveExample = "Jake Seo";
let objectExample = {
name: "Jake Seo"
}
위와 같은 예제가 있을 때,
primitiveExample
변수는 값 자체를 저장한다.objectExample
변수는 오브젝트 자체를 저장하지 않고 메모리의 주소를 저장한다. 다른 말로 표현하면 메모리의 주소를 참조한다.만일, Object
자체를 다른 변수에 재할당하면 어떻게 될까?
let objectExample = {
name: "Jake Seo"
}
let objectExample2 = objectExample;
위와 같이 작성하면 objectExample2
는 독립된 새로운 오브젝트가 된다기보다 objectExample
이 참조하는 주소를 가리키는 또 다른 변수가 된다는 것에 주의해야 한다.
이는 값을 복사하는 것이 아닌 주소를 복사하는 것이 된다.
같은 주소를 참조하기 때문에
objectExample
의 내용이 바뀌면?objectExample2
의 내용도 바뀌게 된다.
자바스크립트에는 두가지의 비교 연산자가 있다.
==
이다. ==
는 오직 값이 같은지만 비교한다. ===
이다. ===
는 값 외에 타입도 같은지 비교한다.간단한 예를 들자면 문자열
'1'
과 숫자1
은==
에서는true
지만===
에서는false
이다.
오브젝트의 경우에는 이 두 연산자가 어떻게 적용될까?
오브젝트는 오직 두 오브젝트가 같은 주소를 가리킬 때만 비교 연산자에 의해 true
값이 출력된다.
위는 서로 다른 오브젝트를 두가지 비교 연산자를 통해 비교한 것인데, 둘 다
false
가 나옴을 볼 수 있다.
위는 같은 주소를 참조하게 만든 뒤에 비교 연산자를 통한 비교를 해본 것이다.
그렇다면 Object
와 primitive
타입을 비교하면 어떻게 될까? 이 경우에는 Object
가 primitive
타입으로 변하게 된다.
primitive
타입인string
과 비교했을 때,.toString()
메소드를 거친 후의 결과가 됨을 알 수 있다.
앞서 =
연산자를 통해 변수에 오브젝트를 다시 한번 할당하는 일은 단순히 그 변수에게 오브젝트의 참조 주소만 할당시킨다는 것을 배웠다. 그렇다면 진짜 오브젝트를 복사해서 독립적인 오브젝트 2개로 만들기 위해서는 어떻게 해야 할까?
기존 오브젝트 내부에 있는 프로퍼티를 순회하며 새 오브젝트에 값을 옮기는 방식이다.
let newObject = {};
for (let key in originalObject) {
newObject[key] = originalObject[key];
}
Object.assign()
메소드란?
위와 같은 메소드이다.
모든 enumerable own properties 를 source
로부터 target
으로 복사한 뒤에 target
오브젝트를 반환한다고 되어있다.
enumerable 하다는 표현이 약간 생소할 수 있는데, 오브젝트에는 기본적으로 for ... in
구문을 통해 나오는 부분이 있고, 그렇지 않은 부분이 있다.
위의 경우를 예로 들자면 'key'
라는 key
는 우리가 추가해줬기 때문에 enumerable 하지만, __proto__
하위에 속한 오브젝트 기본 내장 메소드들은 우리가 딱히 추가해주지 않아도 built-in
으로 존재하는 것들이다. 그런 항목들은 enumerable 하지 않다.
어찌됐든, Object.assign
을 사용해보면
위와 같이 복사가 잘 되었다.
그렇다면 주소값만 참조하는 것은 아닌지, 서로 다른 두 개의 주소가 만들어졌는지 확인하기 위해 두 오브젝트 프로퍼티 내부의 값을 조금 바꿔보자.
me
와 copiedMe
의 내용을 조금씩 바꿔서 동시에 출력해본 결과, 서로 다른 주소 값을 갖고 있음이 확인되었다.
임시로 company
라는 오브젝트 내부에 employee
라는 오브젝트가 있는 예제를 만들었다.
얼핏 보기에는 Object.assign()
메소드를 통해 문제없이 오브젝트가 복사된 것처럼 보이지만, 사실 오브젝트 내부의 프로퍼티인 employee
가 같은 오브젝트를 가리키고 있다.
기존의 company
의 employee
가 이직을 해서 이름이 Transferred
로 바뀌었다고 해보자.
분명 company
오브젝트의 employee
를 수정했는데, newCompany
오브젝트의 employee
마저 바뀌어버리는 것을 볼 수 있다.
둘은 같은 주소를 공유하고 있다. 이러한 경우에는 두가지 방법으로 완전한 복사를 할 수 있다.
위는 JSON.stringify()
메소드를 이용하여 오브젝트를 JSON String
형식으로 바꾼 뒤에 다시 JSON.parse()
메소드를 이용하여 오브젝트를 만들어주는 방식으로 깊은 복사(deep copy) 를 했다.
Lodash는 자바스크립트를 다루다보면 겪게되는 불편함에 대응하여 만들어진 라이브러리이다. Lodash cloneDeep라는 함수를 제공하여 사용자가 편하게 깊은 복사 를 할 수 있게 해준다.