Be lazy, Be crazy

1. 데이터 타입 본문

책/코어 자바스크립트

1. 데이터 타입

LAZY SIA 2021. 12. 28. 20:39
728x90
반응형

 

1-1 데이터 타입의 종류

1) 기본형(원시형, primitive type)

- number, string, boolean, null, undefined, symbol(ES6)

 

2) 참조형(reference type)

- object, Array. function, Date, RegExp, Map, WeakMap, Set ,WeakSet(ES6)


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

메모리와 데이터 

- 컴퓨터는 모든 데이터를 0 또는 1로 바꿔 기억한다

- 비트(bit) : 0 또는 1만 표현할 수 있는 하나의 메모리 조각, 고유한 식별자를 통해 위치 확인

- 바이트(bite) : 8bit. 

 

- 정적 타입 언어 (C/C++, JAVA)는 메모리 낭비를 최소화하기 위해 데이터 타입별로 할당할 메모리 영역을 나누어 정함

- Javscript 는 메모리 관리에 대한 압박에서 자유로워짐

   - 숫자의 경우 형을 구분하지 않고 8바이트를 확보함

   - 문자열은 특별히 정해진 규격이 없음 (한 글자마다 영어는 1바이트, 한글은 2바이트 등으로 필요 메모리 용량이 가변적)

 

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

 

식별자와 변수

- 변수 : 변할 수 있는 데이터 (숫자, 문자열, 객체, 배열 모두 데이터)

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


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

변수 선언

var a;

- 변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다

- 컴퓨터 메모리에서 비어있는 공간 하나 확보해서 이 공간의 이름(식별자)을 a라고 지정 (변수 선언 과정)

- 이후에 사용자가 a 접근하려고 하면 컴퓨터는 메모리에서 a라는 이름을 가진 주소 검색해 해당 공간의 데이터를 반환

- 변수란 결국 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇 (숫자를 담았다가 문자열을 담는 등의 명령)

 

데이터 할당

- 해당 위치에 데이터를 직접 저장하지는 않는다.

- 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 데이터를 저장하고(데이터 영역)

- 그 주소를 변수 영역에 저장하는 식(변수 영역)

- 직접 대입하지 않는 이유 : 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리

 

- 확보한 공간 내에서만 데이터 변환을 할 수 있다면?
   - 변환한 데이터 다시 저장하기 위해 그 공간을 변환된 데이터 크기만큼 늘리는 작업이 선행되어야 함

      - 해당 공간이 메모리 상 가장 마지막에 있었다면 뒤쪽으로 늘리기만 하면 됨

      - 중간에 있는 데이터를 늘려야 한다면 ... 컴퓨터가 처리해야 할 연산이 많아질 수 밖에 없음

 

- 기존 문자열에 어떤 변환을 가하든 상관 없이 무조건 새로 만들어 별도의 공간에 저장

- 변수 영역과 데이터 영역을 분리하면 중복된 데이터에 대한 처리 효율이 높아짐


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

불변값

- 변수와 상수를 구분 짓는 변경 가능성 대상은 변수 영역 메모리

- 불변성과 가변성을 구분 짓는 변경 가능성 대상은 데이터 영역 메모리

- 기본형 데이터은 모두 불변값

// (1)
let a = 'abc';
a = a + 'def';

// (2)
let b = 5;
let c = 5;
b = 7;

(1) 변수 a에 문자열 'abc'를 저장한 데이터 영역 주소를 저장

   기존의 'abc'가 'abcdef'로 바뀌는 것이 아니라 데이터 영역에 새로운 문자열 'abcdef'를 만들어 그 주소를 변수 a에 저장

(2) 변수 b에 5가 저장된 데이터 영역 주소를 저장 (없으면, 새로운 공간 만들어 5를 저장하고 그 주소를 변수 b에 저장)

   변수 c에 5가 저장된 데이터 영역 주소를 저장 (이미 만들어 놓은 값이 있으면, 그 주소를 재활용하여 그 주소를 변수 c에 저장)

   기존에 저장된 5 자체를 7로 바꾸는 것이 아니라 기존에 저장했던 7을 찾아서 있으면 재활용하고, 없으면 새로 만들어 그 주소를 b에 저장

   

- 한 번 만든 값을 바꿀 수 없고, 변경은 새로 만드는 동작을 통해서만 이루어짐 (불변값의 성질)

- 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않음

 

가변값

- 참조형 데이터의 기본적 성질은 가변값인 경우가 많지만, 설정에 따라 변경 불가능한 경우도 있고, 아예 불변값으로 활용하는 방안도 있음

 

