본문 바로가기

책과 강연/모던 자바스크립트 DeepDive

모던 자바스크립트 Deep Dive 11장 : 원시 값과 객체의 비교

원시 타입과 객체 타입은 크게 세 가지 측면이 다르다.

1. 원시 타입은 변경 불가능하지만, 객체 타입은 변경  가능한 값이다.

2. 원시 값을 변수에 할당하면 메모리에 실제 값이 저장되나, 객체를 변수에 할당하면 메모리에는 참조 값이 저장된다.

3. 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달(pass by value)라 한다. 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달(pass by reference)라 한다.

 

이에 대해 더 자세하게 알아보자.

 

원시 값

불변성

원시 값은 변경불가능한 값이다.

재할당은 가능하지만, 변하지 않는다. 변경이 불가능하기에 재할당 시에도 메모리 공간에서 값이 바뀌는 게 아니라 새로운 메모리 공간을 확보하고 재할당한 값을 저장한 후, 변수가 참조하던 메모리 공간을 변경한다.

이런 원시 값의 특성은 데이터의 신뢰성을 보장하며 이런 특성을 불변성(immutabillity)이라 한다.

만약 값이 변한다면, 상태 변경을 추적하기 어렵게 된다.

 

문자열

숫자나 불리언과 다르게 문자열의 크기는 정해지지 않았다. 이에 대해서 다른 언어들은 문자열을 문자의 배열이나 String 객체로 처리하는 등에 방법을 쓰지만, 자바스크립트는 문자열이라는 원시타입 객체를 제공한다. 

그래서 문자열이 생성되면 불변한다. 하지만, 문자열을 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문자에 접근할 수 있다.

var str = 'string'
str[0] = 'A'
console.log(str) //string

※ 유사 배열 객체(array-like object) : 마치 배열처럼 인덱스로 프로퍼티에 접근 가능하며 length 프로퍼티를 갖는 객체.

 

값에 의한 전달

var score = 80
var copy = score

console.log(score,copy) // 80, 80

score = 100

console.log(score,copy) // 100,80

copy = score인데, 둘은 다르다. 왜일까? 이는 값에 의한 전달이기 때문이다. 이를 좀 더 풀어서 표현해보자.

 

값에 의한 전달 방식은 크게 두 가지가 있다. 

 

 

1번 방법은 주소에서 값을 복사하여 새로운 메모리에 할당하고 변수와 연결하는 것이다. 그래서 score가 바뀌는 것과 copy는 완전 관계가 없다.

 

 

2번 방법은 같은 주소에 할당되는 것이다. 파이썬은 이와 같은 방법을 사용하는데, 재할당시에는 어처피 다른 메모리에 새로운 값이 할당되기에 이제 관계가 없어진다.

 

그래서 값에 의한 전달은 기존의 변수가 변해도 복사된 변수와 관계없다.

 

※ 값에 의한 전달은 사실 EMCA의 용어가 아니라 타 언어에서 주로 사용하는 표현이다. 다르게는 공유에  의한 전달이라고 부르기도 한다.

 

객체

객체는 프로퍼티의 개수가 정해져 있지 않으며, 동적으로 삭제 및 추가가 가능하며 제약도 없다. 그래서 메모리 크기를 사전에 할당할 수 없다. 또한 복합적인 자료구조이므로 복잡하고 크기가 클 수 있어서 원시값과 관리 방법도 매우 다르다.

 

변경 가능한 값

객체는 변경가능하다. 이는 변수를 참조하면 참조 값을 통해 실제 객체에 접근하기 때문이다.

이렇게 참조하는 객체는 변경가능한 값이기 객체를 직접 수정해버리면 된다. 원시 값처럼 매 번 새로 생성하면 신뢰성과 불변성을 확보할 수는 있지만, 크기가 매우 클 수도 있어서(프로퍼티가 객체이거나 매우 많은 프로퍼티를 가진 객체이거나) 복사해서 생성하는 비용이 많이 든다. 이는 메모리의 효율적 소비를 어렵고 성능이 나빠지게 만든다. 그래서 그런 점을 감안해서 만들어진 설계이다.

 

하지만 이런 설계에도 단점이 있다. 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것이다.

 

※ 이런 부분이 복사에서 문제가 생긴다. 얕은 복사와 깊은 복사로 생성된 객체는 원본과 다르지만, 중첩되어 있는 객체에 경우 참조 값을 복사하여서 사용하고 깊은 복사는 원시값처럼 그마저도 새로 만든다.

const lodash = require('lodash')
const original = {x: {y:1}};
const shallowCopy = {...original}
const deepCopy = lodash.cloneDeep(original)

console.log(shallowCopy, deepCopy) //{ x: { y: 1 } } { x: { y: 1 } } { x: { y: 1 } }
console.log (original === shallowCopy, original.x === shallowCopy.x) //false true
console.log (original === deepCopy, original.x === deepCopy.x) //false false

original.x.y = 2
console.log(original, shallowCopy, deepCopy) //{ x: { y: 2 } } { x: { y: 2 } } { x: { y: 1 } }
// 보는 것처럼 얕은 복사는 값이 같이 수정된다.

 

참조에 의한 전달

객체가 가리키는 변수를 다른 변수에 입력하면 원본의 참조값이 복사되어 전달되는 데 이를 참조에 의한 전달이라 표현한다.

위 그림처럼 person과 copy는 모두 0x00001332를 가리키고 0x00001332가 변하면  양쪽 다 영향이 가게된다. (위 얕은 복사 예시 참조)

 

※ 엄밀히 말하면, 메모리 주소값을 전달하는 거니 둘 다 값에 의한 전달이 맞지만, 저자는 두 차이를 강조하기 위해서 여기서는 이렇게 구분 지었다.