[Core JavaScript] 01. 데이터 타입

Ethan·2022년 12월 12일
post-thumbnail

본 내용은 정재남 님의 코어 자바스크립트 책을 읽고 정리한 내용을 작성했습니다.

Chapter 01. 데이터 타입

1. 데이터 타입의 종류

1-1. 기본(원시)형 Primitive Type

  • 숫자 number
  • 문자열 string
  • 불리언 boolean
  • null
  • undefined
  • 심볼 symbol

1-2. 참조형 Reference Tyep

  • 객체 object
    • 배열 array
    • 함수 function
    • 날짜 Date
    • 정규 표현식 RegExp
    • Map, WeakMap
    • Set, WeakSet

❓ 기본형과 참조형은 어떤 기준으로 참조 되는 것 일까?

  • 기본형은 할당이나 연산시 복제되고 참조형은 참조되는 것으로 알려져 있고, 엄밀히 말하면 이 둘 모두 복제를 수행한다.
  • 여기서 기본형은 값이 담긴 주소 값을 바로 복제하는 반면 참조형은 값이 담긴 주소 값이 이루어진 묶음을 가리키는 주소 값을 복제한다.

💡 기본형은 불변성을 가진다에 대하여

  • Q. 기본형인 숫자 10을 담은 변수 a에 다시 숫자 15를 담으면 a의 값은 문제 없이 15로 변하는데, 이 불변하다는 것은 무슨 의미일까?
  • A. 이것을 알려면 메모리와 데이터에 대한 지식이 필요로 하며, 메모리 영역에서 데이터가 어떻게 처리 되는지 이해할 필요가 있다.

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

2-1. 메모리와 데이터

비트 bit

  • 0 또는 1만 표현할 수 있는 하나의 메모리 조각 단위를 부르는 말
  • 메모리에는 이 수 많은 비트들로 구성돼 있고, 각 비트는 고유한 식별자를 통해 위치를 파악한다.

바이트 byte

  • 8개의 비트로 구성되어 있는 메모리 덩어리 단위
  • 1바이트는 8개의 비트, 1비트 마다 0 또는 1의 두 가지 값을 표현할 수 있기에 1바이트트는 256(2⁸)개의 값을 표현할 수 있다.
  • 값을 찾기 위해 비트 단위로 일일히 위치를 찾는 것은 비효율적이기 때문에 특정 단위로 묶어 표현할 수 있는 값을 늘리면서
    검색 시간을 줄일 수 있는 방안을 고안하게 됐으며 그 결과가 바이트와 같은 단위의 개념이 생긴 것이다.

자바스크립트의 메모리

  • 과거의 메모리 용량이 부족했던 시절과 다른 상황에서 등장하게 된 자바스크립트는 기존의 정적 타입을 지원하는 언어들과 달리 상대적으로 메모리 관리 측면에서 자유롭다.
  • 숫자의 경우 정수형, 부동소수형을 상관하지 않는 64비트(8바이트)를 확보하게 되고, 따라서 이 부분에 대해서 자바스크립트 개발자가 메모리로 인한 형변환을 걱정 하는 일이 크게 줄었다.

2-2. 식별자와 변수

간혹 이 두 용어를 혼용해서 쓰는 경우가 많은데, 문맥에 따라 혼용해서 사용할 수 있지만 구분할 필요는 있다.

식별자

  • 어떤 데이터를 식별하는 데 사용하는 이름으로 변수명을 의미한다.

변수

  • 변할 수 있는 무언가를 의미하며, 이 무언가는 데이터를 말한다.
    • 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇
  • 데이터는 숫자, 문자열, 불리언, 객체, 배열 등 모두를 포함한다.

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

3-1. 변수 선언

var a;
  • 기본적인 변수 선언식
  • 변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 정한다.

변수 선언에 대한 메모리 영역의 변화 ( 변수 선언 과정 )

  1. 위 명령을 받은 컴퓨터는 메모리에서 비어있는 공간 하나를 확보한다.
  2. 표에 의하면 임의로 1003번으로 정하게 됐고, 이 공간의 이름(식별자)을 a라고 지정한다.
