js

Intro

const a = "apple";

위 코드에서 변수와 식별자를 구분할 수 있나요?

구별이 가능하시다면 변수의 데이터타입을 말할 수 있나요?


학습내용

1. 변수와 식별자 정의
2. 데이터 타입과 타입별 특징
3. 데이터가 가변적일 때 문제점과 해결방안
4. 얕은 복사, 깊은 복사
5. undefined와 null의 차이점



데이터 타입

자바스크립트의 데이터 타입은 크게 가변성에 따라 기본형과 참조형으로 나눌 수 있습니다.

  1. 기본형(primitive): 숫자, 문자열, 불리언, null, undefined, 등
  2. 참조형(reference): 배열, 객체, 함수, 날짜, 정규표현식, 등

기본형도 가변성 아닌가?

기본형 변수도 대입하는 값에 따라 변하기 때문에 가변성을 갖는다고 착각하실 수도 있는데, 여기서 말하는 가변성은 변수영역이 아니라 데이터 영역에 해당합니다. 이를 이해하기 위해 두 타입을 나눠서 자세히 알아 보도록 하겠습니다.



원시형 데이터

불변값

원시형를 왜 불변한다고 하는지 예를 통해 알아보도록 하겠습니다.

var a;
a = "green";
a = a + "apple";

일단 식별자 a로 데이터를 할당하지 않고 선언을 하면 변수영역의 1000이라는 주소로 이름 a,와 undefined값이 들어갑니다.

a = "green";

이후 a에 ‘green’이라는 값이 할당되면 데이터 영역에서 ‘green’ 값이 있는지 확인하고, 없다면 새 주소에 값을 넣고 해당 주소 5000을 a변수의 값에 넣어줍니다.

image

a = a + "apple";

그리고 기존 a값에 ‘apple’을 추가해보도록 하겠습니다. 이러면 데이터 영역 5000의 데이터가 ‘greenapple’로 바뀔 것 같지만 5000엔 ‘green’이 그대로 자리하고 5001에 ‘greenapple’이 들어갑니다. 변수 a의 값도 5000에서 5001로 바뀝니다.

image

즉, 변수 값을 바꿔도 데이터 영역의 주소 안에 있는 값은 가비지 컬렉팅을 당하지 않는 이상 변하지 않습니다. 그래서 원시형 데이터는 불변성을 띈다고 말하는 것입니다.



참조형 데이터

가변값

원시형 데이터와 달리 참조형의 데이터 영역엔 실제 값이 아니라 실제값이 담긴 주소가 들어있습니다. 아래코드를 보면서 예를 들도록 하겠습니다.

var b = {
  product: "apple",
  price: 1000,
};

b를 객체로 초기화한 순간 데이터 영역을 보시면 원시형과 달리 주소값들이 들어가 있습니다.

image

그리고 해당주소에는 객체의 속성이 변수영역으로 들어가 있습니다.

image

당연히 변수영역은 데이터 영역에서 값을 참조하기에 product, price 속성들은 값으로 넣어준 ‘apple’과 1000을 데이터 영역에서 찾습니다. ‘apple’의 경우 이미 5000에 존재하기에 해당 주소를 product란 변수의 값에 넣고 1000의 경우는 데이터영역에 존재하지 않아서 새로 할당후 해당 주소를 price의 값에 넣습니다.

b.product = "candy";
b.price = 500;

자, 이제 값을 바꿔보도록 하겠습니다.

image

보시면 객체의 변수영역은 변했지만 b의 값은 그대로입니다. 여기서 의문을 갖는 분도 있겠죠.

아까, 원시형에서 데이터영역 주소 안에 값이 그대로면 불변성이라고 하지 않았나요? 지금 참조형도 데이터 영역 값들이 그대로고 심지어 변수영역도 그대로인데 그럼 가변성이 아니라 불변성을 띄는 거 아닌가요?

아니요. 참조형은 객체의 변수영역으로 가변성을 따집니다.

지금, 객체의 변수영역을 보시면 같은 주소인데 값만 바뀌었습니다. 아까 원시형 데이터의 값이 변하면 새로운 주소를 할당하는 것과 다르죠.즉, 참조형은 기존 메모리 공간에 계속 새로운 데이터를 대입할 수 있습니다.


가변성 데이터의 문제점

복사한 데이터가 변하면 원본도 변해!

참조형 데이터를 다른 변수에 할당하면 데이터의 실제값이 아니라, 실제값이 위치한 주소가 들어갑니다. 그래서 데이터의 복사본을 수정하면 해당 주소의 값이 변해서 원본 데이터를 훼손하게 됩니다.

var b = {
  product: "apple",
  price: 1000,
};

var c = b;

c.product = "candy";

위 코드의 경우 c의 product를 candy로 바꾸면 b의 product도 candy로 바뀝니다. 그렇다면 해당 문제를 어떻게 해결하면 좋을까요?


