코어 자바스크립트 정리 - 1. 데이터 타입

Books코어 자바스크립트데이터 타입

1) 데이터 타입

1. 데이터 타입의 종류

  • javasscript의 데이터 타입은 크게 두 가지로 나뉜다: 기본형, 참조형

    • 기본형: number, string, boolean, null, undefined, symbol
    • 참조형: object(Map, WeakMap, Set, WeakSet), array, function, date, regexp
  • 기본형: 할당/연산 시 복제된다

    • 값이 담긴 주솟값을 바로 복제
    • 불변성(immutability)를 띈다.
  • 참조형: 할단/연산 시 참조된다.

    • 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제

2. 데이터 타입에 관한 배경지식

  • 모든 데이터는 바이트 단위의 식별자, 즉 ‘메모리 주솟값’을 통해 서로 구분하고 연결할 수 있다.
  • 변수: variable. 변할 수 있는 데이터 (숫자, 문자열, 객체, 배열 등)
  • 식별자: 어떤 데이터를 식별하는데 사용하는 이름. (=변수명)

3. 변수 선언과 데이터 할당

변수 선언:

var a //  변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다.
  • 즉, 변수는 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇
  • 컴퓨터가 위와 같이 변수를 선언하면 메모리 영역에서 아래와 같은 작업을 수행한다.
주소 .. 1002 1003 1004 1005
데이터 이름: a / 값:

데이터 할당:

var a // 변수 a 선언
a = 'abc' // 변수 에 데이터 할당

var a = 'abc' // 변수 선언과 할당을 한 문장으로 표현
  • 실제로 해당 ‘a’ 변수 데이터에 문자열 ‘abc’를 직접 저장하지는 않는다. 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 문자열 ‘abc’를 저장하고, 그 주소를 변수 영역에 저장한다.

변수 영역

주소 .. 1002 1003 1004 1005
데이터 이름: a / 값: @5004

데이터 영역

주소 .. 5002 5003 5004 5005
데이터 ‘abc’
1) 변수 영역에서 빈 공간 (@1003)을 확보한다.
2) 확보한 공간의 식별자를 a로 지정한다.
3) 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장한다.
4) 변수 영역에서 a라는 식별자를 검색한다.(@1003).
5) 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.
  • 변수 영역에 값을 직접 대입하지 않고, 한단계를 더 거치는 이유?
    • 데이터 변환을 자유롭게 할 수 있게 함과 동시에, 메모리를 더욱 효율적으로 관리
    • js는 숫자형 데이터에 8바이트(64비트)의 공간을 확보하는데, 문자열은 정해진 규격이 없다. 메모리 용량이 가변적이므로 ‘확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업’이 필요 없음.
    • 결국, 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장해야함

4. 기본형 데이터와 참조형 데이터

불변값

  • 변수(variable)과 상수(constant)를 구분하는 성질은 ‘변경 가능성’
  • 불변값은 상수가 아님.
  • 변수 vs 상수 구분 짓는 변경 가능성의 대상은 변수 영역의 메모리
  • 불변성 여부를 구분할 때의 변경 가능성은 데이터 영역의 메모리
  • 기본형 데이터(숫자, 문자열, boolean, null, undefined, Symbol) 모두 불변값

불변성 예시:

var a = 'abc' // 변수 a에 'abc' 데이터 주소 할당
a = a + 'def' // 기본 'abc'에 'def' 추가가 아니라, 새로운 문자열 'abcdef'를 만들어 a에 주소 저장

// 즉, 'abc'와 'abcdef'는 완전 별개의 데이터

var b = 5 // 변수 b에 숫자 5 할당. 데이터 영역에서 5 찾고, 없으면 데이터 공간 만들어 저장
var c = 5 // b에 할당한 숫자 5의 주솟값 재활용
b = 7 // 기존 5값을 변경하지 않고, 데이터 영역에서 7을 찾거나 생성해서 주솟값 변경
  • 이처럼 문자열 값은 한번 만들면 변경할 수 없다.
  • 변경은 오로지 새로 만드는 동작을 통해서만 이뤄진다.
  • 한번 만들어진 값은 가비지 컬렉팅을 하지 않은 한 영원히 변하지 않는다.

가변값

  • 참조형 데이터의 기본적인 성질은 가변값이 경우가 많다.
  • 설정에 따라 변경 불가하게 활용할 수도 있음

참조형 데이터 할당:

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

변수 영역

주소 .. 1002 1003 1004 1005
데이터 이름: obj1, 값: @5001

데이터 영역

주소 .. 5001 5002 5003 5004
데이터 @7103 ~ ? 1 ‘bbb’

객체 @5001의 변수 영역