주소1002100310041005
데이터이름: a
값:

3-2. 데이터 할당

01 var a; // 변수 a 선언
02 a = 'abc'; // 변수 a에 데이터 할당
03 
04 var a = 'abc'; // 변수 선언과 할당을 한 문장으로 표현
  • 선언과 할당을 1~2번째 줄과 같이 두 문장으로 나누어 명령할 수도 있고, 4번째 줄처럼 한 문장으로 명령할 수 있다. (어느 쪽이든 자바스크립트 엔진은 같은 동작을 수행한다.)
  • 할당 과정은 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 문자열 ‘abc’를 저장하고, 그 주소를 변수 영역에 저장하는 식으로 이루어진다.

데이터 할당에 대한 메모리 영역의 변화 ( 데이터 할당 과정 )

  1. 변수 영역에서 빈 공간(@1003)을 확보한다.
  2. 확보한 공간의 식별자를 a로 지정한다.
  3. 데이터 영역의 빈 공간(@5004)에 문자열 ‘abc’를 저장한다.
  4. 변수 영역에서 a라는 식별자를 검색한다.(@1003)
  5. 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.
  • 변수 영역
주소1002100310041005
데이터이름: a
값: @5004
  • 데이터 영역
주소5002500350045005
데이터‘abc’

❓ 왜 변수 영역에 값을 직접 대입하지 않고 한 단계를 더 거치는 걸까?

이유 1. 데이터 변환을 자유롭게 할 수 있다.

  • 숫자형 데이터에 대해 64비트 공간을 확보하는 반면 문자열은 정해진 규격이 없어 한 글자마다 영어는 1바이트, 한글은 2바이트 등으로 각 필요한 메모리 용량이 가변적이고, 전체 글자 수 역시 가변적이다.
  • 만약 미리 확보한 공간 안에 데이터 변환을 할 수 있다면 변환한 데이터를 다시 저장하기 위해서 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 선행된다.

이유 2. 메모리를 더욱 효율적으로 관리할 수 있다.

  • 중간에 데이터를 늘려야하는 상황이라면 해당 공간보다 더 뒤에 저장된 데이터를 전부 뒤로 옮기고,
    이동시킨 주소를 각 식별자에 다시 연결하는 작업이 필요하고, 그것은 즉 컴퓨터가 처리해야할 연산이 많다는 의미다.
  • 따라서 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장하는 것이 최적이다.
  • 예로 500개의 변수를 생성해 모든 변수에 숫자 5를 할당한다고 하면 숫자형은 8바이트가 필요하므로 500 x 8, 즉 4000바이트를 사용하지만, 5를 별도의 공간에 한 번만 저장하고 해당 주소만 입력받는다면 주소 공간의 크기가 2 바이트라고 할 때, 500 x 2 + 8로 1008 바이트만 이용하면 된다.

문자열 변환에 대한 메모리 영역의 변화

  1. 문자열 ‘abc’의 마지막에 ‘def’를 추가하려고 하면, 기존 문자열에 어떤 변환이 있더라도 무조건 새로운 공간을 만들어 그 공간에 데이터를 저장하게 되며, 값을 참조하는 주소가 변경된다.
  2. 기존(@5004) 데이터는 자신의 주소를 저장하는 변수가 하나도 없게 되어 가비지 컬렉터에 의해 지워진다.
  • 변수 영역
주소1002100310041005
데이터이름: a
값: @5004
  • 데이터 영역
주소5002500350045005
데이터‘abc’‘abcdef’

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

4-1. 불변 값

  • 기본형 데이터들이 가진 성질로 데이터 영역에서의 메모리 공간에 값을 변경하지 않는 것을 의미한다.
  • 불변 값과 상수는 같은 개념이 아니며, 이 둘을 명확하게 구분하고 있을 필요가 있다.

상수

  • 데이터 할당이 이루어진 변수 영역의 메모리 공간에 다른 데이터를 재할당(변경) 할 수 없는 것을 말한다.