참조형 데이터를 불변값으로 만드는 법

freeze() 사용

만약 원본을 가공하지 않길 원한다면 Object.freeze()를 사용하시면 됩니다. 이 경우 속성, 속성값, 프로토타입까지 동결하기 때문에 원본 훼손을 원천차단할 수 있습니다.

새로운 객체 반환

하지만 보통은 원본을 복사해 활용하겠죠. 이럴 땐 원본객체 안 내용만 새로운 객체에 담아 반환하는 함수, 스프레드오퍼레이터, 또는 immutable.js, immer.js 같은 라이브러리를 사용하면됩니다.

여기선 함수로 예를 들어보겠습니다.

function copyObject(object) {
  var newObject = {};
  for (prop in object) {
    newObject[prop] = object[prop];
  }
  return newObject;
}

newObject엔 object의 속성값이 담겼지만 새로운 객체이므로 데이터영역의 주소값이 다릅니다. 따라서 newObject를 수정해도 object는 변하지 않습니다.


얕은 복사

껍데기만 새로운 데이터, 내부는 원본 주소 참조

우리가 방금 한 복사를 얕은 복사라고 합니다. 객체의 데이터영역 값은 변했지만 객체의 속성값이 또 참조형인 경우, 해당 참조형 데이터의 주소값이 여전히 일치해서 원본을 훼손할 수 있습니다. 예로 아까 만든 copyObject로 새로운 객체를 반환하고 해당 객체를 수정해보독 하겠습니다.

var object = { name: "apple", category: "fruit" };

var c = {
  product: object,
  price: 1000,
};

d = copyObject(c);

지금 보시면 아래처럼 객체 c와 d는 서로 다른 데이터영역을 갖고 있습니다.

image

참조주소가 다르기 때문에 당연히 속성값을 바꿔도 영향이 없겠죠?

d.product.name = "computer";

원본 훼손…

현재 d의 product속성은 참조형 데이터 객체입니다. 아까 복사를 할 때 d의 데이터값(참조주소)을 바꾸는 데는 성공했지만, 데이터가 참조하는 객체변수 product가 똑같은 주소 @1002를 참조하고 있습니다.

image

그래서 product의 값을 바꾸면 product를 참조하는 원본 c역시 값이 바뀌게 됩니다.

image

그렇다면 내부는 어떻게 데이터를 바꿀 수 있을까요?


깊은 복사

내부까지 새로운 데이터

재귀함수로 내부까지 들어가서 새로운 객체를 반환하도록하면 됩니다. 예로 위 copyObject를 깊은 복사를 하도록 만들어 보겠습니다.

function copyObject(target) {
  var newObject = {};

  if (target !== null && typeof target === object) {
    for (prop in target) {
      newObject[prop] = copyObject(target);
    }
  } else {
    return target;
  }
  return newObject;
}

이러면 이제 product 객체의 참조주소가 바뀌어서 아까처럼 d.product를 수정해도 c.product가 바뀌지 않습니다.

image



undefined vs null

할당되지 않으면 undefined 내가 직접 빈값을 넣으면 null

자바스크립트엔진은 다음과 같은 경우 undefined를 반환합니다.

  1. 초기화하지 않은 식별자에 접근할 때
  2. 존재하지 않는 속성에 접근할 때
  3. return문이 없거나 호출되지 않은 함수의 실행결과
  4. 호이스팅시 var로 선언된 식별자에 접근할 때

그런데, 배열의 경우 값을 대입하지 않고 length만 늘렸을 때 undefined가 들어가지 않습니다.

var arr = [];
arr.length = 3;
console.log(arr); //[empty x3]

empty라고 빈공간인데 어짜피 undefined도 빈값이란 걸 생각하면 똑같이 느껴집니다. 하지만 [empty x3]는 [undefined,undefined,undefined]와 차이가 있습니다.

undefined의 의미

undefined는 empty와 달리 할당이 안됐다는 의미를 지닌 데이터입니다. 그래서 empty가 순회를 돌지않는 것과 달리 undefined로 찬 배열은 순회를 돕니다.

그럼 null은?

null역시 빈값이지만 엄연히 데이터입니다. 다만 undefined와 사용 목적이 다릅니다. undefined는 자바스크립트 엔진이 판단해서 내린 빈값이고, null은 사용자가 지정한 빈값입니다. 당연히 두 값을 비교하면 !==입니다. 하지만 일치 연산자 대신 동등연산자(==)로 비교하면 같다고 인식하기 때문에 주의해야합니다.



질문

  1. 기본형 데이터는 변수영역에 직접 값을 저장한다. (o/x)
  2. 스프레드 오퍼레이터로 객체를 복사한 경우, 중첩 객쳬까지 불변값이 된다. (o/x)


참고

💻 코어 자바스크립트

태그:

카테고리:

업데이트:

댓글남기기