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

jjune095·2021년 3월 15일
0
post-thumbnail

01 . 데이터 타입

기본형과 참조형

Primitive type

  • 숫자(Number)
  • 문자열(String)
  • Boolean
  • Null
  • Undefined
  • Symbol (ES6)

Reference type

  • 객체 (Object)
    • 배열(Array)
    • 함수(Function)
    • 날짜(Date)
    • 정규식(RegExp)
    • Map, WeakMap, Set, WeakSet (ES6)

기본형은 immutable, 참조형은 mutable하다.

immutable 하다는 뜻은 무엇일까?? 분명 변수의 값은 바꿨는데 왜 immutable 인걸까??

차근차근 알아보도록 하자.

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

JavaScript는 메모리 용량 제약 없이 숫자의 경우 정수형, 부동소수형을 구분하지 않고 64비트 (8바이트)를 확보한다.

C언어나 자바와 같은 정적타입 언어는 메모리의 낭비를 최소화 하기 위해 데이터 타입별로 메모리 영역 분리한다.(4바이트-int..)

모든 데이터는 byte 단위의 식별자(메모리 주솟값)을 통해 서로 구분하고 연결한다.

변수(variable) - 변할 수 있는 데이터 (숫자, 객체, 배열)
⇒ 변경 가능한 데이터가 담길 수 있는 공간

식별자(identifier) - 어떤 데이터를 식별하는데 사용하는 이름 , 즉 변수명


1. 변수 선언과 데이터 할당

var a= "abc"; // ES5
let a= "abc"; // ES6
  1. 변수 영역에서 빈 공간(@1003)을 확보한다.
  2. 확보한 공간의 식별자를 a(@1003)로 지정한다.
  3. 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장한다.
  4. 변수 영역에서 a라는 식별자를 검색한다.
  5. 앞서 저장한 문자열의 주소(@5004)를 a(@1003)의 공간에 대입한다.

✔️ 변수 영역에 직접 대입하지 않고 번거롭게 한 단계를 더 거치는 이유는 ?

  • 동일한 데이터를 재생성하지 않기에 메모리를 더욱 효율적으로 관리.

  • 글자의 메모리 용량은 가변적이기 때문에 미리 확보한 공간을 넘어서면 확보된 공간에 맞춰 늘리는 작업 자체가 비효율적이다.


2. 기본형 데이터와 참조형 데이터

불변값(immutable)

불변값과 상수는 다른 의미이다.

변수와 상수를 구분 짓는 변경 가능성의 대상 ⇒ 변수 영역 메모리 (재할당 여부)

불변성 여부를 구분할때 변경 가능성의 대상 ⇒ 데이터 영역의 메모리

기본형 데이터(primitive type) 숫자,문자열,boolean,null,Symbol은 모두 불변값이다.

아래 예시를 통해 설명하겠다.

let a= 'abc';
a = a + 'def'

let b = 5;
let c = 5;
b = 7;

이 예시에서 문자열 'abc'가 'abcdef'로 바뀌는 것이 아니라 새로운 'abcdef' 문자열을 새로 만드는 것이다. 따라서 기존의 'abc'는 바뀌지 않기에 불변값이라고 하는 것이다.


b와 c 모두 5를 사용하기에 주소를 데이터 주소를 재활용 하지만 b를 7 로 바뀌면 5가 7로 바뀌는 것이 아니라 새로운 7을 만들어서 할당한다.

이처럼 데이터 영역의 값이 바뀌는게 아니라 다른 값으로 변경되는 것이 아니라 기존은 유지하고 새로 만들어서 할당해줌. (불변하다)

가변값

참조형 데이터(Reference type)
Object(array,function,date,RegExp,Map,Set)는 대부분 가변값이다. (일부 예외 제외)

let obj1 = {
	a: 1,
	b: 'bbb',
}

기본형 데이터와 차이는 '객체의 변수(프로퍼티) 영역'이 별도로 존재한다.

let obj1 ={
  a: 1,
  b: 'bbb',
};
obj1.a=2;

2라는 값은 기존에 없는 값이기에 새로 만들어지고 obj1.a이 가지는 주소값에 할당된다. 하지만 obj1의 객체 자체는 변경되지 않았기에 가변하다고 할 수 있다.

즉, 데이터는 똑같이 불변이지만 객체의 변수 영역에서 변수는 얼마든지 다른 값들이 대입될 수 있다.
⇒ 가변하다.

변수 복사 비교

let a = 10;
let b = a;
let obj1 = { c: 10, d: 'ddd' };
let obj2 = obj1;

b= 15;
obj2.c = 20;

변수 b의 값을 15로 바꾸면 새로운 주소가 생성되어 주소값이 바뀌게 된다. 따라서 a와 b의 주소가 다르다.

하지만, obj1, obj2는 여전히 같은 객체를 바라보고 있다. (값이 달라지지 않음. 가변) 프로퍼티 c가 가르키는 데이터 주소값만 바뀌지 obj1과 obj2는 여전히 같은 주소값을 가진다.


대부분의 자바 스크립트 책에서는 '기본형은 값을 복사, 참조형은 주솟값 복사'라고 설명한다.

하지만, 어떤 데이터 타입이든 변수에 할당하기 위해서는 주솟값을 복사해야 하고 엄밀히 말해 모든 데이터 타입이 참조형 데이터일 수 밖에 없다.

다만 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고 참조형은 한 단계를 더 거친다.

즉, 기본형도 결국 주솟값을 참조하긴 한다.

객체를 직접 변경 (프로퍼티 변경X)

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

obj2 = { c:20 , d: 'ddd' };

obj2에도 새로운 객체를 할당함으로써 값을 직접 변경했고 메모리 데이터 영역에 새로운 공간에 새 객체가 저장된다. 그 주소를 obj2가 저장하면서 객체에 대한 변경임에도 값이 달라진다.

⇒ 즉, 참조형 데이터가 '가변값'이라고 설명할 때는 '가변'은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립한다.

3. 불변 객체 (immutable object)

불변객체가 필요한 이유

값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 발생한다. 예를 들어 React에서 state 업데이트시 모든 부분을 update하는 것이 아니라 특정 부분만 update를 할때 사용된다.

let user ={
	name: 'Kim'
}
let newUser= user;
newUser.name='Lee';

console.log(user.name, newUser.name); // Lee Lee

왜 제대로 바뀌지 않았을까..? 😅
let newUser = user 으로 복사는 같은 객체를 바라본다..

어떻게 해결할 수 있을까?

// 기존 정보 복사 => 얕은 복사
const copyObject = function (target){
	let result = {};
	for (const prop in target) {
		result[prop] = target[prop];
	}
}

하지만 이 역시 얕은 복사임으로 아쉬운 점이 있다.

얕은 복사와 깊은 복사

얕은 복사(shallow copy)는 바로 아래 단계의 값만 복사하는 방법. (참조형 프로퍼티 복사시 주소값만 복사)

깊은 복사(deep copy)는 내부의 모든 값들을 하나하나 찾아 전부 복사하는 방법이다.

얕은 복사의 문제점

  1. 중첩된 객체가 있을때 참조형 데이터가 저장된 프로퍼티를 복사하는 경우 그 주소 값만 복사한다. 따라서 중첩된 객체를 수정하면 원본도 함께 변경된다.
let user{
	name: 'Kim',
    urls: {
    ...
    }
}
let user2= copyObject(user);

urls은 중첩 객체기에 원본 객체와 함께 변경된다.

얕은 복사 해결방법

  1. 내부 중첩 프로퍼티들도 함께 얕은 복사를 한다.
    기본형 데이터일 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다.

  2. 또는 spread 문법을 사용한다. (...object)

  3. 또는 Json을 활용한다.

    JSON으로 표현된 문자열로 전환 → 다시 JSON 객체로 변경.
    httpRequest로 받은 데이터를 저장한 객체를 복사할 때 유용하게 사용된다.

4. undefined와 null

undefined는 자바스크립트 엔진이 사용자가 어떤 값을 지정할 것이라고 예상했지만 실제로 그렇지 않을 때 자동으로 부여한다. (null과는 다르다)

let a;
console.log(a); // (1) undefined. 값을 대입하지 않은 변수에 접근

let obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) undefined. 존재하지 않는 프로퍼티에 접근

const func = () => {};
const c = func(); // (3) return 값이 없으면 undefined를 반환한 것으로 간주
console.log(c); // undefined

단, 배열의 값은 없지만 size가 할당된 경우 undefined가 아니라 비어있는 요소로 정해진다. undefined는 '비어있음'을 의미하여 하나의 값으로 동작하기에 forEach 등에서 순회의 대상이 될 수 있다.

let arr1 = [];
arr1.length = 3;
console.log(arr1); // [empty x 3]

let arr2 = new Array(3);
console.log(arr2); // [empty x 3]

let arr3 = [undefined, undefined, undefined]; // 사용자가 임의로 undefined 부여
console.log(arr3); // [undefined, undefined, undefined]

⇒ 이는 혼란을 주기 때문에 '비어있음'을 명시적으로 나타내고 싶을 때는 undefined가 아닌 null을 사용한다.

단, null을 확인할 때 typeof을 사용하면 객체로 표현되는 버그가 존재한다. 따라서 ==가 아닌 ===으로 비교해서 null인지 undefined인지 확인할 수 있다.

let n = null;
console.log(typeof n); // object
console.log(n==undefined); // true
console.log(n==null); // true

console.log(n===undefined); // false
console.log(n===null); // true

References

위키북스 코어자바스크립트 도서, 정재남 지음

profile
프론트엔드 개발자 남준영입니다.

0개의 댓글