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

윤상준·2022년 10월 16일
0
post-thumbnail

원시 타입과 참조 타입

원시 타입

  • 변경 불가능한 값 (Immutable Value).
  • 변수에 할당 시 실제 값이 저장.
  • 다른 변수에 할당 시 값이 복사되어 전달 (값에 의한 전달, Pass by Value).

참조 타입

  • 변경 가능한 값 (Mutable Value).
  • 변수에 할당 시 참조 값이 저장.
  • 다른 변수에 할당 시 참조 값이 복사되어 전달 (참조에 의한 전달, Pass by Reference).

11.1 원시 값

11.1.1 변경 불가능한 값

한번 생성된 원시 값은 읽기 전용 (Read Only)으로 변경이 불가능.

값 자체를 변경 불가능한 것이지, 변수는 언제든지 재할당을 통해 변수를 교체할 수 있다.

const와 같은 상수는 재할당 역시도 불가능하다.

// const 키워드를 사용해 선언한 변수는 재할당이 금지된다. 상수는 재할당이 금지된 변수일 뿐이다.
const o = {};

// const 키워드를 사용해 선언한 변수에 할당한 원시값(상수)은 변경할 수 없다.
// 하지만 const 키워드를 사용해 선언한 변수에 할당한 객체는 변경할 수 있다.
o.a = 1;
console.log(o); // {a: 1}

원시 값 재할당

원시 값을 재할당하면 이전의 원시 값을 변경하는 것이 아니다.

새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한다.

그 후 변수는 새롭게 재할당한 원시 값을 가리킨다.

따라서 변수가 참조하던 메모리 공간의 주소가 바뀐다.

불변성을 갖는 원시 값을 할당한 변수는 재할당으로만 값을 변경할 수 있다.

11.1.2 문자열과 불변성

0개 이상의 문자 (Character)로 이루어진 집합.

1개의 문자는 2바이트의 메모리 공간에 저장.

몇 개의 문자로 이뤄졌는지에 따라 메모리 공간의 크기가 결정.

반면 숫자는 1도, 100000도 모두 8바이트만 필요.

// 문자열은 0개 이상의 문자들로 이뤄진 집합이다.
var str1 = '';      // 0개의 문자로 이뤄진 문자열(빈 문자열)
var str2 = 'Hello'; // 5개의 문자로 이뤄진 문자열
var str = 'Hello';
str = 'world';

문자열은 유사 배열 객체이기 때문에 반복문 또는 인덱스 접근이 가능.

var str = 'string';

// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.
console.log(str[0]); // s

// 원시 값인 문자열이 객체처럼 동작한다.
console.log(str.length); // 6
console.log(str.toUpperCase()); // STRING

하지만 문자열의 일부 문자는 변경되지 않는다.

var str = 'string';

// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.
// 하지만 문자열은 원시값이므로 변경할 수 없다. 이때 에러가 발생하지 않는다.
str[0] = 'S';

console.log(str); // string

11.1.3 값에 의한 전달

값에 의한 전달

변수에 원시 값을 갖는 변수를 할당하면?

할당받는 변수에는 할당되는 변수의 원시 값이 복사되어 전달.

var score = 80;
var copy = score;

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

score = 100;

console.log(score); // 100
console.log(copy);  // ?
var score = 80;

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

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

할당받는 변수와 할당되는 변수의 값은 다른 메모리 공간에 저장된 별개의 값.

따라서 한 쪽이 변경되어도 다른 한 쪽은 영향을 받지 않는다.

var score = 80;

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

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

// score 변수와 copy 변수의 값은 다른 메모리 공간에 저장된 별개의 값이다.
// 따라서 score 변수의 값을 변경해도 copy 변수의 값에는 어떠한 영향도 주지 않는다.
score = 100;

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

엄밀히 말하면 변수에는 값이 아니라 메모리 주소가 전달된다.

변수와 같은 식별자는 값이 아니라 메모리 주소를 기억한다.

따라서 값에 의한 전달도 사실은 메모리 주소를 전달하는 것이다.

전달된 메모리 주소를 통해 메모리 공간에 접근하여 값을 참조하는 것.

11.2 객체

