[코어 자바스크립트] 1장 데이터 타입
1장 데이터 타입
JS의 데이터 타입은 크게 두 가지가 있음: 기본형, 참조형
기본형
할당이나 연산 시 값을 복제함, 불변성
- Number
- String
- Boolean
- Symbol
- null
- undefined
참조형
할당이나 연산 시 주솟값을 복제함
- Object
- Array
- Function
- Date
- RegExp
- Map, WeakMap
- Set, WeakSet
변수와 식별자
- 변수: 변할 수 있는 무언가
- 식별자: 변수명
변수 선언과 데이터 할당
var a;
- 위의 코드를 풀어 쓰면, “변할 수 있는 데이터(변수)를 만든다. 이 데이터의 식별자는 a이다”
- 메모리 영역에는 위와 같이 저장된다.
var a = 'abc';
- 직접 변수 공간에 값을 저장하지 않고, 해당 데이터의 주솟값을 저장한다.
- 값은 별도의 메모리 공간에 저장한다.
- 순서는 다음과 같다.
- 변수 영역에서 빈 공간(@1003)을 확보한다.
- 확보한 공간의 식별자를 a로 지정한다.
- 데이터 영역의 빈 공간(@4003)에 값을 저장한다.
- 변수 영역에서 a라는 식별자를 검색한다.
- 저장한 값의 주솟값 (@4003)을 @1003의 공간에 대입한다.
- 왜 이렇게 따로 저장하는 것일까?
- JS는 숫자형 데이터는 64비트 (8바이트)의 공간을 확보한다.
- 하지만 문자열의 경우 한글은 2바이트, 영어는 1바이트로 필요한 용량이 가변적이다.
- 값을 바로 저장하게 되면, 재할당시 데이터 크기에 맞춰 늘려줘야 한다.
- 만약 뒤에 이미 공간이 차있으면? 뒤에 저장된 데이터들을 모두 몇 칸씩 옮겨줘야 한다.
- 컴퓨터의 연산이 많아지게 된다.
var a = 'abc';
a = 'abcdef';
- 값을 재할당 하면, 기존 주솟값에 저장된 게 바뀌는게 아니라 새로 생성 후에 할당하게 된다.
- 만약 100개의 변수에 5라는 값을 할당한다고 하면, 매번 생성하는 것이 아니라, 주솟값만 동일하게 할당한다.
- 이와 같은 장점 때문에 변수와 값을 따로 저장하는 것이다.
기본형 데이터와 참조형 데이터
불변값
- 변수와 상수를 구분 짓는 것은 변수 영역 메모리의 변경 가능성이다.
- 한 번 데이터 할당이 이루어진 변수 공간에 다른 데이터를 재할당 할 수 있는지의 여부
- 불변성의 구분 여부는 데이터 영역 메모리의 변경 가능성이다.
- 기본형 데이터들은 전부 불변값이다.
var a = 'abc';
a = a + 'def';
var b = 5;
var c = 5;
b = 7;
- a의 값은 아예 새로운 데이터가 할당된다.
- ‘abc’, ‘abcdef’는 완전히 별개의 데이터이다.
- b, c의 값은 동일하다.
- b를 7로 재할당 했을 때, 5가 7로 바뀌지 않고 새로운 데이터가 할당된다.
- 따라서 5, 7 모두 다른 값으로 변경할 수 없다.
- 한 번 만든 값을 바꿀 수 없고, 변경은 새로 만드는 동작을 통해서만 이루어진다. → 불변성
가변값
var obj1 = {
a: 1,
b: 'bbb'
}
- 변수 영역의 빈 공간(@1002) 확보 후 이름을 obj1로 지정한다.
- 임의의 데이터 저장 공간(@5001)에 데이터를 저장하려고 보니, 여러 개의 프로퍼티로 이뤄진 데이터 그룹이다. 내부 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103~)를 저장한다.
- @7103, @7104에 각각 a, b 프로퍼티 이름을 지정한다.
- 데이터 영역에서 1을 검색한다. 없으므로 생성 후 주솟값을 저장한다. ‘bbb’도 마찬가지.
var obj1 = {
a: 1,
b: 'bbb'
}
obj1.a = 2;
obj1
이 바라보고 있는 주소는 변하지 않았다. 즉 ‘새로운 객체’가 만들어진 것이 아니라, 기존 객체의 내부 값만 바뀐 것이다.
// 중첩 객체
var obj1 = {
x: 1,
arr: [1, 2, 3]
}
obj.arr = 'str';
- 이 상태에서 재할당 한다면, 참조 카운트가 0인 메모리들이 GC의 대상이 된다.
변수 복사 비교
var a = 10;
var b = a;
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;
- 위의 코드에서 값들을 재할당 한다면,
b = 15;
obj2.c = 20;
- 객체 내부 프로퍼티 값이 변경 되지만, 객체의 주솟값은 그대로이기 때문에
obj1
,obj2
모두 값이 바뀌게 된다.
a !== b
ob1 === obj2
- 인 상태가 된다. 이게 기본형과 참조형의 제일 큰 차이점이다.
- 위의 예제는 객체 자체가 아닌, 객체 내부의 프로퍼티 값 변경에 대한 예제이다.
- 만약
obj2
에 새로운 객체를 할당하게 되면, 메모리 주소가 변경이 되기 때문에obj1 !== obj2
가 된다.- 따라서 참조형 데이터가 가변값이라고 말할 수 있는 경우는, 내부 프로퍼티의 값이 변경되는 경우이다.
얕은 복사와 깊은 복사
- 얕은 복사는 바로 아래 단계의 값만 복사하는 방법이고,
- 깊은 복사는 내부의 모든 값들을 복사하는 방법이다.
얕은 복사
var copyObject = function (target) {
var result = {};
for(var prop in target) {
result[prop] = target[prop];
}
return result;
}
- 만약 객체가 nested 된 참조형이라면, 내부 프로퍼티는 원본이 그대로 저장된다.
깊은 복사
var copyDeepObject = function (target) {
var result = {};
if (typeof target === 'object' && target !== null) {
for(var prop in target) {
result[prop] = copyDeepObject(target[prop]);
}
} else {
result = target;
}
return result;
}
- 만약 참조형 데이터를 만나면, 재귀 함수로 복사하게 된다.
hasOwnProperty
를 활용해 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게끔 할 수도 있다.- ES5의
getter/setter
를 복사하는 방법은 ES6Object.getOwnPropertyDescriptor
또는 ES2017의Object.getOwnPropertyDescriptors
` 밖에 없다.
var copyObjectByJSON = function (target) {
return JSON.parse(JSON.stringify(target));
}
JSON
을 활용하여 문자열 → JSON 객체로 바꾸는 방법으로도 깊은 복사를 수행할 수 있다.- 하지만 함수,
__proto__
,getter/setter
와 같은 값들은 복사할 수 없다.
undefined와 null
- JS에는 ‘없음’을 나타내는 값이 두가지가 있다.
undefined
// 1번
var a;
console.log(a); // undefined
// 2번
var obj = {
a: 1
};
console.log(obj.b); // undefined
// 3번
var func = function () {}
var c = func();
console.log(c); // undefined
- JS 엔진은 다음 세 가지 경우에
undefined
를 반환한다.- 값을 대입하지 않은 변수에 접근할 때
- 객체 내부의 존재하지 않는 프로퍼티에 접근할 때
- return 문이 없거나 호출되지 않는 함수의 실행 결과
var arr = [];
arr.length = 3;
console.log(arr); // [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]
- 1번의 경우, 배열이라면 조금 다르게 동작한다.
- 배열에
length
값을 주면 그만큼 값을 할당할 것 같지만, 객체와 동일하게 값을 지정할 때에 공간 확보 후 값을 저장한다. 배열도 객체이기 때문이다.
var arr1 = [undefined, 1];
var arr2 = [];
arr2[1] = 1;
arr1.forEach((v, i) => console.log(v, i)); // undefined 0, 1 1
arr2.forEach((v, i) => console.log(v, i)); // 1 1
- 비어있는 요소는 순회하는 메서드들의 순회 대상에서 제외된다.
- 사용자가 지정한
undefined
는 그 자체로 값이 되기 때문에 순회 대상이 되어버린다. - JS가 지정한
undefined
는 값이 없음을 나타낸다.
var n = null;
console.log(typeof n); // object
console.log(n == undefined); // true
undefined
는 여러모로 헷갈리기 때문에,- 비어있음을 명시적으로 나타내고 싶은 경우에는
null
을 사용하자. - 주의해야 할 점은, JS 자체 버그로
typeof null
이object
라는 것이다.
console.log(n == null); //true
console.log(n === undefined); // false
console.log(n === null); // true
- 따라서
null
값을 체크할 경우에는==
연산자가 아닌===
연산자를 사용해서 비교해야 한다.