Part 11. 원시 값과 객체의 비교

HanSungUk·2022년 5월 30일
0

JS_Books

목록 보기
8/15
post-thumbnail

현재 '모던 자바스크립트 Deep Dive'를 통해 자바스크립트를 학습하고 있습니다. 본 포스트는 해당 내용에 대한 정리를 목적으로 합니다.

- 원시 타입과 객체 타입의 차이점

  • 원시 타입의 값, 즉 원시값은 변경 불가능한 값(immutable value)이다. 이에 비해 객체(참조) 타입의 값, 즉 객체는 변경 가능한 값(mutable value)이다.
  • 원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 저장된다. 이에 비해 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조값이 저장된다.
  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달(pass by value) 이라 한다. 이에 비해 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달(pass by reference)이라 한다.

1. 원시값

1. 변경 불가능한 값

변수가 참조하던 메모리 공간의 주소가 변경된 이유는 변수에 할당된 원시 값이 변경 불가능한 값이기 때문입니다.
따라서, 변수 값을 변경하기 위해 원시 값을 재할당하면 새로운 메모리 공간을 확보하고 재할당한 값을 저장한 후, 변수가 참조하던 메모리 공간의 주소를 변경합니다. 값의 이러한 특성을 불변성(immutability)이라고 합니다.

2. 문자열과 불변성

원시 값을 저장하려면 먼저 확보해야 하는 메모리 공간의 크기를 결정해야 합니다. 하지만 ECMAScript 사양에 문자열 타입(2바이트)과 숫자 타입(8바이트)이외의 원시 타입은 명확히 규정하고 있진 않습니다.

  • 문자열의 특징
    문자열은 0개 이상의 문자(character)로 이뤄진 집합을 말하며, 1개의 문자는 2바이트의 메모리 공간에 저장됩니다. 단순히 계산해볼때 10개의 문자에는 20바이트가 필요합니다.
let str1 = ''; // 0개의 문자로 이뤄진 문자열(빈 문자열)
let str2 = 'Hello'; // 5개의 문자로 이뤄진 문자열

또한 문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문자에 접근할 수 있습니다.

유사 배열 객체(array-like object)
유사 배열 객체란 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말합니다. 문자열은 마치 배열처럼 인덱스를 통해 각 문자에 접근할 수 있으며, length 프로퍼티를 갖기 때문에 유사 배열 객체이고 for문으로 순회할 수 있습니다.

let str = 'string'
console.log(str[0]); // s
console.log(str.length); // 6

// 하지만 문자열은 원시 값이므로 변경할 수 없다. 이때 에러가 발생하지 않는다.
str[0] = 'S'
console.log(str); // string

3. 값에 의한 전달

let score = 80;
// copy 변수에는 score 변수의 값 80이 복사되어 할당된다.
let copy = score;

console.log(score, copy); // 80 80
console.log(score === copy); //true

위 예제에서 score 변수와 copy 변수의 값 80은 다른 메모리 공간에 저장된 별개의 값입니다. 따라서 score 변수의 값을 변경해도 copy 변수의 값에는 어떠한 영향도 주지 않습니다.

즉,두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할 수 없습니다.

2. 객체

1. 변경 가능한 값

원시 값을 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 원시 값에 접근할 수 있습니다. 즉, 원시 값을 할당한 변수는 원시 값 자체를 값으로 갖습니다.
하지만 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조값(reference value) 에 접근할 수 있습니다.
참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체입니다.

// 할당이 이뤄지는 시점에 객체 리터럴이 해석되고, 그 결과 객체가 생성된다.
let person = {
	name: 'Lee'
};

// person 변수에 저장되어 있는 참조 값으로 실제 객체에 접근한다.
console.log(person); // {name: 'Lee'}

객체를 할당한 변수의 경우 "변수는 객체를 참조하고 있다." 또는 "변수는 객체를 가리키고 있다."라고 표현합니다.

let person = {
	name: 'Lee'
};

// 프로퍼티 값 갱신
person.name = 'Han';

// 프로퍼티 동적 생성
person.address = 'Suwon';

console.log(person) // {name: 'Han', address: 'Suwon'}

객체는 변경 가능한 값입니다. 따라서, 재할당 없이 프로퍼티를 동적으로 추가할 수도 있고 프로퍼티 값을 갱신할 수도 있으며 프로퍼티 자체를 삭제할 수도 있습니다.
자바스크립트는 메모리 사용의 효율성과 성능을 위해 이러한 구조적 단점을 감안하여 설계되었습니다.
이에 따른 부작용은 원시 값과는 다르게 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것입니다.

  • 얕은 복사(shallow copy)와 깊은 복사(deep copy)
    객체를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계까지만 복사하는 것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 말합니다.
const o = {x: {y: 1}};

// 얕은 복사
const c1 = {...o}; 
console.log(c1 === o); // false
console.log(c1.x === o.x) // true

// lodash의 cloneDeep을 사용한 깊은 복사
// 'npm install lodash'로 lodash를 설치한 후, Node.js 환경에서 실행
const _ = require('lodash')
// 깊은 복사
const c2 = _.cloneDeep(o);
console.log(c1 === o); // false
console.log(c1.x === o.x) // false

2. 참조에 의한 전달

let person = {
	name: 'Lee'
};

// 참조 값을 복사(얕은 복사)
let copy = person;

객체를 가리키는 변수(원본, person)를 다른 변수(사본, copy)에 할당하면 원본의 참조 값이 복사되어 전달됩니다.
이때, 원본 person과 사본 copy는 자정된 메모리 주소는 다르지만 동일한 참조값을 갖습니다.

결국 "값에 의한 전달"과 "참조에 의한 전달"은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 면에서 동일합니다. 다만 식별자가 기억하는 메모리 공간, 즉 변수에 저장되어 있는 값이 원시 값이냐 참조값이냐의 차이만 있을 뿐입니다.

0개의 댓글