// (1)
let obj = {
    a: 1,
    b: 'bbb'
};

// (2)
obj.a = 2;

(1) 변수 영역에 이름이 obj인 빈 공간 확보

   프로퍼티(a, b)를 저장하기 위해 별도의 변수 영역(객체의 변수 영역)을 마련하고 그 영역의 주소를 데이터 저장 공간에 저장

   마련한 객체의 변수 영역에 프로퍼티 a, b 지정

   이름: a, 값: 데이터 영역에서 1 찾고 없으면 저장하여 그 주소값 작성

   이름: b, 값: 데이터 영역에서 'bbb' 찾고 없으면 저장하여 그 주소값 작성

(2) 이름이 a인 공간의 값을 데이터 영역에서 2 찾고 없으면 저장하여 그 주소값으로 변경

   obj가 바라보는 주소값은 변하지 않음

   데이터 영역이 바라보는 주소의 변수 영역 내 데이터가 변한 것

   

- 참조형 데이터와 기본형 데이터와의 차이는 참조형 데이터는 '객체의 변수 영역'이 별도로 존재

- 데이터는 데이터 영역에 보관되어 있고, 변수 영역에는 데이터 영역의 주소만 작성

 

- 데이터 영역에 저장된 값은 모두 불변값이다.

- 그러나 변수에는 다른 값을 얼만든지 대입할 수 있다. 바로 이 부분 때문에 흔히 참조형 데이터는 불변하지 않다(가변값)라고 하는 것

- object의 데이터 영역에는 주소값 범위 입력

 

변수 복사 비교

변수 복사 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보게 되는 점에서 동일함

복사 과정은 동일하지만 데이터 할당 과정에서 데이터 할당 과정에서 이미 차이가 있기 때문에 변수 복사 이후의 동작에도 큰 차이가 발생

 

객체의 프로퍼티 변경 시

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

b = 15;
obj2.c = 20;

 - 변수 a와 b는 서로 다른 주소를 바라보게 됐으나, 변수 obj1와 obj2는 여전히 같은 객체를 바라보고 있는 상태

- 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다는 차이

 

객체 자체 변경 시

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

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

- 메모리의 데이터 영역의 새로운 공간에 새 객체가 저장되고 그 주소를 변수 영역의 obj2 영역에 저장 (객체에 대한 변경임에도 값이 달라짐)

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


1-5 불변 객체 (immutable object)

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

불변 객체가 필요한 경우

- 값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우

- 바뀌기 전과 바뀐 후의 정보를 비교해야 하는 기능 구현할 때

  (정보가 바뀐 시점에 알림을 보내야 한다거나, 차이를 가시적으로 보여줘야 하는 등)

 

immutable.js, baobab.js 등의 라이브러리

- 자바스크립트 내장 객체가 아닌 라이브러리 자체에서 불변성을 지닌 별도의 데이터 타입과 그에 따른 메서드 제공

 

얕은 복사와 깊은 복사

얕은 복사(shallow copy)

- 바로 아래 단계의 값만 복사

- 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다는 의미

- 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터 주소를 가리키게 됨

- 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀜

var copyObject = function (target) {
    const result = {};
    for (let prop in target) {
        reulst[prop] = target[prop];
    }
    return result;
};

var user = {
    name: 'lazy',
    urls: {
        portfolio: 'http://github.com/abc',
        blog: 'https://lazy-crew.tistory.com'
    }
};

var user2 = copyObject(user);

user2.name = 'sia'; 
console.log(user.name === user2.name); // (1) false 

user.urls.portfolio = 'http://portfolio.com'; 
console.log(user.urls.portfolio === user2.urls.portfolio); // (2) true 

user2.urls.blog = ''; 
console.log(user.urls.blog === user2.urls.blog); // (3) true

(1) 사본인 user2의 name 프로퍼티를 바꿔도 user의 name 프로퍼티는 바뀌지 않음

  - user 객체에 직접 속한 프로퍼티에 대해선 복사해서 완전히 새로운 데이터가 만들어짐

(2)(3) 원본과 사본 중 어느쪽을 바꾸더라도 다른 한쪽의 값도 함께 바뀜

   - 한 단계 더 들어간 urls의 내부 프로퍼티들은 기존 데이터를 그대로 참조함

   -> user.urls 프로퍼티에 대해서도 불변 객체로 만들어야 이런 현상 발생하지 않음

 

깊은 복사(deep copy)

- 내부의 모든 값들을 하나하나 찾아서 전부 복사

var copyObject = function (target) {
    const result = {};
    for (let prop in target) {
        reulst[prop] = target[prop];
    }
    return result;
};

