[코어 자바스크립트] 01 데이터 타입

임승민·2022년 12월 8일
0
post-thumbnail

1-1 데이터 타입의 종류

기본형: 문자, 숫자, 블리언, null, undefined, 심볼(+ES6) 등등

참조형: 객체( 배열, 함수, 날짜, 정규 표현식 등등)

할당, 연산 시 차이

  • 기본형, 값이 담긴 주소값을 복제
  • 참조형, 값이 담긴 주소값들의 묶음을 가리키는 주소값 복제

1-2 데이터 타입에 관한 배경지식

1-2-1 메모리와 데이터

bit: 0또는 1만 표현할 수 있는 메모리 조각/ 측정 단위

byte: 8bit로 구성된 데이터 단위

byte가 생겨난 이유

  • 메모리는 bit들로 구성돼있다. 비트는 고유 식별자로 위치를 확인할 수 있다. 하지만 작은 단위인 bit로 위치를 파악하는 것은 매우 비효율적
  • 8개로 묶어 1byte로 사용, 검색시간 단축과 표현가능 값 증가
  • 너무 많은 bit를 한 단위로 묶으면 낭비되는 bit가 발생
  • 자주 사용하지 않는 데이터를 표현 하려고 많은 bit를 묶는 것 보다, 표현 가능한 데이터의 제약이 있더라도 적당한 bit로 묶는 것이 낫다. 이러한 이유로 byte 탄생
  • 정적 타입 언어들은 메모리 낭비를 방지하기 위해 1byte, 2byte 나눴지만 JS는 메모리 용량이 커졌을 때 생겨나 메모리 영역이 8byte이다.
  • 모든 데이터는 byte단위 식별자 즉, 메모리 주소값으로 서로 구분하고 연결한다.

1-2-2 식별자와 변수

변수: “변할 수 있는 수”, 컴퓨터 용어에선 “변할 수 있는 무언가(데이터)”

식별자: 데이터를 식별하는 데 사용하는 이름, 변수명

1-3 변수 선언과 데이터 할당

1-3-1 변수 선언

변수는 변할 수 있는 데이터가 담길 수 있는 공간

let a; // 변할 수 있는 데이터를 만든다. 식별자는 a이다.

변수 선언 시 메모리 영역

  1. 선언 명령을 하면 컴퓨터는 메모리에 비어있는 공간 하나를 확보한다.
  2. 해당 공간의 식별자는 a라고 저장한다.
  3. 변수 a에 접근하면 컴퓨터는 a라는 이름을 가진 주소를 찾아 해당 공간의 데이터를 반환한다.

1-3-2 데이터 할당

데이터 할당 시 특정 식별자를 가진 주소에 데이터를 할당하는 것이 아니다.

데이터도 메모리 공간을 확보해 저장한 뒤 해당 주소를 특정 식별자를 가진 주소에 할당하는 것이다.

let a = "hello"

데이터 할당 시 메모리 영역

  1. 컴퓨터는 메모리에 빈 공간 하나를 확보한다.
  2. 해당 공간의 식별자는 a라고 저장한다.
  3. 데이터도 마찬가지로 메모리 빈 공간에 “hello”를 저장한다.
  4. 데이터 주소를 식별자a를 가진 주소에 대입한다.

왜 변수와 데이터를 따로 관리할까?

JS기준 숫자형은 8byte의 공간 확보를 하며, 문자열은 메모리 용량과 글자 수가 가변적이라 규격이 없다.

  • 한 글자 기준 영어:1bit, 한글:2bit
  1. 불필요한 연산 제거

만약 식별자와 데이터를 하나의 공간에서 관리한다면 가변적인 문자열이 변경 되면 기존 공간에서 새로운 데이터 크기에 맞게 공간을 확보해야 한다. 그럼 뒤에 있는 저장된 데이터들을 뒤로 옮기고, 이동시킨 주소와 식별자를 다시 연결하여 많은 연산이 이루어진다.
+ abc에서 abcd로 바꾸면 abcabcd를 할당하는 것이 아니라 abcd를 새로운 별도 공간에 저장한다.

  1. 중복 데이터 처리

100개의 변수에 5를 할당한다 하면 100 * 8(숫자형:8byte) = 800 즉, 800byte가 사용된다.

하지만 5를 별도의 공간에 저장하고 주소를 식별자 주소에 할당하면 주소 공간의 크기가 2byte라면 100 * 2 + 8 = 208 즉, 208byte가 사용된다.