주소 .. 7103 7104 7105 7106
데이터 이름: a, 값: @5003 이름: b, 값: @5004
1) 컴퓨터는 변수 영역의 빈 공간(@1002)를 확보하고, 그 주소 이름을 obj1으로 지정
2) 임의의 데이터 저장공간 (@5001)에 저장하려고 보니 여러개의 프로퍼티로 이뤄진 데이터 그룹임.
이 그룹 내부의 프로퍼티를 저장하기 위해 별도의 변수 영역 마련 후, 그 영역의 주소(@7103 ~?)를 @5001에 저장
3) @7103 및 @7104에 각각 a와b라는 프로퍼티 이름 지정
4) 데이터 영역에서 숫자 1 검색.
검색 결과 없으므로 임의로 @5003에 저장하고 이 주소를 @7103에 저장.
'bbb'역시 마찬가지로 작업
  • 기본형 데이터와의 차이는 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 점이다.
  • 객체가 별도로 할애한 영역은 ‘변수 영역’일 뿐 ‘데이터 영역’은 기존의 메모리 공간 그대로 활용. 즉, 데이터 영역에 저장된 값은 불변값
  • 하디만 변수에는 언제든지 다른 값 대입할 수 있다. 이 점이 가변성.
  • 따라서 참조형 데이터는 불변(immutable)하지 않다고 한다.

가비지 컬렉터:

  • 어떤 데이터에 대해 자신의 주소를 참조하는 변수의 갯수를 ‘참조 카운트’ 라고 한다. (ex. 주소 @5003을 참조하는 주소는 @7103 하나 이므로 참조 카운트는 1)
  • 이때, @7103의 데이터 값을 @5004로 변경하면 (예: obj1 = {a: “bbb”, b: “bbb”}) 해당 데이터(@5003)의 참조 카운트는 0이 된다.
  • 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다.
    • 가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량 포화 상태 시 자동으로 수거한다.

변수 복사 비교

var a = 10
var b = a

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

변수 영역

주소 .. 1001 1002 1003 1004
데이터 이름: obj1, 값: @5001 이름:b, 값: @5001 이름: obj1, 값: @5002 이름: obj2, 값: @5002

데이터 영역

주소 .. 5001 5002 5003 5004
데이터 10 @7103 ~ ? ‘ddd’

객체 @5002의 변수 영역

주소 .. 7103 7104 7105 7106
데이터 이름: c, 값: @5001 이름: d, 값: @5003
  • 변수를 단순히 선언, 할당, 복사하는 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보는 점에서 동일하다.
  • 복사 과정은 동일하지만, 데이터 할당 과정과 복사 이후의 동작에서 차이가 발생함.

(1) 변수 복사 이후 값 변경 결과 비교 - 객체의 프로퍼티 변경 시

var a = 10
var b = a

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

b = 15
objc2.c = 20

위 처럼 복사된 객체의 프로퍼티를 변경하면 아래와 같은 흥미로운 결과가 나온다:

a !== b
obj1 === obj2

변수 a와 b는 서로 다른 주소를 바라보게 되었으나, 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있다.

왜 그럴까?

  • 기본형 데이터를 복사한 변수 b의 값을 바꾸면, b 변수의 데이터 값(주소)만 달라진다.
  • 반면, 참조형 데이터 복사 변수 obj2의 프로퍼티 값을 바꾸면, obj1, obj2의 값(주소)는 동일하면서, 해당 값(주소)에 있던 데이터가 변경된다.
  • 즉, 변수 a와 b는 서로 다른 주소를 바라보게 되었으나, 변수 obj1과 obj2는 같은 객체를 바라본다.

메모리를 자세히 들여다 보면, 아래와 같이 변경이 되어있다.

변수 영역

주소 .. 1001 1002 1003 1004
데이터 이름: obj1, 값: @5001 이름:b, 값: @5004 이름: obj1, 값: @5002 이름: obj2, 값: @5002

데이터 영역

주소 .. 5001 5002 5003 5004 5005
데이터 10 @7103 ~ ? ‘ddd’ 15 20

객체 @5002의 변수 영역

주소 .. 7103 7104 7105 7106
데이터 이름: c, 값: @5005 이름: d, 값: @5003
  • “기본형은 값을 복사하고 참조형은 주솟값을 복사한다” 그럼 이 말은 어떤 뜻일까?

    • 엄밀히 따지면 js의 모든 데이터 타입은 참조형이다
    • 다만, 기본형은 주솟값을 한번만 복사하고, 참조형은 한단계 더 거치므로 차이가 생긴다
  • 단, 아래 예시처럼 참조형 데이터의 속성이 아닌 참조형 데이터 객체 자체를 변경하면 값이 달라진다.

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

obj2 = { c: 20, d: 'ddd' }
  • 즉, 참조형 데이터가 ‘가변값’일 때는 참조형 데이터 자체를 변경할 경우가 아니라, 내부의 프로퍼티를 변경할 때만 성립한다.

5. 불변 객체