var user = {
    name: 'lazy',
    urls: {
        portfolio: 'http://github.com/abc',
        blog: 'https://lazy-crew.tistory.com'
    }
};

var user2 = copyObject(user);
user2.urls = copyObject(user.urls); // (1)

user.urls.portfolio = 'http://portfolio.com'; 
console.log(user.urls.portfolio === user2.urls.portfolio); // (2) false

user2.urls.blog = ''; 
console.log(user.urls.blog === user2.urls.blog); // (3) false

(1) urls 프로퍼티에 copyObject 함수를 실행한 결과를 할당

(2)(3) urls 프로퍼티의 내부까지 복사해서 새로운 데이터가 만들어졌으므로 결과는 false

 

객체의 프로퍼티 중
그 값이 기본형 데이터일 경우에는 그대로 복사,

그 값이 참조형 데이터일 경우에는 다시 그 내부의 프로퍼티들을 복사

-> 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 비로소 깊은 복사가 되는 것

 

객체의 깊은 복사를 수행하는 범용 함수

var copyObjectDeep = function(target) {
    var result = {};
    if (typeof target === 'object' && target !== null) { // (1)
        for (var prop in target) {
            result[prop] = copyObjectDeep(target[prop]);
        }
    } else {
        result = target; // (2)
    }
    return result;
};

var obj = {
    a: 1,
    b: {
        c: null,
        d: [1, 2]
    }
};

var obj2 = copyObjectDeep(obj);

obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;

console.log(obj); // { a: 1, b: { c: null, d: [1, 3] } }
console.log(obj2); // { a: 3, b: { c:4, d: {0:1, 1:2} } }

(1) target이 객체이고 null이 아닐 경우(typeof null === 'object' 버그) 내부 프로퍼티들을 순회하며 copyObjectDeep 함수를 재귀적으로 호출

(2) target이 객체가 아닌 경우엔 target을 그대로 지정

-> 원본과 사본이 서로 완전히 다른 객체를 참조하게 되어 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않게 됨

 

JSON을 활용한 깊은 복사

var copyObjectViaJSON = function (target) {
    return JSON.parse(JSON.stringify(target));
};

- 객체를 JSON 문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸는 것

- 다만 메서드나 숨겨진 프로퍼티인 __proto_나 getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티들은 모두 무시

- 순수한 정보만 다룰 때 활용하기 좋은 방법 (ex. httpRequest로 받은 데이터를 저장한 객체를 복사할 때 등)

 


1-6 undefined와 null

'없음'을 나타내는 두 가지 값 : undefined, null

- undefined : 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여하는 값 (직접 할당할 수도 있긴 함)

- null : '비어있음'을 명시적으로 나타내고 싶을 때 사용

 

// (1)
var arr1 = [undefined, 1];

// (2)
var arr2 = [];
arr2[1] = 1;

// 순회와 관련된 배열 메서드
arr1.forEach(function (v,i) { console.log(v, i); }); // undefined 0 / 1 1
arr2.forEach(function (v,i) { console.log(v, i); }); // 1 1

(1) 비어있음을 나타내려고 직접 할당한 undefined

- 값으로써 할당되어 실존하는 데이터

- 순회와 관련된 배열 메서드(forEach, map, filter, reduce 등)들의 순회 대상

 

(2) 비어 있어서 자바스크립트 엔진이 자동으로 부여한 undefined

- arr2[0]은 값을 대입하지 않은 상태

- 순회와 관련된 배열 메서드(forEach, map, filter, reduce 등)들의 순회 대상에서 제외됨

-> 값이 지정되지 않은 인덱스는 '아직은 존재하지 않는 프로퍼티'에 지나지 않는 것

 

비어있음 값을 직접 할당해주고 싶을 때는 null을 사용함

- 자바스크립트 엔진이 반환해주는 경우에만 undefined를 부여받고 직접 입력하는 경우엔 null을 입력하여 혼란 방지

 

ES6에서의 undefined

- ES6의 let, const에 대해서는 undefined를 할당하지 않은 채로 초기화를 마치며, 이후 특정 값을 할당하기 전까지는 해당 변수에 접근할 수 없음

 

null 주의할 점 (JS 자체 버그)

- typeof null === 'object' 이라는 점을 주의해야 함

-> 어떤 변수의 값이 null인지 여부를 판별하기 위해선 typeof가 아닌 다른 방식으로 접근

- 동등 연산자(==)로 비교할 경우 null과 undefined가 서로 같다고 판단

- 일치 연산자(===)를 써야만 정확히 판별할 수 있음

 

반응형
Comments