기본형 데이터 할당 예시

  • 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않으며, 변경은 새로운 데이터의 공간을 만들어 저장하고 그것을 참조하는 것을 통해서만 이루어진다.
01 var a = 'abc';
02 a = a + 'def';
03
04 var b = 5;
05 var c = 5;
06 b = 7;
  1. 1~2번째 줄에서 변수 a에 ‘abc’할당
  2. ‘def’를 추가하면 기존의 ‘abc’와 합쳐 새로운 문자열인 ‘abcdef’를 만들고 그 주소를 변수 a에 저장한다.
    (이때 abc와 abcdef는 완전히 별개의 데이터다.)
  3. 4번째 줄에서는 변수 b의 숫자5를 할당한다. 그러면 컴퓨터는 데이터 영역에서 5를 찾고, 없으면 그때 데이터 공간을 하나 만들어 저장한다.
  4. 5번째 줄에서 4번 줄에서 다시 같은 수인 5를 할당하려고 한다면, 컴퓨너틑 데이터 영역에서 5를 찾고, 4번째 줄에서 이미 만들어 놓은 값이 있어서 그 주소를 재활용한다.
  5. 6번째 줄처럼 변수 b의 값을 7로 바꾸면, 기존에 저장된 5 자체를 7로 바꾸는게 아니라 기존에 저장한 7을 찾아서 있으면 재활용하고, 없으면 새로 만들어서 b에 저장한다.
  6. 결국 5와 7 모두 다른 값으로 변경할 수는 없다.

4-2. 가변 값

  • 참조형 데이터들의 기본적인 성질로 데이터 영역에서의 메모리 공간에 값을 변경할 수 있는 것을 의미한다.
  • 설정에 따라서 불변 값으로 활용할 수 있는 방법도 존재한다.(Object.defineProperty, Object.freeze 등)

참조형 데이터의 할당 예시

  • 기존 데이터와의 차이점은 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 것이다.
  • 변수 영역만 제공할 뿐 데이터 영역은 기존의 메모리 공간을 그대로 활용한다.
    • 그러므로 참조형 타입에서 제공하는 이 변수 영역에는 다른 값을 얼마든지 대입하고, 변경할 수 있다.
01 var obj1 = {
02 	a: 1,
03 	b: 'bbb'
04 };
  • 변수 영역
주소1001100210031004
데이터이름: obj1
값: @5001
  • 데이터 영역
주소5001500250035004
데이터@7103 ~ ?1‘bbb’
  • 객체 @5001 변수 영역
주소7103710471057106
데이터이름 : a
값: @5003이름: b
값: @5004
  1. 컴퓨터는 우선 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj1으로 지정한다.
  2. 임의의 데이터 공간(@5001)에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이루어진 데이터의 그룹임을 확인하게 된다.
  3. 그룹 내부의 프로퍼티를 저장하기 위한 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~ ?)를 @5001에 저장한다.
  4. @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름을 지정한다.
  5. 데이터 영역에서 숫자 1을 검색한다. 검색 결과가 없으므로 임의의 주소 @5003에 저장하고, 이 주소를 @7103에 저장한다. 문자열 ‘bbb’ 역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장한다.

참조형 데이터의 프로퍼티 재할당 예시

01 var obj1 = {
02 	a: 1,
03 	b: 'bbb'
04 };
05 obj1.a = 2;
  1. 5번째 줄에 obj1의 a프로퍼티에 숫자 2를 할당한다.
  2. 데이터 영역에서 숫자 2가 있는지 검색한다.
  3. 검색 결과가 없으면 빈 공간인 @5005에 저장하고, 이 주소를 @7103에 저장한다.
  4. 변수 obj1이 바라보고 있는 주소 @5001은 변하지 않고, 기존의 객체 내부의 값에서 변경사항이 발생한다.
  • 변수 영역
주소10011002100310041005
데이터이름: obj1
값: @5001
  • 데이터 영역
주소50015002500350045005
데이터@7103 ~ ?1‘bbb’2
  • 객체 @5001 변수 영역
주소71037104710571067107
데이터이름 : a
값: @5005이름: b
값: @5004

중첩된 참조형 데이터의 프로퍼티 할당 예시