자바스크립트 객체는 프로퍼티 키를 인덱스로 하는 해시 테이블 (Hash Table)과 유사하다.

더 높은 성능을 위해 일반적인 해시 테이블보다 더 나은 방법으로 객체를 구현.

V8 자바스크립트 엔진은 히든 클래스 (Hidden Class) 방식을 사용하여 고정된 객체 레이아웃 (클래스)와 유사하게 객체를 관리한다.

11.2.1 변경 가능한 값

객체 (참조) 타입의 값은 변경 가능한 값 (Mutable Value).

var person = {
  name: 'Lee'
};

객체가 할당된 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하여 참조 값 (Reference Value)에 접근 가능.

참조 값 (Reference Value)
생성된 객체가 저장된 메모리 공간의 주소 그 자체.

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

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

객체를 할당한 변수는 재할당 없이 객체를 직접 변경 가능.

객체 값은 변경 가능한 값이기 때문.

재할당 없이 프로퍼티를 동적으로 추가, 값 갱신, 프로퍼티 삭제 등이 가능.

var person = {
  name: 'Lee'
};

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

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

console.log(person); // {name: "Kim", address: "Seoul"}

객체 타입 값은 여러 개의 식별자가 하나의 객체를 공유할 수 있다.

얕은 복사 (Shallow Copy)와 깊은 복사 (Deep Copy)

const o = { x: { y: 1 } };

// 얕은 복사
const c1 = { ...o }; // 35장 "스프레드 문법" 참고
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(c2 === o); // false
console.log(c2.x === o.x); // false

얖은 복사와 깊은 복사 모두 원본을 복사하여 새로운 객체를 생성.

중첩된 객체의 경우 얕은 복사는 중첩 객체의 참조 값을 복사.

깊은 복사는 중첩 객체까지 모두 복사하여 완전한 새로운 복사본을 생성.

또는 원시 값을 할당한 변수를 다른 변수에 할당하는 것을 깊은 복사, 객체를 할당한 변수를 다른 변수에 할당하는 것을 얕은 복사라고 하는 경우도 있다.

const v = 1;

// "깊은 복사"라고 부르기도 한다.
const c1 = v;
console.log(c1 === v); // true

const o = { x: 1 };

// "얕은 복사"라고 부르기도 한다.
const c2 = o;
console.log(c2 === o); // true

11.2.2 참조에 의한 전달

객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달.

두 변수는 저장된 메모리 주소는 다르지만 동일한 참조 값을 갖는다.

두 개의 식별자가 하나의 객체를 공유하는 것.

원본 또는 사본 중 한 쪽에서 객체를 변경하면 다른 한 쪽도 변경.

var person = {
  name: 'Lee'
};

// 참조값을 복사(얕은 복사). copy와 person은 동일한 참조값을 갖는다.
var copy = person;

// copy와 person은 동일한 객체를 참조한다.
console.log(copy === person); // true

// copy를 통해 객체를 변경한다.
copy.name = 'Kim';

// person을 통해 객체를 변경한다.
person.address = 'Seoul';

// copy와 person은 동일한 객체를 가리킨다.
// 따라서 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다.
console.log(person); // {name: "Kim", address: "Seoul"}
console.log(copy);   // {name: "Kim", address: "Seoul"}

결론

“값에 의한 전달”, “참조에 의한 전달” 모두 메모리 공간에 저장된 값을 복사하여 다른 공간에 할당하는 과정.

옮기는 값이 원시 타입이냐 참조 타입이냐의 차이만 있을 뿐.

따라서 엄밀히 말하면 자바스크립트에서는 “값에 의한 전달”만이 존재한다고 볼 수 있다.

var person1 = {
  name: 'Lee'
};

var person2 = {
  name: 'Lee'
};

console.log(person1 === person2); // ①
console.log(person1.name === person2.name); // ②

① 에서 person1과 person2는 서로 다른 메모리 공간에 저장되어있는 별개의 객체이므로 일치하지 않는다.

② 에서 person1.name과 person2.name은 값으로 평가될 수 있는 표현식 즉, 문자열의 원시 타입이다. 따라서 일치한다.

profile
하고싶은건 많은데 시간이 없다!

0개의 댓글