코어 자바스크립트
정재남 지음
위키북스
목차
데이터 타입의 종류
- 데이터 타입은 크게 기본형과 참조형 2가지로 나뉜다.
- 기본형은 숫자(number), 문자열(string), 불리언(boolean), null, undefined, symbol이 있다.
-
참조형은 객체(object), 배열(Array), 함수(Function), 날짜(Date), 정규표현식(regExp), 맵(Map), 셋(Set)이 있다.
Data Type ---- Primitive Type ---- Number | |--- String | |--- Boolean | |--- null | |--- undefined | |--- Symbol | |--- Reference Type ---- Array |--- Function |--- Date |--- RegExp |--- Map, WeakMap |--- Set, WeakSet
- 기본형과 참조형의 구분은 값을 어떻게 복제 하느냐에 따라 달라진다. 기본형은 값이 담긴 주솟값을 복제하지만, 참조형은 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제 한다는 것이다.
데이터 타입에 관한 배경지식
- 컴퓨터는 모든 데이터를 0과 1로 표현한다. 이런 하나의 메모리 조각을 비트(bit)라 한다. 1개의 비트로만 표현할 경우 나타낼수 있는 값은 2가지 밖에 없고 비트 단위로 표현할 경우 이는 매우 비효율적이다. 이를 하나로 묶은 것이 바이트(byte)이다.
- C/C++, Java 등의 정적 타입 언어는 메모리의 낭비를 최소화 하기 위해 데이터 타입별로 할당할 메모리 영역을 2바이트, 4이트로 나누었다. 이는 상대적으로 메모리 용량이 불가피한 시절에는 불가피한 선택이였다. 하지만 현재는 메모리 용량이 과거보다 월등히 커진 상태여서 자바스크립트 메모리 공간을 상대적으로 넉넉히 할당하였다.
- 모든 데이터는 메모리 주솟값(memory address)을 통해 서로를 구분하고 연결 할 수 있다.
- 식별자와 변수에 대해서 고민해보자. 식별자는 어떤 데이터를 식별하는데 사용하는 이름, 즉 변수명이 되는 것이고, 변수는 변할 수 있는 데이터를 뜻한다. 이 개념에 대해서 자세히 알고 있어야 한다. 문맥에 따라서 혼용하기도 하지만, 이 차이를 구분 할 수 있어야 한다.
변수 선언과 데이터 할당
- 변수란 결국 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇이라 생각하자.
- 간단히 변수를 설정하면 메모리에 어떻게 담기는지 확인해보자
# Code
var a = 'abc';
# 변수 영역
| address |...| 1002 | 1003 | 1004 | 1005 |
| | | | name: a | | |
| | | | value: @5003 | | |
# 데이터 영역
| address |...| 5002 | 5003 | 5004 | 5005 |
| | | | 'abc' | | |
- 메모리에 비어 있는 공간을 하나 확보한뒤 해당 공간의 이름(식별자)를 a라고 지정한다.
- 그다음 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보한뒤 문자열 'abc'를 저장하고, 이 주소를 변수영역에 저장한다.
- 그러면 위 예제에서 뒤에 'defg'를 붙인 경우에는 메로리가 어떻게 변할까?
# 변수 영역
| address |...| 1002 | 1003 | 1004 | 1005 |
| | | | name: a | | |
| | | | value: @5004 | | |
# 데이터 영역
| address |...| 5002 | 5003 | 5004 | 5005 |
| | | | 'abc' | 'abcdef' | |
- 데이터 영역에 `defg`를 합친 `abcdefg` 문자열을 새로 만든뒤 별도의 공간에 저장한다.
- 그다음 해당 데이터 주소를 변수영역의 주소값을 변경한다.
- 그러면 왜 변수 영역에 값을 직접 대입하지 않을까? 이는 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리 하기 위해서이다.
기본형 데이터와 참조형 데이터
-
불변값
- 변수와 상수를 구분하는 성질은 변경 가능성이다. 바꿀 수 있으면 변수, 바꿀 수 없으면 상수이다.
- 불변값과 상수는 같은 개념이 아니다. 주의하자. 변수와 상수를 구분 짓는 변경 가능성의 대상은 변수 영역의 메모리이다. 한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지 여부가 관건이다. 반면에 불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역의 메모리이다.
- 기본형 데이터인 숫자, 문자열, boolean, null, undfined, Symbol은 모두 불변값이다.
var a = 'abc'; a = a + 'def'; // 기존 데이터 영역의 값 'abc'가 'abcdef'로 변경 되는 것이 아니다. // 새로운 문자열 'abcdef'를 만든 후 이를 저장한다. // 결국 'abc'와 'abcdef'는 완전히 별개의 데이터이다. var b = 5; var c = 5; b = 7; // b가 할당되면 컴퓨터는 데이터 영역에 5를 저장한다. // c가 할당되면 컴퓨터는 데이터 영역에 5를 저장하려 하는데 // 해당 값은 이미 b가 할당될 때 저장되어 있다. 컴퓨터는 // 이 값을 다시 할당하지 않고 이미 할당된 5를 사용한다. // b에 7이 할당되면 컴퓨터는 데이터 영역에 7을 할당하는데 // 해당 값을 할당된 적이 없는 새로운 값이니 7을 새로운 영역에 // 할당하고 변수 영역의 메모리 주소를 변경한다.
- 기본형 데이터는 값을 변경 할 수 없다. 이는 가바지 컬렉팅을 당하지 않는 한 영원히 변하지 않는다.
-
가변값
- 기본적인 성질은 가변값인 경우가 많지만 설정에 따라 변경 불가능한 경우도 있다. ( Object.deifineProperty, Object.freeze 등)
- 기본형 데이터와의 차이는 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 것이다.
- 데이터 영역에 저장된 값은 모두 불변값이다. 바뀌는 것은 객체의 변수영역의 값이 변경되는 것이다.
- 어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수를 참조 카운트라고 한다. 이 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다.
-
변수 복사 비교
var a = 10; var b = a; var obj1 = { c: 10, d: 'ddd' }; var obj2 = obj1;
# 변수 영역 | address | 1001 | 1002 | 1003 | 1004 |...| | | name: a | name: b | name: ob1 | name: ob2 | | | | value: @5001 | value: @5001 | value: @5002 | value: @5002 | | # 데이터 영역 | address | 5001 | 5002 | 5003 | 5004 | ... | | | 10 | @7103 ~ ? | 'ddd' | | | #객체 @5002의 변수 영역 | address | 7103 | 7104 | | | name: c | name: d | | | value: @5001 | value: @5003 | # 코드 실행 순서 1. 변수 a를 위해 변수 영역의 빈 공간 @1001을 확보하고 실별자 a를 지정한다. 숫자 10을 데이터 영역에서 검색하고 값이 없으므로 빈 공가 @5001에 저장한 뒤, 이 주소를 @1001에 넣는다. 2. 변수 b를 위해 변수 영역의 빈 공간 @1002를 확보하고 식별자 b를 지정한다. 숫자 10을 데이터 영역에서 검색하는데 값이 있으므로 @5001을 @1002 주소 영역에 넣는다. 3. 변수 obj1을 위해 변수 영역의 빈 공간 @1003을 확보하고 식별자 obj1을 지정한다. 데이터 그룹을 저장하기 위해서 @5002에 빈 공간을 확보하고 데이터 그룹이 담겨야 하기 때문에 별도의 변수 영역 @7103~을 확보해 이 주소를 저장한다. 그 다음 property c, d를 변수영역에 각각 저장한다. 4. 변수 obj2를 위해 변숫 역역의 빈 공간 @1004를 확보하고 식별자 obj2를 지정한다. 이제 식별자 obj1을 검색해(@1003) 그 값인 @5002를 들고, @1004에 값으로 대입한다.
- 변수를 복사하는 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보게 되는 점에서 동일하다.
- 복사 과정은 동일하지만 데이터 할당 과정에서 이미 차이가 있기 때문에 변수 복사 이후의 동작에도 큰 차이가 발생한다.
var a = 10; var b = a; var obj1 = { c: 10, d: 'ddd' }; var obj2 = obj1; b = 15; obj2.c = 20;
# 변수 영역 | address | 1001 | 1002 (값이 변경됨) | 1003 | 1004 |...| | | name: a | name: b | name: ob1 | name: ob2 | | | | value: @5001 | value: @5004 | value: @5002 | value: @5002 | | # 데이터 영역 | address | 5001 | 5002 | 5003 | 5004 | 5005 |... | | | 10 | @7103 ~ ? | 'ddd' | 15 | 20 | | #객체 @5002의 변수 영역 | address | 7103 | 7104 | | | name: c | name: d | | | value: @5005 | value: @5003 | # 코드 실행 순서 1 ~ 4번까지는 위 코드와 동일 5. 데이터 영역에 15가 없으므로 새로운 공간 @5004에 저장하고, 그 주소를 든 채로 변수 영역에서 식별자가 b인 주소를 찾은뒤 value를 변경해준다. 6. 데이터 영역에 20이 없으므로 새로운 공간 @5005에 저장하고, 그 주소를 든 채로 변수 영역에서 obj2를 찾고(@1004), obj2의 값인 @5002가 가리키는 변수 영역에서 다시 c를 찾아(@7103) 그곳에 @5005를 대입한다.
- 어떤 데이터 타입이든 변수에 할당하기 위해서는 주소값을 복사해야 하기 때문에, 엄밀히 따지면 자바스크립트의 모든 데이터 타입은 참조형 데이터일 수밖에 없다.
- 다만 기본형은 주솟값을 복사하는 과정이 한번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다.
- 참조형 데이터가 가변값이라고 설명할 때의 가변은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립한다.
불변 객체
- 참조형 데이터의 가변은 데이터 자체가 아닌 내부 프로퍼티를 변경할 때만 성립한다. 데이터 자체를 변경하고자 하면(새로운 데이터를 할당하고자 하면) 기본형 데이터와 마찬가지로 기존 데이터는 변하지 않습니다.
- 불변 객체는 어떨 떄 필요 할까? 값으로 전달받은 객체에 변경을 가하더라도 객체는 변하지 않아야 하는 경우에 필요하다.
-
얕은 복사와 깊은 복사
- 얕은 복사는 바로 아래 단계의 값만 복사하는 방법이고, 깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법이다.
- 얕은 복사일 경우에 프로퍼티에 대한 원본과 사본은 모두 동일한 참조형 데이터의 주소를 가리키게 된다. 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 변경 된다.
- 어떤 객체를 복사 할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 프로퍼티 중에서 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다.
- 깊은 복사를 간단하게 처리할 때 객체를 JSON 문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸면 깊은복사가 된다. 단, 메서드나 숨겨진 프로퍼티인 proto나 getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티들은 모두 무시된다.
undefined와 null
- undefined와 null 모두 없음을 나타낸다. 의미는 같지만 미세하게 다르고, 사용하는 목적 또한 다르다. 이 2가지를 잘 구분해야 한다.
-
undefined의 경우 사용자가 명시적으로 지정할 수 있지만, 자바스크립트 엔진이 자동으로 부여 하는 경우가 있다. 자바스크립트 엔진이 부여하는 경우는 사용자가 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로는 그렇게 하지 않았을때 undefined를 반환한다.
- 값을 대입하지 않은 변수
- 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
- return 문이 없거나 호출되지 않는 함수의 실행 결과
-
null은 비어있음을 명시적으로 나타내고 싶을 떄 사용한다. 즉, undefined는 사용자가 실수로 나타낼수 있지만 null의 경우에는 사용자가 직접 할당 하지 않는 이상 나올 수 없는 값이다.
- null을 사용할때 주의할 점이 있는데, typeof null은 object라는 것이다. 따라서 null값을 판별하기 위해서는 typeof 대신 다른 방법을 사용해야 한다.
위 정리글은 코어 자바스크립트(Core JavaScript)에서 인용한 것입니다.
[ISBN 979-11-5839-172-0 (93000)] Copyright 2019 by 정재남