불변 객체를 만드는 간단한 방법

  • 불변 객체 (immutable object)는 최근 React, Vue.js, Angular 등의 라이브러리 프레임워크 뿐만 아니라, 함수형 프로그래밍, 디자인 패턴 등에서도 매우 중요함
  • 객체도 데이터 차제를 변경하고자 하면 (새로운 데이터 할당) 기본형과 마찬가지로 기존 데이터는 변하지 않는다 (불변성)
    • 내부 속성 변경 시 가변성
  • 따라서, 내부 속성 변경시마다 매번 새로운 객체를 만들어 재할당을 하거나, 자동으로 새로운 객체를 만드는 도구를 활용하면 객체도 불변성을 확보할 수 있음
    • ex) immutable.js, immmer js, immutability-helper 등의 라이브러리. ES6의 spread operator, Object.assign 메서드

왜 불변 객체가 필요할까?

  • 값으로 전달받은 객체에 변경을 주더라도, 원본 객체는 변하지 않아야 하는 경우가 있음.
var user = {
  name: 'Kim',
  gender: 'female',
}

var changeName = function (user, newName) {
  var newUser = user
  newUser.name = newName
  return newUser
}

var user2 = changeName(user, 'Jung')

console.log(user.name, user2.name) // Jung Jung

위 예시처럼 user2 호출 시, 내부 프로퍼티만 변경함으로써 기존 user 객체를 동시에 변경하였다. (가변성)

user와 user2가 다른 값을 가질 수 있도록 하려면 아래와 같이 새로운 객체를 반환하도록 수정하면 된다.

var user = {
  name: 'Kim',
  gender: 'female',
}

var changeName = function (user, newName) {
  return {
    name: newName,
    gender: user.gender,
  }
}

var user2 = changeName(user, 'Jung')

console.log(user.name, user2.name) // Kim Jung

하지만, 변경된 코드의 경우 변경할 필요가 없는 기존 객체의 ‘gender’속성도 하드코딩으로 입력했다. 객체에 정보가 많을수록 비효율적이게 되는데, 프로퍼티 갯수에 상관없이 모든 프로퍼티를 복사하는 함수를 만들 수 있다.

// 얕은 복사
var copyObject = function (target) {
  var result = {}
  for (var prop in target) {
    result[prop] = target[prop]
  }
}
  • 물론 협업하는 모든 개발자들이 copyObject라는 얕은 복사를 사용하면 문제가 되지 않는다. (=user 객체가 곧 불변 객체)
  • 하지만, 그 규칙을 지키지 않을 수도 있으므로 아예 프로퍼티를 변경할 수 없게 제약을 거는게 더 안전하다.

얕은 복사와 깊은 복사

  • 얕은 복사(shallow copy): 바로 아래 단계의 값만 복사.

    • ex) 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사
    • 즉, 원본과 사본이 동일한 참조형 데이터 주소를 가리키므로 사본을 바꾸면 원본도 변경됨
  • 깊은 복사(deep copy): 내부의 모든 값들을 하나하나 찾아서 전부 복사.

    • ex) 중첩된 객체에서 프로퍼티 내부의 값들까지 전부 복사. 재귀로 구현 가능
// 깊은 복사
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
}
  • target이 객체인 경우에는 내부 프로퍼티를 순회하며 copyObjectDeep 함수를 재귀적으로 호출하고, 객체가 아닌경우에는 target을 그대로 지정한다.
  • 이 함수는 원본과 사본이 서로 완전히 다른 객체를 참조하게 하여 어느쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않는다.

6. undefined와 null

  • undefinednull은 자바스크립트에서 모두 ‘없음’을 나타낸다. 하지만 미세하게 차이점이 있다.

1. undefined

  • 자바스크립트 엔딘은 사용자가 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로 그렇게 하지 않았을 때 undefined를 반환한다.
      1. 값을 대입하지 않은 변수. 즉, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
      1. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
      1. return 문이 없거나 호출되지 않는 함수의 실행 결과

2. null

  • null은 ‘비어있음’을 명시적으로 나타내고 싶을 때 사용한다.
  • typeof null은 object라고 반환되는데, 이는 자바스크립트 자체 버그이다. 따라서 변수의 값이 null인지 확인하려면 일치 연산자로 확인을 해줘야 한다.

undefined와 null을 구분하기 위해서는 동등 연산자 대신, 일치 연산자(===)를 사용해야 한다.

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

console.log(n == undefined) // true
console.log(n == null) // true

console.log(n === undefined) // false
console.log(n === null) // true

참고

  • 정재남, 『코어 자바스크립트』, 위키북스(2019), p1-35.



Profile picture
@김하연
3년차 프론트엔드 개발자 입니다. 사용자 경험 개선, 코드의 재사용성, 읽기 쉬운 코드에 집중하여 개발합니다.
AboutGithub LinkedinResume
Loading script...