ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TIL] 3주차 : 데이터 타입(심화)
    이노베이션캠프/Js 문법 종합반 2023. 5. 23. 19:56

    * 데이터타입

    1. 데이터타입의 종류(기본형과 참조형)

    (이미지 출처 : https://velog.io/@imjkim49/자바스크립트-데이터-타입-정리 )

    - 참조형 : 크기가 큰 것들, 기본적으로 Object

    - 기본형과 참조형 나누는 기준 : 값의 저장 방식, 불변성 여부

    💡 [기본형과 참조형의 구분 기준]
    1. 복제의 방식
      1. 기본형 : 값이 담긴 주소값을 바로 복제
      2. 참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
    2. 불변성의 여부
      1. 기본형 : 불변성을 띔 (=불변하다) // 메모리관점에서 불변하다
      2. 참조형 : 불변성을 띄지 않음

    2. 메모리와 데이터에 관한 배경지식

    1) 메모리, 데이터

    (1) 비트

    - 컴퓨터가 이해하는 가장 작은 단위

    - 0과 1을 가지고 있는 메모리를 구성하기 위한 작은 조각

    (2) 바이트

    - 0과 1만 표현하는 비트를 모두 찾는 것은 부담

    - 비트를 8개 모아둔 byte 단위 탄생

    (3) 메모리 : byte 단위로 구성

    - 모든 데이터는 byte 단위의 식별자인 메모리 주소값을 통해서 서로 구분됨

    - 즉, 바이트로 메모리의 주소값을 부여한다.

     

    2) 식별자, 변수

    let testValue = 3;

    - 식별자 : testValue // 변수명

    - 변수 : 3 // 데이터

     

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

    1) 할당 예시(풀어쓴 방식, 붙여 쓴 방식 동일하게 작동)

    /** 선언과 할당을 풀어 쓴 방식 */
    var str;
    str = 'test!';
    
    /** 선언과 할당을 붙여 쓴 방식 */
    var str = 'test!';

    편하게 이해하기 위해 선언과 할당을 풀어 쓴 방식을 중심으로 보면서 진행

    - 메모리 영역

    주소 ... 1002 1003 1004 1005 ...
    데이터   str / 5002(주소저장)        
    주소 5002 5003 5004 5005
    데이터   test!        

    ( 책 <코어 Javascripts>에서 발췌, 실제 메모리 영역이 이렇게 생기지 않았지만 편의를 위해 이렇게 표현 )

    - 위의 두 행(기울어진 글꼴)을 변수 영역

    - 아래의 두 행을 데이터 영역이라고 생각

    - 변수의 영역을 돌며 빈 공간을 찾아 변수명과 데이터가 저장된 곳의 주소 저장(여기서는 주소 1002),

    데이터 영역을 돌아가며 빈공간을 찾아 데이터 저장(여기서는 5002)!

    - 왜 굳이 데이터의 주소를 저장하나?

    자유로운 데이터의 변환을 위해 : 이미 입력한 문자열이 길어진다면? 숫자는 항상 8byte로 고정이지만, 문자는 고정이 아님. 그래서 할당을 한 뒤 데이터를 변환하려고 할 때, 더 큰 데이터를 저장하려고 하면 저장하려는 데이터의 크기만큼 쭉 모든 데이터를 미뤄야함

    메모리의 효율적인 관리를 위해 : 똑같은 데이터를 여러번 저장해야 한다면? 1만개의 변수를 생성해 모든 변수에 숫자 1을 할당한다고 하자. 모든 변수를 별개로 인식한다고 한다면 1만개의 변수 공간을 확보해야함.

    즉, 1만개 * 8byte = 8만 byte 공간이 필요함. 

    하지만 변수의 영역에 별도 저장을 한다면,

    변수 영역: 2byte(가정) * 1만개 = 2만 byte / 데이터 영역 : 8byte 1개 = 8 byte (하나를 저장해놓고 모두 같은 주소를 가지면 되니까) 총 2만 8byte

     

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

    1) 메모리를 기준으로 다시 한 번 생각해보는 두 가지 주요 개념

    (1) 변수 vs 상수

    - 변수 : 변수 영역 메모리를 변경할 수 있음 (데이터 영역 주소를 바꾸는 거임 / 데이터 영역의 메모리가 변경되지는 않음 / 새롭게 할당된 주소를 가지고 오는 것)

    - 상수 : 변수 영역 메모리를 변경할 수 없음

    (2) 불변하다 vs 불변하지 않다

    - 불변하다 : 데이터 영역 메모리를 변경할 수 없음 

    - 불변하지 않다 : 데이터 영역 메모리를 변경할 수 있음

     

    2) 불변값과 불변성(with 기본형데이터) // 총망라

    // a라는 변수가 abc에서 abcdef가 되는 과정을 통해 불변성을 유추해봅시다!
    
    // 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정할게요.
    var a = 'abc';
    
    // 'def'라는 값이 @5002라는 주소에 추가되는 것이 아니죠!
    // @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
    // 즉, "변수 a는 불변하다." 라고 할 수 있습니다.
    // 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 됩니다.
    a = a + 'def';

     

    3) 가변값과 가변성(with 참조형 데이터)

    (1) 참조형 데이터의 변수 할당 과정

    // 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요합니다!
    var obj1 = {
    	a: 1,
    	b: 'bbb,
    };
    주소 1001 1002 1003 1004
    데이터 obj1 / @7103~      
    주소 5001 5002 5003 5004
    데이터 1 'bbb'    

     

    주소 7103 7104 7105 7106
    데이터 a / @5001 b / @5002    

    - 기본형 데이터와 같이 첫번째 표의 위의 두 행이 변수영역, 밑의 두 행과 밑의 표까지가 데이터영역

    - 맨 밑의 영역(표)는 obj1이 참조형 데이터이기 때문에, obj1을 위한 별도 메모리 공간(데이터영역 포함)임. 

    - 이런식으로 변수 할당과정이 이루어짐

    - 즉, object를 위한 별도의 공간이 필요하냐, 아니냐

     

    (2) 참조형 데이터가 불변하지않다(가변하다)라고 하는 이유

    var obj1 = {
    	a: 1,
    	b: 'bbb',
    };
    
    // 데이터를 변경해봅시다.
    obj1.a = 2;
    1. 위에 작성해놓았던 표를 토대로 변경 테스트를 해보며 알아봅시다 😉
    2. 과정은 아래와 같아요.
      1. 변경할 값인 숫자 2를 데이터 영역에서 검색합니다.
      2. 없네요! 2를 새로 추가하고, 해당 주소(ex : @5003)를 obj1을 위한 별도 영역에 갈아껴줍니다.
    3. 데이터 영역에 저장된 값은 여전히 계속 불변값이지만, obj1을 위한 별도 영역은 얼마든지 변경이 가능해요. 이것 때문에 참조형 데이터를 흔히, **‘불변하지 않다(=가변하다)’**라고 합니다.

    // 그대로 가지고옴. 위의 것 읽는 것이 더 빠름

    // 결국 데이터 영역이 변경(데이터 영역 중 별도 영역) 이 되었기에, 가변하다고 보는 것

     

    (3) 중첩객체의 할당

    - 객체 안에 또 다른 객체가 들어가는 것

    var obj = {
    	x: 3,
    	arr: [3, 4, 5],
    }
    
    // obj.arr[1]의 탐색과정은 어떻게 될까요? 작성하신 표에서 한번 찾아가보세요!

     

    주소 1001 1002 1003 1004 1005 ...
    데이터 obj / @7103~          
    주소 5001 5002 5003 5004 5005 ...
    데이터 3 4 5      
    주소 7103 7104 ...
    데이터 x / @5001 arr / @8104~  

    //obj를 위한 공간

    주소 8104 8105 8106 ...
      0 / @5001 1 / @5002 2 / @5003  

    // arr를 위한 공간

     

    (4) 참조 카운트가 0인 메모리 주소의 처리 // 가비지 콜렉터가 처리했으니 안심하라구!

    - 참조 카운트란?

    객체를 참조하는 변수나 다른 객체의 수를 나타내는 값. 참조 카운트가 0인 객체는 더 이상 사용되지 않으므로 가비지 컬렉터에 의해 메모리에서 제거됨

    - 가비지 컬렉터란? 

    더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할. 

     

    4) 변수 복사의 비교

    // STEP01. 쭉 선언을 먼저 해볼께요.
    var a = 10; //기본형
    var obj1 = { c: 10, d: 'ddd' }; //참조형
    
    // STEP02. 복사를 수행해볼께요.
    var b = a; //기본형
    var obj2 = obj1; //참조형
    주소 1001 1002 1003 1004 1005  
    데이터 a / @5001 obj1 / @7103~ b / @5001 obj2 / @7103~    
    주소 5001 5002 5003 5004 5005  
    데이터 10 'ddd'        

     

    주소 7103 7104 ...
    데이터 c / @5001 d / @5002  

     복사 이후 값 변경

    // STEP01. 쭉 선언을 먼저 해볼께요.
    var a = 10; //기본형
    var obj1 = { c: 10, d: 'ddd' }; //참조형
    
    // STEP02. 복사를 수행해볼께요.
    var b = a; //기본형
    var obj2 = obj1; //참조형
    
    b = 15;
    obj2.c = 20;
    주소 1001 1002 1003 1004 1005  
    데이터 a / @5001 obj1 / @7103~ b / @5003 obj2 / @7103~    
    주소 5001 5002 5003 5004 5005  
    데이터 10 'ddd' 15 20    

     

    주소 7103 7104 ...
    데이터 c / @5004 d / @5002  

    참조형 데이터에서 문제가 생김!!!

    // 기본형 변수 복사의 결과는 다른 값!
    a !== b;
    
    // 참조형 변수 복사의 결과는 같은 값!(원하지 않았던 결과😭)
    obj1 === obj2;

    그렇기에 가변한다는 것은 매우 위험한 것임

     

    이번에 아예 obj2의 객체를 갈아끼워보겠음

    //기본형 데이터
    var a = 10;
    var b = a;
    
    //참조형 데이터
    var obj1 = { c: 10, d: 'ddd' };
    var obj2 = obj1;
    
    b = 15;
    obj2 = { c: 20, d: 'aaa'};
    주소 1001 1002 1003 1004 1005  
    데이터 a / @5001 obj1 / @7103~ b / @5003 obj2 / @8104~    
    주소 5001 5002 5003 5004 5005  
    데이터 10 'ddd' 20 'aaa'    

     

    주소 7103 7104 ...
    데이터 c / @5001 d / @5002  
    주소 8104 8105 ...
    데이터 c / @5003 d / @5004  

    이렇게 되면 우려하던 상황이 발생하지 않음!

     

    +) 참조형 데이터가 ‘가변값’이라고 할 때의 ‘가변’은 참조형 데이터 자체를 변경할 경우가 아니라, 그 내부의 프로퍼티를 변경할 때 성립한다고 할 수 있겠네요 👍


    하지만 위에처럼 내부 프로퍼티를 싹 다 맨날 쓰는 건 하드코딩임. 

    그래서 얕은 복사를 사용함

     

    * 불변객체(얕은 복사 깊은 복사)

    1. 얕은 복사

    //이런 패턴은 어떨까요?
    var copyObject = function (target) {
    	var result = {};
    
    	// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
    	// 하드코딩을 하지 않아도 괜찮아요.
    	// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
    	// 되겠죠!?
    	for (var prop in target) {
    		result[prop] = target[prop];
    	}
    	return result;
    }
    //위 패턴을 우리 예제에 적용해봅시다.
    var user = {
    	name: 'wonjang',
    	gender: 'male',
    };
    
    var user2 = copyObject(user);
    user2.name = 'twojang';
    
    if (user !== user2) {
    	console.log('유저 정보가 변경되었습니다.');
    }
    
    console.log(user.name, user2.name);
    console.log(user === user2);

    이런 식으로 사용

    copyObject를 이용하게 되므로 처음 만들 때부터 아예 새로운 객체를 만들어주는 거임. 별도의 오브젝트임이 보장이 됨.

     

    하지만 이것도 문제가 있음! 중첩된 객체에 대해서 완벽한 복사가 불가능하기 때문

    그래서 사용되는 것이 깊은 복사임

     

    2. 깊은 복사

    내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는 방법

     

    -얕은 복사의 문제점이 나타나는 코드

    var user = {
    	name: 'wonjang',
    	urls: {
    		portfolio: 'http://github.com/abc',
    		blog: 'http://blog.com',
    		facebook: 'http://facebook.com/abc',
    	}
    };
    
    var user2 = copyObject(user);
    
    user2.name = 'twojang';
    
    // 바로 아래 단계에 대해서는 불변성을 유지하기 때문에 값이 달라지죠.
    console.log(user.name === user2.name); // false
    
    // 더 깊은 단계에 대해서는 불변성을 유지하지 못하기 때문에 값이 같아요.
    // 더 혼란스러워 지는거죠 ㅠㅠ
    user.urls.portfolio = 'http://portfolio.com';
    console.log(user.urls.portfolio === user2.urls.portfolio); // true
    
    // 아래 예도 똑같아요.
    user2.urls.blog = '';
    console.log(user.urls.blog === user2.urls.blog); // true

    - 결국 user.urls 프로퍼티도 불변 객체로 만들어야 함

    - 깊은 복사를 이용한 코드(이것도 중첩에 중첩된 건 또 문제가 생김)

    var user = {
    	name: 'wonjang',
    	urls: {
    		portfolio: 'http://github.com/abc',
    		blog: 'http://blog.com',
    		facebook: 'http://facebook.com/abc',
    	}
    };
    
    // 1차 copy
    var user2 = copyObject(user);
    
    // 2차 copy -> 이렇게까지 해줘야만 해요..!!
    user2.urls = copyObject(user.urls);
    
    user.urls.portfolio = 'http://portfolio.com';
    console.log(user.urls.portfolio === user2.urls.portfolio);
    
    user2.urls.blog = '';
    console.log(user.urls.blog === user2.urls.blog);

    > 결론 : 객체의 프로퍼티 중, 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사 ⇒ 재귀적 수행!

    //결과 확인
    var obj = {
    	a: 1,
    	b: {
    		c: null,
    		d: [1, 2],
    	}
    };
    var obj2 = copyObjectDeep(obj); // 이게바로 재귀적 수행
    
    obj2.a = 3;
    obj2.b.c = 4;
    obj2.b.d[1] = 3;
    
    console.log(obj);
    console.log(obj2);

    이렇게까지 해야한다는 거임. 


    * undefined와 null

    1. undefined

    - 개발자가 정해주는 경우는 거의 없음

    1) 변수의 값이 지정되지 않은 경우, 데이터의 영역의 메모리 주소를 지정해주지 않은 식별자에 접근할 때

    2) .이나 []로 접근하려 할 때, 해당 데이터가 존재하지 않는 경우

    3) return 문이 없거나 호출되지 않는 함수의 실행결과

    var a;
    console.log(a); // (1) 값을 대입하지 않은 변수에 접근
    
    var obj = { a: 1 };
    console.log(obj.a); // 1
    console.log(obj.b); // (2) 존재하지 않는 property에 접근
    // console.log(b); // 오류 발생
    
    var func = function() { };
    var c = func(); // (3) 반환 값이 없는 function
    console.log(c); // undefined

    - 없다 를 표현할 때는 undefined말고 null 사용

     

    2. null

    - typeof null 하면 object 가 나온다! 는 것만 명심

    // 걍 Js 버그임

    var n = null;
    console.log(typeof n); // object
    
    //동등연산자(equality operator)
    console.log(n == undefined); // true
    console.log(n == null); // true
    
    //일치연산자(identity operator)
    console.log(n === undefined); //false
    console.log(n === null); //true
Designed by Tistory.