참조형 데이터의 프로퍼티에 다시 새로운 참조형 데이터를 할당하는 경우 중첩 객체라고 부른다.

01 var obj = {
02    x: 3,
03    arr: [ 3, 4, 5 ]
04 };
  • 변수 영역
주소10011002100310041005
데이터이름: obj1
값: @5001
  • 데이터 영역
주소50015002500350045005
데이터@7103 ~ ?3@8104 ~ ?45
  • 객체 @5001 변수 영역
주소71037104
데이터이름 : x
값: @5002이름: arr
값: @5003
  • 배열 @5003 변수 영역
주소810481058106
데이터이름 : 0
값: @5002이름: 1
값: @5004이름: 2
값: @5005
  1. 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj로 지정한다.
  2. 임의의 데이터 저장공간(@5001)에 데이터를 저장한다.
    • 이때 데이터는 여러 개의 변수와 값들을 모아놓은 그룹(객체)이므로 이 그룹의 각 변수들을 저장하기 위해 별도의 변수 영역을 생성하고(@7103~?), 그 영역의 주소를 @5001에 저장한다.
  3. @7103에 이름 x, @7104에 이름 arr를 지정한다.
  4. 데이터 영역에서 숫자 3을 검색한다.
    • 숫자 3이 없기 때문에 임의의 @5002에 저장하고, 이 주소를 @7103에 저장한다.
  5. @7104에 저장할 값은 배열로서 역시 데이터 영역에서 검색한다.
    • 해당하는 주소 값이 없기 때문에 이 그룹 내부의 프로퍼티를 저장하기 위해 별도의 변수 영역을 마련하고(@8104~?), 그 영역의 주소 정보(@8104~?)를 @5003에 저장한 후, @5003을 @7104에 저장한다.
  6. 배열의 요소가 총 3개이므로 3개의 변수 공간을 확보하고 각 인덱스를 부여한다.
  7. 데이터 영역에서 숫자 3을 검색해서(@5002) 그 주소를 @8104에 저장한다
  8. 데이터 영역에 숫자 4가 없으므로 @5004에 저장하고, 이 주소를 @8105에 저장한다.
  9. 데이터 영역에 숫자 5가 없으므로 @5005에 저장하고, 이 주소를 @8106에 저장한다.

여기서 obj.arr[1]을 검색하고자 하면 메모리에서는 다음과 같은 검색 과정을 거친다.
@1002 → @5001 → (@7103 - ?) → @7104 → @5003 → (@8104 - ?) → @8105 → @5004 → 4 반환

중첩된 참조형 데이터의 프로퍼티 재할당 예시

현재 상태에서 아래와 같은 재할당 명령을 내리면 어떻게 동작할까?

01 var obj = {
02    x: 3,
03    arr: [ 3, 4, 5 ]
04 };
05 obj.arr = 'str';
  • 변수 영역
주소100110021003100410051006
데이터이름: obj1
값: @5001
  • 데이터 영역
주소500150025003500450055006
데이터@7103 ~ ?3@8104 ~ ?45‘str’
  • 객체 @5001 변수 영역
주소71037104
데이터이름 : x
값: @5002이름: arr
값: @5006
  • 배열 @5003 변수 영역
주소810481058106
데이터이름 : 0
값: @5002이름: 1
값: @5004이름: 2
값: @5005
  1. @5006에 문자열 ‘str’을 저장하고, 그 주소를 @7104에 저장한다.
    • @5003은 더이상 자신의 주소를 참조하는 변수가 없는 상태가 된다.
  2. 참조하고 있는 카운트 수가 0이 되면서 해당 메모리 주소들은 가비지 컬렉터의 수거 대상이 된다.
    • 가비지 컬렉터는 런타임 환경에 따라 특정 시점 혹은 메모리 사용량이 포화 상태에 임박할 때마다 자동적으로 수거 대상들을 수거한다.
    • 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 된다.
  3. @5003이 수거되고 나면 이후 이를 참조하고 있던 @8104 ~ ? 범위의 각 데이터들도 참조 카운트가 0이 되면서 GC 대상이 되어 이후에 사라지게 된다.

