4 분 소요

1장 데이터 타입

JS의 데이터 타입은 크게 두 가지가 있음: 기본형, 참조형

기본형

할당이나 연산 시 을 복제함, 불변성

  • Number
  • String
  • Boolean
  • Symbol
  • null
  • undefined

참조형

할당이나 연산 시 주솟값을 복제함

  • Object
    • Array
    • Function
    • Date
    • RegExp
    • Map, WeakMap
    • Set, WeakSet

변수와 식별자

  • 변수: 변할 수 있는 무언가
  • 식별자: 변수명

변수 선언과 데이터 할당

var a;
  • 위의 코드를 풀어 쓰면, “변할 수 있는 데이터(변수)를 만든다. 이 데이터의 식별자는 a이다”

Untitled

  • 메모리 영역에는 위와 같이 저장된다.
var a = 'abc';

Untitled 1

  • 직접 변수 공간에 값을 저장하지 않고, 해당 데이터의 주솟값을 저장한다.
  • 값은 별도의 메모리 공간에 저장한다.
  • 순서는 다음과 같다.
    1. 변수 영역에서 빈 공간(@1003)을 확보한다.
    2. 확보한 공간의 식별자를 a로 지정한다.
    3. 데이터 영역의 빈 공간(@4003)에 값을 저장한다.
    4. 변수 영역에서 a라는 식별자를 검색한다.
    5. 저장한 값의 주솟값 (@4003)을 @1003의 공간에 대입한다.
  • 왜 이렇게 따로 저장하는 것일까?
    • JS는 숫자형 데이터는 64비트 (8바이트)의 공간을 확보한다.
    • 하지만 문자열의 경우 한글은 2바이트, 영어는 1바이트로 필요한 용량이 가변적이다.
    • 값을 바로 저장하게 되면, 재할당시 데이터 크기에 맞춰 늘려줘야 한다.
      • 만약 뒤에 이미 공간이 차있으면? 뒤에 저장된 데이터들을 모두 몇 칸씩 옮겨줘야 한다.
    • 컴퓨터의 연산이 많아지게 된다.
var a = 'abc';
a = 'abcdef';

Untitled 2

  • 값을 재할당 하면, 기존 주솟값에 저장된 게 바뀌는게 아니라 새로 생성 후에 할당하게 된다.
  • 만약 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'
}

Untitled 3

  1. 변수 영역의 빈 공간(@1002) 확보 후 이름을 obj1로 지정한다.
  2. 임의의 데이터 저장 공간(@5001)에 데이터를 저장하려고 보니, 여러 개의 프로퍼티로 이뤄진 데이터 그룹이다. 내부 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103~)를 저장한다.
  3. @7103, @7104에 각각 a, b 프로퍼티 이름을 지정한다.
  4. 데이터 영역에서 1을 검색한다. 없으므로 생성 후 주솟값을 저장한다. ‘bbb’도 마찬가지.
var obj1 = {
	a: 1,
	b: 'bbb'
}

obj1.a = 2;

Untitled 4

  • obj1 이 바라보고 있는 주소는 변하지 않았다. 즉 ‘새로운 객체’가 만들어진 것이 아니라, 기존 객체의 내부 값만 바뀐 것이다.
// 중첩 객체
var obj1 = {
	x: 1,
	arr: [1, 2, 3]
}

Untitled 5

obj.arr = 'str';

Untitled 6

  • 이 상태에서 재할당 한다면, 참조 카운트가 0인 메모리들이 GC의 대상이 된다.

변수 복사 비교

var a = 10;
var b = a;

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

Untitled 7

  • 위의 코드에서 값들을 재할당 한다면,
b = 15;
obj2.c = 20;

Untitled 8

  • 객체 내부 프로퍼티 값이 변경 되지만, 객체의 주솟값은 그대로이기 때문에 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 를 복사하는 방법은 ES6 Object.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를 반환한다.
    1. 값을 대입하지 않은 변수에 접근할 때
    2. 객체 내부의 존재하지 않는 프로퍼티에 접근할 때
    3. 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 nullobject라는 것이다.
console.log(n == null); //true

console.log(n === undefined); // false
console.log(n === null); // true
  • 따라서 null 값을 체크할 경우에는 == 연산자가 아닌 === 연산자를 사용해서 비교해야 한다.

카테고리:

업데이트: