기본형: 문자, 숫자, 블리언, null, undefined, 심볼(+ES6) 등등
참조형: 객체( 배열, 함수, 날짜, 정규 표현식 등등)
할당, 연산 시 차이
bit: 0또는 1만 표현할 수 있는 메모리 조각/ 측정 단위
byte: 8bit로 구성된 데이터 단위
byte가 생겨난 이유
변수: “변할 수 있는 수”, 컴퓨터 용어에선 “변할 수 있는 무언가(데이터)”
식별자: 데이터를 식별하는 데 사용하는 이름, 변수명
변수는 변할 수 있는 데이터가 담길 수 있는 공간
let a; // 변할 수 있는 데이터를 만든다. 식별자는 a이다.
변수 선언 시 메모리 영역
데이터 할당 시 특정 식별자를 가진 주소에 데이터를 할당하는 것이 아니다.
데이터도 메모리 공간을 확보해 저장한 뒤 해당 주소를 특정 식별자를 가진 주소에 할당하는 것이다.
let a = "hello"
데이터 할당 시 메모리 영역
왜 변수와 데이터를 따로 관리할까?
JS기준 숫자형은 8byte의 공간 확보를 하며, 문자열은 메모리 용량과 글자 수가 가변적이라 규격이 없다.
영어:1bit
, 한글:2bit
만약 식별자와 데이터를 하나의 공간에서 관리한다면 가변적인 문자열이 변경 되면 기존 공간에서 새로운 데이터 크기에 맞게 공간을 확보해야 한다. 그럼 뒤에 있는 저장된 데이터들을 뒤로 옮기고, 이동시킨 주소와 식별자를 다시 연결하여 많은 연산이 이루어진다.
+ abc
에서 abcd
로 바꾸면 abc
에 abcd
를 할당하는 것이 아니라 abcd
를 새로운 별도 공간에 저장한다.
100개의 변수에 5
를 할당한다 하면 100 * 8(숫자형:8byte) = 800
즉, 800byte가 사용된다.
하지만 5
를 별도의 공간에 저장하고 주소를 식별자 주소에 할당하면 주소 공간의 크기가 2byte
라면 100 * 2 + 8 = 208
즉, 208byte가 사용된다.
불변값 ≠ 상수
기본형은 모두 불변값이다. 값을 a→ab
로 재할당 시 기존a
가 ab
가 되는게 아니라 a
, ab
는 별개의 데이터이다.
값 자체를 바꿀 수 없다. 오직 새로 만들어야만 변경할 수 있다. 가비지 컬렉팅을 당하지 않는 한 값은 변하지 않는다.
참조형 데이터 변수 할당 과정
let obj = {
a = 1,
b = "text"
}
a
,b
라는 이름의 주소(@7103, @7104) 2개를 만들고, 주소를 @5001에 저장한다.1
, “text”
공간을 만들고 해당 주소를 @7103, @7104에 저장한다.@7103 = 1
, @7104 = “text”
가비지 컬렉터의 수거 대상
참조 카운트: 특정 데이터를 참조하는 변수들의 개수
특정 데이터를 참조하는 변수가 하나라면 참조 카운트는 1
이다. 그런데 해당 변수값을 재할당하여 더이상 특정 데이터를 참조하는 변수가 없다면 해당 데이터는 참조 카운드가 0
이된다.
그럼 해당 데이터는 가비지 컬렉터의 수거 대상이 되어 런타임 환경에 따른 특정 시점, 메모리 사용량 포화 상태 시 수거되어 해당 공간은 빈공간이 된다.
가비지 컬렉터, 중첩된 참조형 데이터 재할당 시
let obj = {
arr:[1,2,3]
}
obj.arr = 'text'
기본형 복사 비교
let num1 = 1
let num2 = num1
console.log(num1 === num2) //true
let num1 = 1
let num2 = num1
num2 = 2
console.log(num1 === num2) //false
참조형 복사 비교
//객체 내부 변경(가변)
let obj1 = {a:1,b:'bbb'}
let obj2 = obj1
obj2.b = 'change'
console.log(obj1 === obj2) //false
//객체 변경(불변)
let obj1 = {a:1,b:'bbb'}
let obj2 = obj1
obj2 = {a:1,b:'bbb'}
console.log(obj1 === obj2) //false
불편 객체가 필요한 상황: 전달받은 객체를 변경해도 원본 객체에 영향을 미치지 않아야 할 때
불변 객체를 만들려면 내부 프로퍼티를 변경하는 것이 아닌, 객체를 새로 만들어 변경해야 한다.
그래야 변경 전과 후를 비교할 수 있다.
//객체 변경(불변)
let obj1 = {a:1,b:'bbb'}
let obj2 = obj1
obj2 = {a:1,b:'bbb'}
console.log(obj1 === obj2) //false
위의 경우 처럼 프로퍼티 하나를 변경하기 위해 변경하지 않을 프로퍼티도 하드코딩으로 입력했다. 만약 1억개의 프로퍼티중 일부만 바꿔야 한다면 나머지를 하드코딩으로 입력하기란 쉽지않다.
깊은복사: 내부 모든 값 복사
얕은복사: 아래 단계 값만 복사 (spread, Object.assign)
중첩 객체 복사 시 참조형 프로퍼티의 주소만 복사, 원본, 복사본 모두 동일한 참조형 데이터의 주소를 참조하고있다.
// 얕은 복사 함수
const copy = (target) => {
let result = {}
for (let prop in target){
result[prop] = target[prop]
}
return result
}
let obj1 = { a:{name:'tom',age:20}, b:1}
let obj2 = copy(obj1)
obj1.b === ob2.b //false
obj1.a === obj2.a //true
깊은 복사 하는법
재귀함수를 만들어 사용하는 방법이 있다.
// 깊은 복사 함수
const deepCopy = (target) => {
let result = {}
if(typeof target === 'object' && target !== null){
for (let prop in target){
result[prop] = deepCopy(target[prop])
}
} else {
result = target
}
return result
};
하지만 위의 함수를 이용하면 배열은 객체가 된다.
let obj = {
a:[1,2,3]
}
let obj2 = deepCopy(obj)
obj //{ a: [ 1, 2, 3 ] }
obj2 //{ a:{ '0': 1, '1': 2, '2': 3 } }
더 좋은 방법은 객체를 JSON 문자열 전환했다 다시 객체로 바꾼다.
JSON이 변경할 수 없는 프로퍼티는 무시된다.(proto, getter/setter 등)
const jsonCopy = (target) => {
return JSON.parse(JSON.stringify(target));
};
let obj = {
a:[1,2,3]
}
let obj2 = jsonCopy(obj)
obj2.a[0] = 999
obj.a //[1,2,3]
obj2.a //[999,2,3]
lodash
라이브러리를 이용하는 것이다.“없음”을 표현하는 두 타입
undefined: JS엔진이 자동으로 부여
null: 사용자가 명시적으로 부여
JS엔진이 undefined 부여하는 경우
null의 타입
null의 데이터 타입은 null이 맞다. 하지만 typeof를 사용하면 object가 나오는데 이는 JS 자체 버그이다.
typeof null // object
값이 null인지 확인하려면 아래의 방법을 이용해야 한다.
let n = null;
n == null // true
n === null // true
n == undefined // true
n === undefined // false