자바스크립트의 데이터 타입

이희제·2024년 3월 24일
post-thumbnail

코어 자바스크립트 1장에 대한 정리 내용이다.

자바스크립트 데이터 타입은 크게 2가지로 나눌 수 있다.

  1. 기본형
  • 숫자, 문자열, 불리언, null, undefined, symbol, bigint
  • 불변성
  • 할당 연산 시에 값이 담긴 주솟값 바로 복제
  1. 참조형
  • 객체, 배열, 함수, 날짜, 정규표현식 등
  • 가변성
  • 할당, 연산 시에 값이 담기 주솟값들의 묶음이 복제

식별자와 변수

변수는 변할 수 있는 수 즉, 변경 가능한 데이터가 담길 수 있는 메모리 공간
식별자는 변수명

변수 선언

변수 선언은 변할 수 있는 데이터를 만드는 것

데이터 할당

변수에 대해 데이터 할당 시에는 변수 영역에 대한 메모리, 값(데이터)에 대한 메모리 영역 각각 나눠서 확보된다.

=> 메모리를 더 효율적으로 관리하기 위해

데이터가 변경되면 메모리 공간을 새로 할당해서 변수와 연결한다. 따라서 기존 데이터는 변경되지 않기 때문에 불변성이다.

불변값

변수와 상수를 구분 짓는 변경 가능성의 대상은 변수 영역의 메모리이다. (재할당 여부)

반면, 불변성의 여부를 판단할 때는 데이터 영역의 메모리가 변경 가능성이 있는 지를 본다.

기본형 데이터(숫자, 문자열, boolean, null, undefined, Symbol)는 모두 불변값이다.

var a = 'abc';
a = a + 'def';

위 예시에서 a에 "def"를 뒤에 추가한다면 메모리 영역에서 "abc"가 "abcdef"로 변하는 것이 아니라 새로운 주소 내 "abcdef"를 생성하고 그 주소를 변수 a에 저장한다.

불변값은 가비지 컬렉팅 대상이 아닌 이상 변하지 않는다.


가변값

기본형 데이터는 모두 불변값이지만 참조형 데이터는 모두 가변값일 수도 있고 아닐 수도 있다.

우선 일반적으로 가변값일 때의 참조형 데이터를 살펴보자.

var obj1 = {
  a: 1,
  b: 'bbb',
};
obj1.a = 2;

예시와 같이 obj1 내의 a 값을 1에서 2로 변경해보자. 메모리 영역을 보면 다음과 같다.

데이터 영역에 저장된 값은 모두 불변값이지만 객체 변수 값은 변경될 수 있다. 따라서 가변값이라고 하는 것이다.

이를 기반으로 위 예시 표를 보면 @7103 주소에 있는 a의 값이 @5003에서 @5005로 변경된 걸 확인할 수 있다. 반면 obj1의 값은 변하지 않았다.

변수 복사 비교

기본형 데이터와 참조형 데이터의 복사 과정을 비교해보자

var a = 10;
var b = a;

var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

기본형 데이터, 참조형 데이터 모두 복사 대상이 가지고 있는 주소값을 그대로 들고온다.

변수 b는 a와 동일한 주소값인 @5001을 변수 obj2는 obj1과 동일한 주소값인 @5002를 가지게 된다.

변수 복사 과정은 기본형, 참조형 모두 동일하다. 하지만, 변수 복사 이후 동작에서는 차이를 보인다.

var a = 10;
var b = a;
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

b = 15;
obj2.c = 20;

복사한 변수에 다시 다른 값을 할당해보자.

기본형 데이터를 복사한 b는 값이 변경되었지만 참조형 데이터를 복사한 obj2는 값은 변경되지 않았다. 대신, obj2 내부 변수인 c의 값이 변경되었다.

obj2에 새로운 객체를 할당하면 어떨까?

var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

obj2 = { c: 20, d: 'ddd' };

새로운 객체를 할당하면 새로운 메모리 데이터 영역의 공간에 새로운 객체가 저장되고 그 주소가 변수 영역의 값에 저장이 된다. 따라서 기존 obj1과 값이 달라진 것이다.

여기서 알 수 있는 점은 참조형 데이터의 "가변"은 참조형 데이터 자체를 변경할 경우가 아니라 내부 프로퍼티를 변경할 때만 성립한다는 것이다.

불변 객체를 만드는 방법

참조형 데이터는 변경 사항이 있을 때마다 새로운 객체를 만들어 재할당한다면 불변성을 확보할 수 있다.

불변 객체는 객체에 변경이 일어나도 원본 객체가 변하지 않아야 하는 경우에 필요하다.

다음 예시와 같이 아예 새로운 객체를 return하여 불변 객체를 만들 수 있다.

var user = {
  name: 'Jaenam',
  gender: 'male',
};

// 내부 프로퍼티가 변경된 새로운 객체를 return 해준다. 
var changeName = function(user, newName) {
  return {
    name: newName,
    gender: user.gender,
  };
};

var user2 = changeName(user, 'Jung');

if (user !== user2) {
  console.log('유저 정보가 변경되었습니다.'); // 유저 정보가 변경되었습니다.
}
console.log(user.name, user2.name); // Jaenam Jung
console.log(user === user2); // false

얕은 복사와 깊은 복사

  • 얕은 복사: 바로 아럐 단계의 값만 복사
  • 깊은 복사: 내부의 모든 값들을 찾아서 복사

참조형 데이터인 객체를 복사하기 위해서는 깊은 복사가 필요하다.

객체를 먼저 얕은 복사를 해보자

var copyObject = function(target) {
  var result = {};
  for (var prop in target) {
    result[prop] = target[prop];
  }
  return result;
};

var user = {
  name: 'Jaenam',
  urls: {
    portfolio: 'http://github.com/abc',
    blog: 'http://blog.com',
    facebook: 'http://facebook.com/abc',
  },
};
var user2 = copyObject(user);
user2.name = 'Jung';

console.log(user.name === user2.name); // false

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true

얕은 복사를 하게 되면 user.urls가 불변 객체로 만들어지지 않아 깊은 복사를 통해 불변 객체로 만들어야 한다.

재귀적으로 깊은 복사를 해주는 함수를 다음과 같이 만들 수 있다.

var copyObjectDeep = function(target) {
  var result = {};
  if (typeof target === 'object' && target !== null) {
    for (var prop in target) {
      result[prop] = copyObjectDeep(target[prop]);
    }
  } else {
    result = target;
  }
  return result;
};

위 함수를 사용하게 되면 원본 데이터와 복사된 데이터가 각각 다른 주소값을 가지기 때문에 서로 영향을 주지 않는다.

보통은 위 함수를 구현하는 대신 간단하게 깊은 복사를 할 수 있는 방법이 있다.

var copyObjectViaJSON = function(target) {
  return JSON.parse(JSON.stringify(target));
};

JSON.strinfy를 통해 문자열화 하고 다시 JSON.parse를 통해 객체로 변경하는 것이다.

undefined와 null

undefined와 null은 자바스크립트에서 "없음"을 나타내는 값이다.

1. undefined

undefined는 개발자가 명시적으로 지정할 수 있고, 그렇지 않은 경우 값이 없을 때 자바스크립트 엔진이 자동으로 부여하는 경우가 있다.

명시적으로 값이 지정되지 않은 상황에 다음 세 경우에 자바스크립트 엔진이 undefined를 반환한다.

  • 값을 대입하지 않은 변수 (데이터 영역의 메모리 주소를 지정하지 않은 식별자 접근)
  • 객체 내부의 존재하지 않은 프로퍼티에 접근
  • return 문이 없거나 호출되지 않는 함수의 실행 결과

예시 코드를 통해 살펴보자

var a;
console.log(a); // (1) undefined. 값을 대입하지 않은 변수에 접근

var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) 존재하지 않는 프로퍼티에 접근
console.log(b); // c.f) ReferenceError: b is not defined

var func = function() {};
var c = func(); // (3) 반환(return)값이 없으면 undefined를 반환한 것으로 간주.
console.log(c); // undefined

배열에 대해서는 조금 다른 동작을 확인할 수 있다.

var arr1 = [];
arr1.length = 3;
console.log(arr1); // [empty x 3]

var arr2 = new Array(3);
console.log(arr2); // [empty x 3]

var arr3 = [undefined, undefined, undefined];
console.log(arr3); // [undefined, undefined, undefined]

arr1arr2 모두 배열의 각 요소에 undefined가 할당되어 있지 않다.

arr3는 명시적으로 undefined를 할당했기 때문에 undefined가 출력이 된다.

'비어있는 값'은 순회 대상에서 제외되고 undefined는 비어 있음을 나타내지만 명시적으로 할당한 경우 하나의 값으로 취급된다. 따라서 순회의 대상도 될 수 있는 것이다.

  • undefined 명시적으로 할당: 하나의 값으로 취급
  • undefined 명시적으로 할당X: 자바스크립트 엔진이 값이 존재하지 않는다는 "undefined" 반환
var arr1 = [undefined, 1];
var arr2 = [];
arr2[1] = 1;

arr1.forEach(function(v, i) {
  console.log(v, i);
}); // undefined 0 / 1 1
arr2.forEach(function(v, i) {
  console.log(v, i);
}); // 1 1

arr1.map(function(v, i) {
  return v + i;
}); // [NaN, 2]
arr2.map(function(v, i) {
  return v + i;
}); // [empty, 2]

arr1.filter(function(v) {
  return !v;
}); // [undefined]
arr2.filter(function(v) {
  return !v;
}); // []

arr1.reduce(function(p, c, i) {
  return p + c + i;
}, ''); // undefined011
arr2.reduce(function(p, c, i) {
  return p + c + i;
}, ''); // 11

2. null

명시적으로 비어있음을 나타내고 싶을 때는 null을 쓰면 된다.

undefined는 자바스크립트 엔진이 자동으로 내려주는 값으로만 구분지어서 사용할 수 있다.

주의점은 null의 타입이 object이다. 이는 자바스크립트의 자체 버그라고 한다.

var n = null;
console.log(typeof n); // object

값이 null인지 판별하기 위해서는 일치 연산자(===)를 사용하자.

var n = null;

console.log(n === undefined); // false
console.log(n === null); // true
profile
그냥 하자

0개의 댓글