1-4 기본형 데이터와 참조형 데이터

1-4-1 불변값

불변값 ≠ 상수

기본형은 모두 불변값이다. 값을 a→ab로 재할당 시 기존aab가 되는게 아니라 a, ab는 별개의 데이터이다.

값 자체를 바꿀 수 없다. 오직 새로 만들어야만 변경할 수 있다. 가비지 컬렉팅을 당하지 않는 한 값은 변하지 않는다.

1-4-2 가변값

참조형 데이터 변수 할당 과정

let obj = {
	a = 1,
	b = "text"
}
  1. 메모리 빈공간에 obj라는 이름의 주소(@1002)를 만든다.
  2. 프로퍼티 주소를 담아둘 공간(@5001)을 만들고, 주소를 @1002에 저장한다.
  3. a,b라는 이름의 주소(@7103, @7104) 2개를 만들고, 주소를 @5001에 저장한다.
  4. 데이터 1, “text” 공간을 만들고 해당 주소를 @7103, @7104에 저장한다.
    @7103 = 1, @7104 = “text”

1-4-3 변수 복사 비교

  • 객체 내부 변경 => 가변
    만약, obj.a를 재할당해도 식별자obj 주소의 값인 주소@5001은 변하지 않는다.
    객체 자체를 변경하는 것이 아닌 내부 값을 변경할 때만 가변하다.
  • 객체 변경 => 불변
    객체 자체를 재할당하면 새로운 프로퍼티 모음 공간이 생기고 해당 주소가 변수의 값이 된다.
    따라서 이 상황은 기본형 재할당 상황과 같고 불변하다 할 수 있다.

가비지 컬렉터의 수거 대상

참조 카운트: 특정 데이터를 참조하는 변수들의 개수

특정 데이터를 참조하는 변수가 하나라면 참조 카운트는 1이다. 그런데 해당 변수값을 재할당하여 더이상 특정 데이터를 참조하는 변수가 없다면 해당 데이터는 참조 카운드가 0이된다.
그럼 해당 데이터는 가비지 컬렉터의 수거 대상이 되어 런타임 환경에 따른 특정 시점, 메모리 사용량 포화 상태 시 수거되어 해당 공간은 빈공간이 된다.

가비지 컬렉터, 중첩된 참조형 데이터 재할당 시

  1. 재할당되어 더이상 참조되지 않는 프로퍼티 모음 공간이 수거 대상이 되어 사라진다.
  2. 자연스럽게 프로퍼티 모음 공간 안의 주소들도 사라진다.
  3. 그럼, 해당 주소의 변수들도 참조 카운드 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

05 불변 객체

1-5-1 불변 객체를 만드는 간단한 방법

불편 객체가 필요한 상황: 전달받은 객체를 변경해도 원본 객체에 영향을 미치지 않아야 할 때

불변 객체를 만들려면 내부 프로퍼티를 변경하는 것이 아닌, 객체를 새로 만들어 변경해야 한다.
그래야 변경 전과 후를 비교할 수 있다.

//객체 변경(불변)
let obj1 = {a:1,b:'bbb'}
let obj2 = obj1

obj2 = {a:1,b:'bbb'} 
console.log(obj1 === obj2) //false

위의 경우 처럼 프로퍼티 하나를 변경하기 위해 변경하지 않을 프로퍼티도 하드코딩으로 입력했다. 만약 1억개의 프로퍼티중 일부만 바꿔야 한다면 나머지를 하드코딩으로 입력하기란 쉽지않다.

1-5-2 얕은 복사와 깊은 복사

깊은복사: 내부 모든 값 복사

얕은복사: 아래 단계 값만 복사 (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

깊은 복사 하는법

  1. 재귀함수 만들기

재귀함수를 만들어 사용하는 방법이 있다.

// 깊은 복사 함수
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 } }
  1. JSON 사용하기

더 좋은 방법은 객체를 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]
  1. 더 좋은 방법은 lodash라이브러리를 이용하는 것이다.

06 undefined와 null

“없음”을 표현하는 두 타입

undefined: JS엔진이 자동으로 부여

null: 사용자가 명시적으로 부여

JS엔진이 undefined 부여하는 경우

  1. 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
  2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
  3. return 문이 없거나 호출되지 않는 함수의 실행 결과

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 

0개의 댓글