
오늘은 자바스크립트에서의 데이터 타입과 데이터를 처리하는 과정을 알아보겠습니당
#변수 #데이터 #불변성
01. 데이터 타입의 종류
- 데이터타입 (data type)
- 기본형 (Primitive type) ➡️ 불변성을 띈다
- 숫자 (Number)
- 문자열 (String)
- 불리언 (Boolean)
- null
- undefined
- 심볼 (Symbol)
- 참조형 (Reference type)
- 객체 (Object)
- 배열 (Array)
- 함수 (Function)
- 날짜 (Date)
- 정규표현식 (RegExp)
- Map, WeakMap
- Set, WeakSet
- 객체 (Object)
- 기본형 (Primitive type) ➡️ 불변성을 띈다
+) 파란색으로 표시된 부분은 ES6에 추가된 내용입니다.
그렇다면 기본형과 참조형의 차이점은 무엇일까요 ⁉️
-> 기본형은 값이 담긴 주솟값을 바로 복제하지만, 참조형은 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제한다는 차이점이 있습니다.
02. 데이터 타입에 관한 배경지식
- 메모리와 데이터
컴퓨터는 모든 데이터를 0 또는 1로 표현합니다. 그리고 0또는 1만 표현할 수 있는 하나의 메모리 조각을 비트라고 하는데용.
메모리는 이러한 많은 비트들로 구성되어 있습니다. 그래서 각각의 비트의 위치는 고유한 식별자를 통해 확인할 수 있습니다.
그러나 비트 단위로 위치를 확인하는 것은 비효율적이기 때문에 여러 개의 비트를 묶어 바이트(1바이트 = 8비트)라는 단위를 사용하여 값을 표현할 수 있습니다.
C/C++, 자바 등의 정적 타입에서는 메모리의 낭비를 최소화하기 위해 데이터 타입별로 할당할 메모리의 영역을 2바이트(short), 4바이트(int) 등으로 나누어 정하였습니다.
하지만 자바스크립트에서는 숫자의 경우, 정수형인지 부동소수형인지를 구분하지 않고 8바이트를 할당받습니다.
- 식별자와 변수의 차이점
- 식별자: 데이터를 식별하는 데 사용하는 이름 = 변수명
- 변수: 변경 가능한 데이터가 담길 수 있는 공간
03. 변수 선언과 데이터 할당
- 변수 선언
var a; // 변수 a 선언 (현재 a는 undefined이다)
a = 'abc'; // 변수 a에 데이터 할당
var a = 'abc'; // 변수 선언과 할당을 한 문장으로
| 변수 영역 | 주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
| 데이터 | 이름(식별자): a 값: @5004 -> 주소를 저장한다는 것이 포인트! |
||||||
| 데이터 영역 | 주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
| 데이터 | 'abc' |
- 변수 영역에서 빈 공간(@1003)을 확보한다.
- 확보한 공간의 식별자를 a로 지정한다.
- 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장한다.
- 변수 영역에서 a라는 식별자를 검색한다.(@1003)
- 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.
위와 같이 변수 영역에 값을 직접 대입하지 않고 번거롭게 한 단계 더 거치는 이유는 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위함입니다.
만약 변수 영역에 값을 직접 대입하게 된다면 변환한 데이터를 다시 저장하기 위해서는 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 필요하기 때문에 컴퓨터가 처리해야 할 연산이 많아집니당. 그래서 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장하는 것이 좋습니당
+) 변수 값 수정하기!
'abc'의 마지막에 'def'를 추가하려면 앞서 'abc'가 저장된 공간에 'abcdef'를 할당하는 대신 'abcdef'라는 문자열을 새로 만들어 별도의 공간에 저장하고, 그 주소를 변수 공간에 연결합니당
그리고 기존에 있던 'abc' 데이터는 자신의 주소를 저장하는 변수가 하나도 없게 되므로 가비지 컬렉터의 수거 대상이 됩니다.
04. 기본형 데이터와 참조형 데이터
- 불변값
- 변수와 상수를 구분 짓는 변경 가능성의 대상 → 변수 영역 메모리(한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당 할 수 있는지 여부)
- 불변성의 여부를 구분할 때의 변경 가능성의 대상 → 데이터 영역 메모리
var a = 'abc';
a = a + 'def';
var b =5;
var c = 5;
b = 7;
1~2번째 줄은 기존의 'abc'가 'abcdef'로 바뀌는 것이 아니라 새로운 문자열 'abcdef'를 만들어 그 주소를 변수 a에 저장하는 것이므로 'abc'와 'abcdef'는 완전히 별개의 데이터입니당
4~5번째 줄은 변수b와 c에 숫자 5를 할당하기 위해서 데이터 영역에서 5를 찾고 없으면 그제서야 데이터 공간을 하나 만들어 저장합니다. 즉, 이미 만들어진 값이 있으면 그 주소를 재활용한다는 것입니다.
6번째 줄은 변수 b의 값을 7로 바꾸기 위해서 기존에 저장된 5 자체를 7로 바꾸는 것이 아니라 기존에 저장된 7이 있다면 재활용하고 없다면 새로 만들어 b에 저장합니다. 결국 5와 7 모두 다른 값으로 변경할 수 없습니다.
- 가변값
아래는 참조형 데이터를 할당하는 방법입니다. 기본형 데이터와의 차이점은 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 점입니다.
여기서 데이터 영역에 저장된 값은 모두 불변값입니다. 그러나 변수에는 다른 값을 얼마든지 대입할 수 있으므로 흔히 참조형 데이터는 가변값이라고 하는 것입니다.
| 변수 영역 |
주소 | 1001 | 1002 | 1003 | 1004 | ... |
| 데이터 | 이름(식별자): obj1 값: @5001 |
|||||
| 데이터 영역 |
주소 | 5001 | 5002 | 5003 | 5004 | ... |
| 데이터 | @7103 ~ ? | 1 | 'bbb' | |||
| 객체 @5001의 변수 영역 |
주소 | 7103 | 7104 | 7105 | 7106 | ... |
| 데이터 | 이름(식별자): a 값: @5003 |
이름(식별자): b 값: @5004 |
- 컴퓨터는 우선 변수 영역의 빈공간(@1002)를 확보하고 그 주소의 이름을 obj1으로 지정합니다.
- 임의의 데이터 저장 공간(@5001)에 여러개의 프로퍼티를 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~ ?)를 @5001에 저장합니다.
- @7103와 @7104에 각각 a, b라는 프로퍼티 이름을 지정합니다.
- 데이터 영역에서 숫자 1을 검색하고 검색 결과가 없으므로 임의로 @5003에 저장하고, 이 주소를 @7103에 저장합니다. 문자열 'bbb'로 똑같은 방식으로 주솟값을 @7104에 저장합니다.
만약 obj1의 프로퍼티에 다른 값을 할당한다면, 데이터 영역에 새로운 값이 추가되고 객체 @5001의 변수 영역이 저장하는 값이 바뀌는 것이지 obj1이 바라보고 있는 주소는 @5001로 변하지 않습니다. 즉, '새로운 객체'가 만들어진 것이 아니라 기존의 객체 내부의 값이 바뀐 것을 알 수 있습니다.
- 변수 복사 비교
위에서 동작 방식을 배웠으니, 변수를 복사할 때의 변화를 통해 기본형 데이터와 참조형 데이터의 차이를 확인해보겠습니당
// a와 b 모두 같은 주소값을 바라봄
var a = 10;
var b = a; // 기본형 데이터 복사
// obj1과 obj2 모두 같은 주소값을 바라봄
var obj1 = {c: 10, d: 'ddd'};
var obj2 = obj1; // 참조형 데이터 복사
// 변수 복사 이후 값 변경
b = 15;
obj2.c = 20; // 객체의 프로퍼티 변경 - (1)
obj2 = {c: 20, d='ddd'}; // 새로운 객체를 할당 - (2)
위와 같이 변수 복사를 하게 되면 기본형과 참조형 데이터 모두 각각 같은 주소값을 바라보게 됩니다. 하지만 이후 값을 변경하면 기본형 데이터의 경우 변수 a와 b는 서로 다른 주솟값을 바라보게 됩니다. 그러나 (1)의 경우, 변수 obj1과 obj2는 내부 프로퍼티의 값이 변경된 것뿐이지 여전히 같은 객체를 바라보고 있으므로 변함없이 서로 같은 주솟값을 바라보게 됩니다.
하지만 여기서 (2)의 경우처럼, obj2의 내부 프로퍼티 값이 아닌 객체 자체의 값을 변경하게 된다면 a와 b와 마찬가지로 obj1과 obj2는 서로 다른 주소값을 가리키게 됩니다.
즉, 참조형 데이터가 '가변값'이라고 설명할 때의 '가변'은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립합니다! (서로 같은 주소값을 바라보고 있지만 값이 다르기 때문에 '가변'이라고 할 수 있는 것임!)
05. 불변 객체
- 불변 객체를 만드는 간단한 방법
불변 객체는 최근의 React, Vue.js, Anglular 등의 라이브러리나 프레임워크에서 뿐만 아니라 함수형 프로그래밍, 디자인 패턴 등에서도 매우 중요한 기초가 되는 개념이라고 합니당
참조형 데이터의 '가변'은 데이터 자체가 아닌 내부 프로퍼티를 변경할 때만 성립합니다. 데이터 자체를 변경하고자 하면(새로운 데이터를 할당하고자 하면) 기본형 데이터와 마찬가지로 기존 데이터는 변하지 않습니다.(기존의 데이터를 바꾼 것이 아니라 새로운 데이터를 할당 받은 것이기 때문에!)
그렇다면 참조형 데이터를 가변값으로 여겨야 하는 상황임에도 이를 불변값으로 사용하는 방법이 있습니다. 이 경우 내부 프로퍼티들을 일일이 복사하면 됩니다.(깊은 복사) 혹은 라이브러리를 사용하는 방법을 사용하면 됩니당
- 얕은 복사와 깊은 복사
- 앝은 복사: 바로 아래 단계의 값만 복사하는 방법
- 깊은 복사: 내부의 모든 값을 하나하나 찾아서 전부 복사하는 방법
얕은 복사의 경우, 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사하기 때문에 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게되어 원본이 바뀌면 사본도 바뀌고, 사본이 바뀌면 원본도 바뀌게 됩니다. 그래서 이러한 현상이 발생하지 않기 위해서는 깊은 복사를 해야합니다.
깊은복사의 경우에는 원본과 사본이 서로 완전히 다른 객체를 참조하게 되어 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않게 됩니다.
+) JSON을 활용하여 깊은 복사하기
: 객체를 JSON 문법으로 표현된 문자열로 전화했다가 다시 JSON 객체로 바꾸는 방법입니다. 하지만 메서드(함수)나 숨겨진 프로퍼티인 __proto__나 getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티들은 모두 무시합니다. httprequest로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 활용하기 좋은 방법입니당
06. undefined와 null
자바스크립트에는 '없음'을 나타내는 값이 두 가지가 있습니다.
- undefined: 어떤 변수에 값이 존재하지 않을 경우를 의미
- null: 사용자가 명시적으로 '없음'을 표현하기 위해 대입한 값
- undefined
undefined의 경우 사용자가 명시적으로 지정할 수도 있지만, 아래의 3가지 경우처럼 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여하는 경우도 있습니다.
- 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
- 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
- return문이 없거나 호출되지 않는 함수의 실행 결과
// 1번 경우
var a;
console.log(a); // undefined
// 2번 경우
var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // undefined
console.log(b); // ReferenceError: b is not defined
// 3번 경우
var func = function() { };
var c = func();
console.log(c); // undefined
그런데 위의 1번 경우(값을 대입하지 않은 경우가)에 대해 배열은 조금 특이한 동작을 함을 확인할 수 있습니다.
var arr1 = [];
arr1.length = 3;
concole.log(arr1); // [empty x 3]
var arr2 = new Array(3);
concole.log(arr2); // [empty x 3]
var arr3 = [undefined, undefined, undefined];
console.log(arr3); // [undefined, undefined, undefined]
빈배열을 만들고 배열의 크기를 3으로 하여 만들어진 arr1과 new연산자와 함께 Array 생성자 함수를 호출함으로써 만들어진 arr2의 값을 확인해보면 [empty x 3]이 출력되는데, 이는 배열에 3개의 빈 요소를 확보했지만 확보된 각 요소에는 문자 그대로 어떤 값도 할당되어있지 않으며, 심지어 undefined조차도 할당되어있지 않음을 의미합니다.
하지만 리터럴 방식으로 배열을 생성하면서 각 요소에 undefined를 부여한 arr3은 undefined의 값을 가지는 것을 확인해 볼 수 있습니당
이렇게 '비어있는 요소'와 'undefined를 할당한 요소'는 출력 결과부터 다르며 '비어있는 요소'는 순회와 관련된 많은 배열 메서드들의 순회 대상에서 제외됩니다!
ex) 배열의 각 요소를 순회하는 것을 기본으로 추가적인 기능을 수행하는 메서드: forEach, map, filter, reduce ...
- null
null을 사용할 때 한 가지 주의할 점이 있습니당 바로바로 typeof null이 object라는 것인데용 이건 자바스크립트 자체 버그라서 어떤 변수의 값이 null인지 여부를 판별하기 위해서는 typeof 대신 다른 방식으로 접근해야합니다.
var n = null;
console.log(typeof n); // object
// 동등 연산자(==)로 비교
console.log(n == undefined); // true
console.log(n == null); // true
// 일치 연산자(===)로 비교
console.log(n === undefined); // false
concole.log(n === null); // true
// 즉, 일치 연산자를 사용해야지 정확히 판별이 가능합니다.
📚 < 코어 자바스크립트 >
코어 자바스크립트: 핵심 개념과 동작 원리로 이해하는 자바스크립트 프로그래밍
자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다! 최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고 해도 결코 과언이 아
wikibook.co.kr
'Frontend > JavaScript' 카테고리의 다른 글
| [JavaScript] npm과 yarn의 혼용과 충돌(Conflict) (1) | 2024.01.31 |
|---|