4-3. 변수 복사 비교

기본 타입과 참조 타입 - 변수 복사 비교 예시

01 var a = 10;
02 var b = a;
03
04 var obj1 = { c: 10, d: 'ddd' };
05 var obj2 = obj1;
  • 변수 영역
주소1001100210031004
데이터이름: a
값: @5001이름: b
값: @5001이름: obj1
값 : @5002이름: obj2
값 : @5002
  • 데이터 영역
주소5001500250035004
데이터10@7103 ~ ?‘ddd’
  • 객체 @5002 변수 영역
주소71037104
데이터이름 : c
값: @5001이름: d
값: @5003
  1. 기본형의 경우 변수 a를 b에 복사한다면 다음과 같은 일이 벌어진다.
    • @1001에 저장된 값인 @5001을 b에 확보해둔 @1002 공간에 값으로 할당한다.
  2. 참조형의 경우 변수의 영역을 저장하기 때문에 값을 복사하면 그 식별자 역시 같은 변수의 영역을 가리키는 주소 값을 저장하게 된다.

기본 타입과 참조 타입 - 변수 복사 이후 값 변경 결과 비교 예시(1)

01 var a = 10;
02 var b = a;
03
04 var obj1 = { c: 10, d: 'ddd' };
05 var obj2 = obj1;
06
07 b = 15;
08 obj2.c = 20;
  • 변수 영역
주소10011002100310041005
데이터이름: a
값: @5001이름: b
값: @5004이름: obj1
값 : @5002이름: obj2
값 : @5002
  • 데이터 영역
주소50015002500350045005
데이터10@7103 ~ ?‘ddd’1520
  • 객체 @5002 변수 영역
주소71037104
데이터이름 : c
값: @5005이름: d
값: @5003
  1. 7번째 줄에서 원시 타입을 복사한 식별자 b에 15의 값을 재할당한다.
    • 데이터 영역에 15가 없기 때문에 새로운 공간에 저장하고, 그 주소를 변수 영역의 식별자 b의 주소를 찾아 값으로 매핑시킨다.
  2. 8번째 줄은 참조 타입을 복사한 식별자 obj2가 참조하고 있는 식별자 c의 값을 20으로 재할당한다.
    • 마찬가지로 데이터 영역에 20이 없으므로 새로운 공간에 저장하고, 그 주소를 변수 영역의 obj2의 값인 @5002에 접근하고, 해당 변수 영역에서 다시 c를 찾아 자신의 공간인 @5005를 값으로 매핑시킨다.
  3. 기본형 데이터를 복사한 식별자 b의 값은 변경되어 기존에 복사한 식별자 a와 서로 다른 주소를 가지게 된다.
  4. 참조형 데이터를 복사한 식별자 obj2의 경우 참조하고 있는 주소 내부의 식별자 값이 바뀐 것이라 여전히 식별자 obj1과 같은 주소를 바라보는 상태다.
💡 기본형 복사와 참조형 복사에 대해
- 사실 자바스크립트에서 모든 데이터 타입은 참조형 데이터다. 
다만 기본형은 주소 값을 복사하는 과정이 한 번만 이루어지고, 참조형은 한 단계를 더 거치게 된다는 차이가 있다.

기본 타입과 참조 타입 - 변수 복사 이후 값 변경 결과 비교 예시(2)

만약 obj2에 새로운 객체를 할당함으로써 값을 직접 변경하면 어떻게 될까?

01 var a = 10;
02 var b = a;
03
04 var obj1 = { c: 10, d: 'ddd' };
05 var obj2 = obj1;
06
07 b = 15;
08 obj2.c = { c: 20, d: 'ddd' };
  • 변수 영역
주소100110021003100410051006
데이터이름: a
값: @5001이름: b
값: @5004이름: obj1
값 : @5002이름: obj2
값 : @5006
  • 데이터 영역
주소500150025003500450055006
데이터10@7103 ~ ?‘ddd’1520@8204 ~ ?
  • 객체 @5002 변수 영역
