업데이트:

이번 장은 6장 데이터 타입에서 다룬 바 있다. 원시 타입과 객체 타입의 차이점을 짚고 넘어가자.


  1. 원시 타입의 값(원시 값)은 변경 불가능하고 객체 타입의 값은 변경 가능하다.
  2. 값이 변수에 할당될 때, 원시 타입은 실제 값이 저장되고 객체는 참조 값이 저장된다.
  3. 값이 전달될 때, 원시 타입은 원시 값이 복사되어 전달되고 객체는 참조 값이 복사되어 전달된다. (값에 의한 전달 / 참조에 의한 전달)

[01] 원시 값

변경 불가능한 값

원시 타입의 값은 읽기 전용 값으로 변경할 수 없다.

이때 값을 변경할 수 없다는 것은 변수 값을 변경할 수 없다는 것이 아니라 처음 생성된 원시 값 자체를 변경할 수 없다는 것이다.

변수는 재할당을 통해 언제든지 값을 변경 할 수 있다.

상수는 한번만 할당이 허용되므로 변수 값을 변경할 수 없는, 재할당이 금지된 변수라고 보면 된다.

const o = {};
//const를 사용하면 재할당이 금지된다. (상수의 특징)
o.a = 1;
//하지만 할당한 객체는 변경 가능하다.
console.log(o); // {a: 1}

Untitled

위 그림과 같이 값을 재할당 하면 새로운 메모리 주소에 새로운 값이 저장되는 것을 볼 수 있다.

재할당 외에 원시 값인 변수 값을 변경할 수 있다면, 예기치 않게 변수 값이 변경되어 상태 변경을 추적하기 어렵게 만든다. 원시 값은 불변성을 가진다는 것을 기억하자.

문자열과 불변성

ECMAScript에서 문자열 타입(2바이트)와 숫자 타입(8바이트)은 크기가 규정되어 있다.

1개의 문자는 2바이트의 메모리 공간에 저장된다고 한다. 10개의 문자열에는 20바이트가 필요하다.

var str = 'Hello';
str = 'world';

식별자 str은 문자열 ‘Hello’를 가리키고 있다가 ‘world’를 가리키도록 변경되었다.

메모리 공간에는 Hello와 world 둘 다 존재한다.

🔍 유사 배열 객체

var str = 'string';
//인덱스를 사용해 배열처럼 각 문자에 접근 가능
console.log(str[0]);
//원시 값인 문자열이 객체처럼 동작
console.log(str.length); // 6
console.log(str.toUpperCase()); //STRING

문자열이 원시 값이지만 객체처럼 사용하면 원시값을 감싸는 래퍼 객체로 자동 변환한다고 한다.

뒤에서 더 자세하게 다룰 내용이니 알고만 넘어가자.

var str = 'string';

str[0] = 'S';
//원시 값이므로 문자열은 변경할 수 없다. 인덱스를 통해 접근은 가능해서 에러는 발생하지 않는다.
console.log(str); // string

값에 의한 전달

var score = 80;
var copy = score;

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

score = 100;

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

score 변수에 100이라는 값을 재할당하면 복사된 변수인 copy에는 100이 할당되어 있을까?

답은 아니다. 값에 의한 전달이 일어나기 때문에 copy 변수에는 원시 값인 80이 복사되어 전달되었고, score 변수의 원시 값 80의 메모리 공간과는 다른 메모리 공간에 저장되어 있다.

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


[02] 객체

객체는 프로퍼티의 개수가 정해져 있지 않고 동적으로 추가/삭제가 되기 때문에 원시 값처럼 확보해야 할 메모리 공간의 크기를 사전에 정해둘 수 없다.

변경 가능한 값

객체 타입의 값은 변경 가능하다.

var person = {
	name: 'minhye',
};
person.name = 'Kim';
person.address = 'Seoul';

console.log(person);

Untitled

위 코드를 그림을 나타낸 것이다.

처음 person 변수에 객체를 할당하면, 객체 값 자체가 메모리 주소에 저장되는 것이 아니라 객체를 가리키는 메모리 주소를 저장한다.

참조 값으로 객체에 접근하기 때문에 처음 할당한 객체의 내용도 변경 가능한 것이다.

객체의 구조적 단점은 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 점이다.

얕은 복사와 깊은 복사

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

//얕은 복사
const c1 = { ...o};
console.log(c1 === o); //false
//원본과 복사본은 참조 값이 다르다.
console.log(c1.x === o.x); //true
//객체 내부의 객체는 얕은 복사된다.

얕은 복사는 객체 내부의 프로퍼티는 보호되지 않는다.

function deepFreeze(obj) {
  const props = Object.getOwnPropertyNames(obj);

  props.forEach((name) => {
    const prop = obj[name];
    if(typeof prop === 'object' && prop !== null) {
      deepFreeze(prop);
    }
  });
  return Object.freeze(obj);
}

const user = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

deepFreeze(user);

user.name = 'Kim';           // 무시된다
user.address.city = 'Busan'; // 무시된다

console.log(user); // { name: 'Lee', address: { city: 'Seoul' } }

내부 객체까지 변경 불가능하게 만드려면 Deep freeze (깊은 복사)를 해야 한다.

참조에 의한 전달

여러 개의 식별자가 하나의 객체를 공유한다는 단점이 있다고 하였다.

이러한 단점에는 어떤 부작용이 있을까?

var person = {
	name: 'Lee'
}
var copy = person;
//얕은복사

Untitled

위 코드를 그림으로 나타낸 것이다. person과 copy의 메모리 주소는 서로 다르지만 안에 저장된 값은 같다. 이때 저장되어 있는 값은 객체가 담긴 메모리 주소라서 참조에 의한 전달이라고 한다. 또한 person과 copy 변수가 하나의 객체를 공유하고 있다.

이렇게 되면 copy에서 값을 동적으로 변경했을 때 person 값도 변경된다는 부작용이 발생한다.

💁🏻‍♀️ 사실 자바스크립트에는 참조에 의한 전달이 존재하지 않는다 ?!

값에 의한 전달과 참조에 의한 전달은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달하는 점에서 같다.

변수에 저장된 값이 원시값이냐 참조값이냐의 차이이다.

그래서 사실상 참조에 의한 전달은 없고 값에 의한 전달만 존재한다고 볼 수 있다.

자바스크립트에는 포인터가 존재하지 않기 때문에 다른 프로그래밍 언어의 “참조에 의한 전달”과 의미가 정확히 맞지는 않는다는 것을 주의하자.

댓글남기기