주소71037104
데이터이름 : c
값: @5001이름: d
값: @5003
  • 객체 @5006 변수 영역
주소82048205
데이터이름 : c
값: @5005이름: d
값: @5003
  • 메모리의 데이터 영역의 새로운 공간에 새 객체가 저장되고 그 주소를 변수 영역의 obj2 위치에 저장하게 된다.
  • 즉, 참조형 데이터의 가변은 데이터 자체의 변경이 아니라 그 내부의 프로퍼티를 변경할 때만 성립된다.

5. 불변 객체

5-1. 불변 객체

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

  • 새로운 객체를 만드는 라이브러리 등을 사용

    • Immutable.js
    • immer.js
    • baobab.js
    • Lodash
  • ES6의 메서드 활용하기

    • Spread Operator
    • Object.assign

불변 객체 문제 해결 하는 방법 - 함수를 통해 새로운 객체를 생성

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

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

ver user2 = changeName(user, 'Winters');

if(user !== user2) {
	console.log('유저의 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // Ethan Winters
console.log(user === user2); // false
  • user와 user2는 서로 다른 객체를 반환되며 안전하게 변경 전과 후를 비교할 수 있게 된다.
  • 대상 객체에 정보가 많을 수록, 변경할 정보가 많을 수록 사용자가 입하는 수고가 늘어나는 단점이 있다.

불변 객체 문제 해결 하는 방법 - 기존 정보를 복사해서 새로운 객체를 생성(얕은 복사)

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

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

var user2 = copyObject(user);
user2.name = 'Winters';

if(user !== user2) {
	console.log('유저의 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // Ethan Winters
console.log(user === user2); // false
  • target으로 받아온 객체의 프로퍼티를 복사함으로써 불변 객체를 보다 효율적으로 만들 수 있다.
  • 다만 이것 또한 얇은 복사를 수행하고, 프로토타입 체이닝 상의 모든 프로퍼티를 복사하며, getter와 setter를 복사하지 않는 단점이 존재한다. (이 문제를 보완하려면 함수가 무거워진다.)

5-2. 얕은 복사와 깊은 복사

얕은 복사 Shallow Copy

  • 아래 단계의 값만 복사하는 방법
  • 중첩된 객체의 참조형 데이터가 저장된 프로퍼티를 복사하게 되면 그 주소 값을 복사하게 된다.
  • 즉, 깊이가 2 이상의 객체에 대해서 다시 가변의(참조하는) 성질을 가지게 된다는 의미다.

깊은 복사 Deep Copy

  • 내부의 모든 프로퍼티를 직접 전부 복사하는 방법

    • 재귀 함수
    • JSON.parse + JSON.stringify
  • 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게 하는 방법

    • hasOwnProperty 메서드 활용하기
  • ES5의 getter/setter를 복사하는 방법

    • ES2015 : Object.getOwnPropertyDescriptor
    • ES2017 : Object.getOwnPropertyDescriptors

객체의 깊은 복사를 수행하는 함수로 개선 ( 재귀 함수 )

var copyObjectDeep = function(target) {
	var result = {};
	if(typrof target === 'object' && target !== null) {
			for (var prop in target) {
				result[prop] = copyObjectDeep(target[prop]);
		}
	} else {
		result = target;
	}
	return result;
};
  • target이 객체인 경우 내부 프로퍼티를 순회하며 copyObjectDeep 함수를 재귀적으로 호출한다.
    • target ≠ null 의 경우 typeof가 null에 대해서도 ‘object’를 반환하기 때문에 처리해주는 조건이다.
  • 객체가 아니라면 target을 그대로 지정하여 완전하게 중첩된 객체까지 완전히 원본과 사본이 서로 다른 깊은 복사를 만들 수 있다.

깊은 복사 결과 확인 예시

var obj = {
	a: 1,
	b: {
			c: null,
			d: [1, 2]
	}
};

var obj2 = copyObjectDeep(obj);

obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;

console.log(obj); // { a: 1, b: { c: null, d: [1, 2] } }
console.log(obj2); // { a: 3, b: { c: 4, d: [1, 3] } }

JSON을 활용한 간단한 깊은 복사 예시

  • JSON 문법의 문자열로 변환했다가 다시 JSON 객체로 변환하여 깊은 복사를 수행할 수 있다.
var copyObjectViaJSON = function(target) {
	return JSON.parse(JSON.stringify(target));
};
  • httpRequest로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 활용하기 좋은 복사 방법
  • 객체 관계가 모두 끊기기 때문에 메서드, __proto, getter/setter 등의 프로퍼티들은 사용할 수 없게 된다.

6. undefined 와 null

자바스크립트에서 ‘없음’을 나타내는 값으로, 이 두 값은 의미도 살짝 다르고 사용하는 목적도 다르다.

6-1. undefined

  • 값이 존재 하지 않음을 나타내는 값
  • 사용자가 잘못된 값의 접근을 할 때, 실제 값이 없다는 의미로 자바스크립트 엔진이 자동으로 부여한다.
    • 명시적으로도 사용할 수 있으나 컨벤션이나 관례 상 자동(암묵적인)으로 할당되는 값으로만 사용된다.

자동으로 undefined를 부여하는 경우 예시

다음과 같은 경우에 그 위치에 undefined를 반환한다.

01 var a;
02 console.log(a); // (1) undefined. 값을 대입하지 않은 변수에 접근
03
04 var obj = { a: 1 };
05 console.log(obj.a); // 1
06 console.log(obj.b); // (2) undefined. 존재하지 않은 프로퍼티에 접근
07 console.log(b); // c.f) ReferenceError: b is not defined
08
09 var func = function() {};
10 var c = func(); // (3) 반환 값이 없으면 암시적으로 undefined를 반환한 것으로 간주한다.
11 console.log(c); // undefined
  1. 값을 대입하지 않은 변수
  2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려는 경우
  3. return 문이 없거나 호출되지 않는 함수의 실행 결과

undefined와 배열 예시

01 var arr1 = [];
02 arr1.length = 3;
03 console.log(arr1); // [empty x 3]
04
05 var arr2 = new Array(3);
06 console.log(arr2); // [empty x 3]
07
08 var arr3 = [undefined, undefined, undefined];
09 console.log(arr3); // [undefined, undefined, undefined]
  1. 1~3번째 줄에 대한 결과는 배열에 3개의 빈 요소를 확보했지만 확보된 각 요소에는 그 어떠한 값도 할당되어 있지 않음을 의미하는 출력 결과다. ( undefined 조차 할당되지 않은 상태 )
  2. 5~6번째 줄에 대한 결과는 new 연산자를 이용해 Array 생성자 함수를 호출하여 배열 인스턴스를 생성하는 방식으로 이 또한 위에서 출력한 결과와 동일하다.
  3. 8~9번째 줄에서 리터럴 방식에 undefined를 부여하게 된다면 이전에 출력한 결과와 다른 결과를 보여준다.

빈 요소와 배열의 순회 예시

비어있는 요소(empty)와 undefined를 할당한 요소는 출력한 결과가 다르다.

  • 비어있는 요소의 경우 이터러블(반복 가능한) 취급이 되지 않아 배열 메서드에서 순회 대상에 제외된다.
01 var arr1 = [undefined, 1]; // [undefined, 1]
02 var arr2 = [];
03 arr2[1] = 1; // [empty, 1]
04
05 arr1.forEach(function(v, i) { console.log(v, i); }); // undefined 0 / 1 1
06 arr2.forEach(function(v, i) { console.log(v, i); }); // 1 1
07
08 arr1.map(function(v, i) { return v + i; }); // [NaN, 2]
09 arr2.map(function(v, i) { return v + i; }); // [empty, 2]
10
11 arr1.filter(function(v) { return !v; }); // [undefined]
12 arr2.filter(function(v) { return !v; }); // []
13
14 arr1.reduce(function(p, c, i) { return p + c + i; }, ''); // undefined011
15 arr2.reduce(function(p, c, i) { return p + c + i; }, ''); // 11
  1. arr1은 undefined와 1을 직접 할당하고, arr2는 빈 배열의 인덱스 1에 값 1을 할당한다.
  2. 각 순회 가능한 메서드(forEach, map, filter, reduce) 등에서 서로 다른 결과를 보인다.
    • forEach에서 arr2의 empty는 순회 대상에서 제외 됨을 확인할 수 있다.
    • 이외 다른 메서드도 마찬가지로 empty를 순회에서 고려하지 않고 건너뛰는 것을 알 수 있다.
  3. 값이 지정되지 않은 인덱스는 아직은 존재하지 않는 프로퍼티를 의미한다.
    • 특정 인덱스에 값을 지정해야 빈 공간을 확보하고 인덱스를 이름으로 지정하고 데이터의 주소 값을 저장하는 등의 동작을 수행한다.

명시적 undefined와 암묵적 undefined

  • 명시적 undefined

    • 비어있음을 나타내기 위한 값(데이터)
    • 값으로 취급되어 프로퍼티나 배열의 요소는 고유의 키 값이 실존하게 되고, 순회의 대상이 된다.
    • 명시적으로 사용하는 것은 순회되는 값으로도 사용될 수 있기 때문에 암묵적으로 제공되는 undefined와 섞이는 경우 혼란을 만든다.
  • 암묵적 undefined

    • 자바스크립트 엔진이 사용자 대신해서 반환 처리하는 값으로 실제로 값이 존재 하지 않음을 의미한다.
    • 해당 프로퍼티나 배열의 인덱스 자체가 존재하지 않은 상태로 순회 대상에서 제외된다.

6-2. null

  • 값이 존재 하지 않음을 나타내는 값
  • 명시적으로 비어있음을 나타내기 위한 값으로 사용된다.

null 타입의 버그

  • typeof null === object
  • 자바스크립트 자체 버그 (…😊)
  • 어떤 변수의 값이 null인지 여부를 판별하기 위해서는 다른 방식이 필요로 하다.
01 var n = null;
02 console.log(typeof n); // object
03
04 console.log(n == undefined); // true
05 console.log(n == null); // true
06
07 console.log(n === undefined); // false
08 console.log(n === null); // true
  1. 4~5번째 줄과 같이 동등 연산자(==)로 비교할 경우 null과 undefined가 서로 같다고 판단한다.
  2. 7~8번째 줄과 같이 일치 연산자(===)를 사용해야 정확히 판별할 수 있다.

7. 정리

7-1. 데이터 타입

  • 기본형 : 불변 데이터
  • 참조형 : 가변 데이터

7-2. 변수와 식별자

  • 변수 : 변경 가능한 데이터가 담길 수 있는 공간
  • 식별자 : 어떤 변수의 이름

7-3. 변수 선언 및 데이터 할당

  1. 메모리의 빈 공간에 식별자 저장

  2. 그 공간의 값에 undefined를 할당

  3. 이후 변수에 데이터를 할당

    3-1. 기본형 데이터

    • 별도의 공간에 데이터를 저장하고, 그 공간의 주소를 변수의 값 영역에 할당

    3-2. 참조형 데이터 (여러 개의 변수들을 그룹으로 묶어 놓은 집합체)

    • 참조형 데이터 내부 프로퍼티를 위한 변수 영역을 별도로 확보한다.
    • 그 주소를 변수에 연결한 후 앞서 확보한 변수 영역에 각 프로퍼티의 식별자를 저장한다.
    • 그 다음 각 데이터의 별도 공간에 저장해서 그 주소를 식별자들과 매칭시킨다.

7-4. 참조형 데이터의 불변성

  1. 내부 프로퍼티를 일일이 복사를 한다.(깊은 복사)
    • 재귀 or JSON 변환
  2. 라이브러리 사용하기(Lodash, immer.js 등)

7-5. 값이 없는 타입

  • undefined : 어떤 변수에 값이 존재하지 않을 경우를 의미하는 값
  • null : 명시적으로 ‘없음’을 표현하기 위해 사용하는 값
  • 명시적으로 undefined 값을 대입하는 것은 지양할 것

참조

코어 자바스크립트

0개의 댓글