Skip to content

Latest commit

 

History

History
505 lines (364 loc) · 29.3 KB

File metadata and controls

505 lines (364 loc) · 29.3 KB

데이터 타입과 변수

🤘 목표

  • 원시 타입과 참조 타입이 무엇이고 어떤 차이점이 있는지 이해한다.
  • 변수 선언과 데이터 할당 과정을 이해한다.
  • 불변값과 가변값이 무엇이고 어떤 차이점이 있는지 이해한다.
  • 변수 복사시 일어나는 변화에 대해 이해하고 기본형과 참조형 복사에 어떤 차이점이 있는지 이해한다.

🕵 데이터 타입의 정의와 종류

  1. 데이터

    넓은 의미에서 데이터는 의미 있는 정보를 가진 모든 값을 뜻합니다. 어떠한 사실, 개념, 명령 또는 실험이나 관측 결과로 얻은 수치나 값을 숫자, 문자, 기호 등으로 표현한 것입니다.

  2. 데이터 타입

    데이터 타입은 자료형이라고도 합니다. 여러 종류의 데이터를 식별하는 분류, 더 나아가 해당 자료형의 값을 저장하는 방식을 말합니다.

  3. 자바스크립트의 데이터 타입

    • 기본형(원시 타입)
    • 참조형(레퍼런스 타입)

    Primitive Type Reference Type (Object)
    Number Array
    String Function
    Boolean Date
    null RegXp
    undefined Map, WeakMap
    Symbol Set, WeakSet

    원시 타입은 값이 담긴 주솟값을 바로 복제하는 반면 참조 타입은 값이 담긴 주솟값들로 이뤄진 묶음을 가리키는 주솟값을 복제한다는 점이 다릅니다. 때문에, 원시 타입은 불변성을 띕니다. 이게 뭔 개소리냐구요? 이를 이해하기 위해서는 메모리에 관한 배경지식이 필요합니다. 😉

🖥️ 메모리와 데이터

컴퓨터는 모든 데이터를 0 혹은 1로 바꿔 기억합니다. 이때 0 혹은 1, 두 가지 데이터만 표현할 수 있는 하나의 메모리 조각을 비트(bit)라고 합니다. 보통 전구에 많이 비유하는데요, 전구 하나가 비트라고 하면 불이 꺼진 전구는 0을 표현하고 불이 켜진 전구는 1을 표현하는 셈입니다. 참고로 비트가 8개 모이면 바이트(Byte)라고 합니다. 1바이트는 2^8가지의 데이터를 표현할 수 있기 때문에 0부터 255까지의 값을 가질 수 있게 됩니다.

메모리는 매우 많은 비트로 구성돼 있는데, 각 비트는 고유한 식별자를 통해 위치를 확인할 수 있습니다. 그러나 고작 0이나 1만 표현할 수 있는 비트 단위로 위치를 확인하는 것은 매우 비효율적입니다. 때문에 앞서 말씀 드린 바이트나 킬로바이트, 메가바이트와 같은 더 큰 단위로 비트를 묶습니다. 이렇게 비트를 더 큰 한 단위로 묶으면 검색 시간도 줄이고 표현할 수 있는 데이터의 수도 늘어나지만, 낭비되는 비트가 생기기도 한다는 단점도 있습니다.

"비트보다 더 큰 값은 표현해야겠고... 그렇다고 메모리를 낭비할 순 없고..." 자연스럽게 이와 같은 고민이 생겼습니다. "자주 사용하지 않을 데이터를 표현하기 위해 빈 공간을 남겨놓기보다, 표현 가능한 값의 개수에 제약이 따르더라도 크게 문제가 되지 않을 적정할 공간을 묶자!"라는 고민의 결과(= 적정 공간, 적정 크기)가 바로 바이트 단위입니다.

1Byte = 8bit

1bit 1bit 1bit 1bit 1bit 1bit 1bit 1bit
0 1 0 1 1 0 0 1

C/C++, 자바 등의 정적 타입 언어에서는 메모리 낭비를 최소화하기 위해 데이터 타입별로 할당 메모리 영역을 2Byte, 4Byte 등으로 나눠 정해놓았습니다 이는 잦은 형 변환 문제를 일으켰습니다. 예를 들어 사용자가 지정된 데이터 타입의 메모리 영역보다 더 큰 값을 입력하는 경우 오류가 나거나, 잘못된 값이 저장됩니다. 이와 같은 문제에 제대로 대처하기 위해 이슈가 발생할 때마다 개발자가 일일이 타입을 변환해야만 하는 번거로운 작업을 해야만 했습니다. 참고를 위해 잠시 MySQL의 자료형 중 숫자형 일부를 살펴보겠습니다. 자세한 사항은 이 글의 범위를 넘어서는 부분이므로 참고만 하시면 좋을 듯합니다.

자료형 Byte 표현범위
TINYINT 1Byte signed: -128 ~ 127, unsigned: 0 ~ 255 (2^8개)
INT 4Byte signed: -2147683648 ~ 2147483647, unsigned: 0 ~ 4294967295 (2^32 개)

한편, 메모리 용량이 과거보다 월등히 커진 상황에서 등장한 자바스크립트는 상대적으로 메모리 관리에 대한 압박에서 자유로워졌습니다. 때문에 메모리 공간을 좀 더 넉넉하게 할당했습니다. 숫자의 경우 정수형인지 부동소수형(실수형, floating point)인지 구분하지 않고 64비트(8바이트)를 확보합니다. 이 중 1비트는 부호(+/-), 11비트는 지수부, 나머지 52비트는 가수부로 씁니다. 부동소수점이나 메모리에 관해 더 깊이 다루는 것은 이 글의 범위를 넘어서므로, '메모리와 데이터' 파트는 이쯤에서 부동소수점에 관한 재미있는 포스트을 링크하는 것으로 마무리하도록 하겠습니다.

📦 변수 선언과 데이터 할당

  • 변수 : 변할 수 있는 데이터
  • 식별자 : 어떤 데이터를 식별하는 데 사용하는 이름(변수명)
  1. 변수 선언

    var a; // undefined

    위의 예제를 사람의 언어로 풀어쓰면, "변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다"가 됩니다. 변할 수 있는 데이터이므로 선언할 때 undefined라도 나중에 다른 값으로 바꿀 수 있습니다. 변수란 데이터가 담길수 있는 공간 또는 그릇입니다. 박스에 비유를 하곤 합니다만, 정확히 변수는 열린 박스에 가깝습니다. 열려 있으므로, 박스에 내용물을 담았다 꺼내고 다른 데이터를 담는 것이 가능합니다. 컴퓨터가 위의 예제의 명령을 받아 메모리 영역에서 어떤 작업을 수행하는지 살펴보겠습니다.

    주소 ... 1002 1003 1004 1005 ...
    데이터 이름: a, 값:
    • 먼저, 명령을 받은 컴퓨터는 메모리에서 비어있는 공간 하나를 확보합니다.
    • 그 다음 이 공간의 이름(식별자)을 a라고 지정합니다. ← 변수 선언
    • 이후 사용자가 a에 접근하고자 하면, 컴퓨터는 메모리에서 a라는 이름을 가진 주소(이 예제에서는 @1003)를 검색해 해당 공간에 담긴 데이터를 반환합니다.

  1. 데이터 할당

    var a; // undefined
    
    a = 'abc';
    
    var a = 'abc';

    선언과 할당을 나누어 명령할 수 있고, 선언과 할당을 동시에 명령할 수 있습니다. 어느 방법으로 선언과 할당을 하더라도 자바스크립트 엔진은 결국 같은 동작을 수행합니다. 할당시 예상과는 다르게 해당 위치에 문자열 'abc'를 직접 저장하지는 않습니다. 데이터를 저장하기 위한 별도의 메모리 공간을 확보해서 문자열 'abc'를 저장하고, 그 주소를 변수 영역에 저장하는 식으로 이뤄집니다. 이제부터는 성질에 따라 변수 영역데이터 영역으로 구분해서 설명하겠습니다. 데이터 할당의 전체 흐름은 다음과 같습니다.

    변수 영역

    주소 ... 1002 1003 1004 1005 ...
    데이터 이름: a, 값: @5004

    데이터 영역

    주소 ... 5002 5003 5004 5005 ...
    데이터 'abc'
    • 먼저, 변수 영역에서 빈 공간(@1003)을 확보합니다.
    • 그 다음 확보한 공간의 식별자를 a로 지정합니다. ← 변수 선언
    • 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장합니다.
    • 변수 영역에서 a라는 식별자를 검색합니다.
    • 문자열의 주소(@5004)를 @1003의 공간에 대입합니다.(값)

    "왜 변수 영역에 직접 값을 대입하지 않고 번거로운 과정을 거치는 걸까?"라는 의문이 생깁니다. 이는 데이터 변환을 자유롭게 할 수 있게 함과 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위한 고민의 결과입니다. 앞서 자바스크립트는 숫자형 데이터에 대해 64비트(8바이트)의 공간을 확보한다고 했습니다. 반면 문자열은 정해진 규격이 없습니다. 한 글자마다 영어는 1바이트, 한글은 2바이트 등으로 각각 필요한 메모리 용량이 가변적이며 전체 글자 수 역시 가변적이기 때문입니다.

    만일 미리 확보한 공간 내에서만 데이터를 변환할 수 있다면, 변환한 데이터를 다시 저장하기 위해서는 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 선행돼야 합니다. 해당 공간이 메모리 상의 가장 마지막에 있었다면, 뒤쪽으로 늘리기만 하면 되므로 어렵지 않지만 중간에 있는 데이터를 늘려야 하는 상황이라면 어떨까요? 해당 공간보다 뒤에 저장된 데이터들을 전부 뒤로 옮기고, 이동시킨 주소를 각 식별자에 다시 연결하는 작업을 해야합니다. 컴퓨터가 처리해야 할 연산이 많이줄 수밖에 없습니다. 결국 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나눠 저장하는 것이 최선입니다.

    문자열 'abc'의 마지막에 'def'를 추가하려고 하면 컴퓨터는 앞서 'abc'가 저장된 공간에 'abcdef'를 할당하는 대신, 'abcdef'라는 문자열을 새로 만들어 별도의 공간에 저장하고, 그 주소를 변수 공간에 연결합니다. 반대로 'abc'의 마지막 'c'를 제거하려고 해도 새로 만듭니다. 기존 문자열에 어떤 변환을 가하든 상관 없이 무조건 새로 만들어 별도의 공간에 저장합니다. 기존 데이터(@5004)는 자신의 주소를 저장하는 변수가 하나도 없게 되면 가비지 콜렉터의 수거 대상이 됩니다. 가비지 콜렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거 대상들을 수거합니다. 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 됩니다. 가비지 컬렉션 알고리즘은 이곳을 참고하시기 바랍니다.

    변수 영역

    주소 ... 1002 1003 1004 1005 ...
    데이터 이름: a, 값: @5005(변경됨!)

    데이터 영역

    주소 ... 5002 5003 5004 5005 ...
    데이터 'abc' 'abcdef'

    다른 예로 500개의 변수를 생성해서 모든 변수에 숫자 5를 할당하는 상황을 가정해 봅니다. 각 변수를 별개로 인식하려면 500개의 변수 공간을 확보하는 것은 당연히 불가피합니다. 그런데 각 변수 공간마다 매번 숫자 5를 할당하려면 총 4000바이트를 사용해야 합니다. 대신 5를 데이터 영역의 별도의 공간에 한번만 저장하고 해당 주소만 저장하면, 주소 공간의 크기가 2바이트일 때 1008바이트만 사용할 수 있습니다. 이처럼 변수 영역과 데이터 영역을 분리하면 중복된 데이터에 대한 처리 효율이 높아집니다.

  2. 불변값

    변수와 상수를 구분하는 성질은 '변경 가능성'입니다. 앞서 변수를 열린 상자에 비유했는데요, 상수는 닫힌 상자에 비유할 수 있겠습니다. 바꿀 수 있으면 변수, 바꿀 수 없으면 상수입니다. 불변값과 상수를 같은 개념으로 오해하기 쉬운데, 이 둘을 명확히 구분할 필요가 있습니다. 변수와 상수를 구분하는 변경 가능성의 대상은 변수 영역 메모리입니다. 한번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지 여부가 관건입니다. 반면, 불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역 메모리입니다.

    • 변수/상수 : 변수 영역 메모리
    • 가변값/불변값 : 데이터 영역 메모리

    var a = 'abc';
    
    a += 'def';
    
    var b = 5;
    var c = 5;
    
    b = 7;

    변수 a에 문자열 'abc'를 할당했다가 뒤에 'def'를 추가하면새로운 문자열 'abcdef'를 만들어 데이터 메모리 영역에 생성되고 그 주소를 변수 a에 저장합니다. 'abc'와 'abcdef'는 완전히 별개의 데이터입니다.

    변수 b에 숫자 5를 할당하려고 하면 컴퓨터는 일단 데이터 메모리 영역에서 5를 찾고, 없으면 데이터 공간을 하나 만들어 저장합니다. 그 주소를 b에 저장합니다. 변수 c에 같은 수인 5를 할당하려고 하면 컴퓨터는 데이터 메모리 영역에서 5를 찾습니다. 이미 만들어 놓은 값이 있으니 그 주소를 재활용합니다.

    변수 b의 값을 7로 바꾸고자 하면 기존에 저장된 5 자체를 새로 바꾸는 것이 아니라 데이터 메모리 영역에서 7을 찾고, 없으면 새로 만들어 변수 b에 저장합니다. 결국 5와 7 모두 다른 값으로 변경할 수 없습니다. 이처럼 문자열, 숫자 값 등은 다른 값으로 변경할 수 없습니다. 변경은 새로 만드는 동작을 통해서만 이뤄집니다. 이것이 바로 불변값의 성질입니다. 한번 만들어진 값은 가비지 콜렉팅을 당하지 않는 한 영원히 변하지 않습니다. 기본형 데이터인 숫자, 문자열, Boolean, null, undefined, Symbol은 모두 불변값입니다.

  3. 가변값

    참조형 데이터의 기본 성질은 가변값인 경우가 많지만 설정에 따라 변경 불가능한 경우(Object.defineProperty(), Object.freeze())도 있고, 아예 불변값으로 활용하는 방안도 있습니다. 불변값으로 활용하는 예외적인 방향은 이 글의 범위를 벗어나므로 우선 참조형 데이터를 변수에 할당하는 과정부터 확인하겠습니다.

    var obj1 = {
        a: 1,
        b: 'bbb',
    };

    변수 영역

    주소 1001 1002 1003 1004 ...
    데이터 이름: obj1, 값: @5001

    데이터 영역

    주소 5001 5002 5003 5004 ...
    데이터 @7103 ~ ? 1 'bbb'

    객체 @5001의 변수 영역

    주소 7103 7104 7105 7106 ...
    데이터 이름: a, 값: @5003 이름: b, 값: @5004
    • 먼저 변수 영역의 빈 공간(@1002)을 확보하고, 식별자를 obj1로 지정합니다.
    • 여러 개의 프로퍼티로 이뤄진 데이터 그룹이기 때문에 별도의 변수 영역을 마련하고 그 영역의 주소(@7103 ~ ?)를 @5001에 저장합니다.
    • @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름을 지정합니다.
    • 데이터 영역에서 숫자 1을 검색하고 검색 결과가 없으므로 임의로 @5003에 저장하고 이 주소를 @7104에 저장합니다. 'bbb'도 같은 방식입니다.

    기본형 데이터(불변값)와의 차이는 객체 변수(프로퍼티) 영역이 별도로 존재한다는 점입니다. 객체가 별도로 할애한 영역은 객체 @5001의 변수 영역일 뿐, 데이터 영역은 기존 메모리 공간을 그대로 활용하고 있습니다. 데이터 영역에 저장된 값은 모두 불변값이나, 객체 @5001의 변수 영역에는 다른 값을 얼마든지 대입할 수 있습니다. 바로 이 부분 때문에 참조형 데이터는 "불변하지 않다(가변값이다)"고 하는 것입니다.

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

    obj1의 a 프로퍼티에 숫자 2를 할당하려고 하면 컴퓨터는 데이터 영역에서 2를 검색합니다. 검색 결과가 없으므로 빈 공간인 @5005에 저장하고, 이 주소를 @7103에 저장합니다. obj1.a = 2 명령 전과 후 변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않았습니다. 즉, 새로운 객체가 만들어진 것이 아니라 기존 객체의 내부 값만 바뀐 것입니다.

    변수 영역

    주소 1001 1002 1003 1004 1005 ...
    데이터 이름: obj1, 값: @5001(변하지 않음!)

    데이터 영역

    주소 5001 5002 5003 5004 5005 ...
    데이터 @7103 ~ ? 1 'bbb' 2

    객체 @5001의 변수 영역

    주소 7103 7104 7105 7106 7107 ...
    데이터 이름: a, 값: @5005(변경됨!) 이름: b, 값: @5004

    중첩된 참조형 데이터(객체)의 프로퍼티 할당도 살펴보겠습니다.

    var obj = {
        x: 3,
        arr: [3, 4, 5],
    };

    변수 영역

    주소 1001 1002 ~ 1004 1005 ...
    데이터 이름: obj, 값: @5001

    데이터 영역

    주소 5001 5002 5003 5004 5005 ...
    데이터 @7103 ~ ? 3 @8104 ~ ? 4 5

    객체 @5001의 변수 영역

    주소 7103 7104 ...
    데이터 이름: x, 값: @5002 이름: arr, 값: @5003

    객체 @5003의 변수 영역

    주소 8104 8105 8106 ...
    데이터 이름: 0, 값: @5002 이름: 1, 값: @5004 이름: 2, 값: @5005
    • 먼저 변수 영역의 빈 공간(@1002)를 확보하고, 식별자를 obj로 지정합니다.
    • 여러 개의 프로퍼티로 이뤄진 데이터 그룹이기 때문에 별도의 변수 영역을 마련하고 그 영역의 주소(@7103 ~ ?)를 @5001에 저장합니다.
    • @7103에 이름 x를, @7104에 이름 arr를 지정합니다.
    • 데이터 영역에서 숫자 3을 검색하고 검색 결과가 없으므로 임의로 @5002에 저장하고 이 주소를 @7103에 저장합니다.
    • @7104에 저장할 값은 배열(데이터 그룹)이기 때문에 별도의 변수 영역을 마련하고 그 영역의 주소(@8104 ~ ?)를 @5003에 저장합니다.
    • 배열의 요소가 총 3개이므로 3개의 변수 공간을 확보하고 각각 인덱스를 부여합니다.
    • 데이터 영역에서 숫자 3을 검색해(@5002) 그 주소를 @8104에 저장합니다. 나머지도 같은 방식입니다.

    이제 obj.arr[1]을 검색하고자 하면 메모리에서는 다음과 같은 검색 과정을 거칩니다.

    • obj 검색 1: obj라는 식별자를 가진 주소를 찾습니다.(@1002)
    • obj 검색 2: 값이 주소이므로 해당 주소로 이동합니다.(@5001)
    • obj 검색 3: 값이 주소이므로 해당 주소로 이동합니다.(@7103 ~ ?)
    • obj.arr 검색 1: arr라는 식별자를 가진 주소를 찾습니다.(@7104)
    • obj.arr 검색 2: 값이 주소이므로 해당 주소로 이동합니다.(@5003)
    • obj.arr 검색 3: 값이 주소이므로 해당 주소로 이동합니다.(@8104 ~ ?)
    • obj.arr[1] 검색 1: 인덱스 1에 해당하는 주소를 찾습니다(@8105)
    • obj.arr[1] 검색 2: 값이 주소이므로 해당 주소로 이동합니다.(@5004)
    • obj.arr[1] 검색 3: 값이 숫자형 데이터이므로 4를 반환합니다.

    이 상태에서 다음과 같이 재할당 명령을 내려보겠습니다.

    obj.arr = 'str';
    • 데이터 영역에 'str'이 존재하지 않으므로 @5006에 'str'을 저장합니다.
    • @7104는 이제 @5006을 가리킵니다.
    • @5003이 더는 참조되지 않으므로 @5003과 @5003이 참조하는 @8104 ~ ?는 가비지 콜렉터의 수거 대상이 됩니다.

    어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수를 참조 카운트라고 합니다. @5003과 같이 참조 카운트가 0인 메모리 주소는 가비지 콜렉터의 수거 대상이 됩니다. 자세한 흐름은 코어 자바스크립트 15페이지를 참고하시기 바랍니다.

📃 변수 복사

var a = 10;
var b = a;

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

변수 영역

주소 1001 1002 1003 1004 ...
데이터 이름: a, 값: @5001 이름: b, 값: @5001 이름: obj1, 값: @5002 이름: obj2, 값: @5002

데이터 영역

주소 5001 5002 5003 5004 ...
데이터 10 @7103 ~ ? 'ddd'

객체 @5002의 변수 영역

주소 7103 7104 ...
데이터 이름: c, 값: @5001 이름: d, 값: @5003

우선 기본형 데이터부터 살펴보도록 하겠습니다.

  • 먼저 변수 영역의 빈 공간 @1001을 확보하고, 식별자를 a로 지정합니다. ← 변수 선언
  • 데이터 영역에서 숫자 10을 검색해(@5001) 그 주소를 @1001에 저장합니다. ← 변수 할당
  • 변수 영역의 빈 공간 @1002를 확보하고, 식별자를 b로 지정합니다.
  • 식별자 a를 검색해 저장값인 @5001을 @1002의 값으로 저장합니다.

다음으로 참조형 데이터를 살펴보도록 하겠습니다.

  • 먼저 변수 영역의 빈 공간 @1003을 확보하고, 식별자를 obj1로 지정합니다.
  • 데이터 영역에서 빈 공간 @5002를 확보하고 데이터 그룹이 담길 별도의 변수 영역(@7103 ~ ?)을 확보해 그 주소를 저장합니다.
  • @7103과 @7104에 각각 식별자 c, d를 지정하고 대입할 값을 데이터 영역에서 검색, 혹은 할당해 저장합니다.
  • 변수 영역의 빈 공간(@1004)를 확보하고, 식별자를 obj2로 지정합니다.
  • 식별자 obj1을 검색해 저장값인 @5002를 @1004의 값으로 저장합니다.

위와 같이 복사 과정은 동일하지만, 데이터 할당 과정에서 차이가 있기 때문에 복사 이후의 동작에는 큰 차이가 발생합니다.

  1. 객체의 프로퍼티 변경

    b = 15;
    obj2.c = 20;

    변수 영역

    주소 1001 1002 1003 1004 1005 ...
    데이터 이름: a, 값: @5001 이름: b, 값: @5004(변경됨!) 이름: obj1, 값: @5002 이름: obj2, 값: @5002(변하지 않음!)

    데이터 영역

    주소 5001 5002 5003 5004 5005 ...
    데이터 10 @7103 ~ ? 'ddd' 15 20

    객체 @5002의 변수 영역

    주소 7103 7104 ...
    데이터 이름: c, 값: @5005(변경됨!) 이름: d, 값: @5003

    b = 15 명령 먼저 살펴보겠습니다. 데이터 영역에 아직 15가 없으므로 새로운 공간 @5004에 저장하고 그 주소를 든 채로 변수 영역에서 식별자 b를 찾아 값에 @5004를 저장합니다. 그 다음 obj2.c = 20 명령을 살펴보면, 데이터 영역에 아직 20이 없으므로 새로운 공간 @5005에 저장합니다. 그 주소를 든 채로 변수 영역에서 obj2를 찾고, obj2의 값인 @5002가 가리키는 변수 영역에서 다시 c를 찾아(@7103) @5005를 대입합니다. 기본형 데이터를 복사한 변수 b의 값을 바꿨더니 @1002의 값이 달라진 반면, 참조형 데이터를 복사한 변수 obj2의 프로퍼티의 값을 바꿨더니 @1004의 값은 달라지지 않았습니다.

    즉, 변수 a와 b는 서로 다른 주소를 바라보게 됐으나, 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있는 상태입니다. 이 결과가 바로 기본형과 참조형 데이터의 가장 큰 차이점입니다.

    a !== b;
    obj1 === obj2;
  2. 객체 자체를 변경

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

    변수 영역

    주소 1001 1002 1003 1004 1005 1006 ...
    데이터 이름: a, 값: @5001 이름: b, 값: @5004 이름: obj1, 값: @5002 이름: obj2, 값: @5006(변경됨!)

    데이터 영역

    주소 5001 5002 5003 5004 5005 5006 ..
    데이터 10 @7103 ~ ? 'ddd' 15 20 @8204 ~ ?

    객체 @5006의 변수 영역

    주소 8204 8205 ...
    데이터 이름: c, 값: @5005 이름: d, 값: @5003

    1번 예제는 변수 값(b)을 직접 변경할 때와, 값이 아닌 내부 프로퍼티(obj2.c)를 변경할 때의 결과를 비교한 것입니다. 이번에는 b의 경우와 마찬가지로 obj2에도 새로운 객체를 할당함으로써 값을 직접 변경했습니다. 그러면 메모리의 데이터 영역의 새로운 공간에 새 객체가 저장되고 그 주소를 변수 영역의 obj2 위치에 저장하기 때문에 객체에 대한 변경임에도 값이 달라졌습니다. 애초에 비교 대상의 조건 자체가 다르니 동작도 다른 것이 당연합니다. 즉, 참조형 데이터가 가변값이라고 설명할 때의 가변은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립합니다.

    obj1 !== obj2;

💬 마치며

많은 자바스크립트 책에서 "기본형은 값을 복사하고 참조형은 주솟값을 복사한다"고 설명하고 있지만 사실은 어떤 데이터 타입이든 변수에 할당하기 위해서는 주솟값을 복사해야 하기 때문에, 엄밀히 말하면 자바스크립트의 모든 데이터 타입은 참조형 데이터라고 할 수 있으나, 기본형은 주솟값을 복사하는 과정이 한번만 이뤄지고, 참조형은 한 단계를 더 거치게 되는 차이가 있습니다. 일반적으로 "기본형도 결국 주솟값을 참조한다"는 사실을 소개하지 않는 이유는 이해하기에 다소 어려움이 있기 때문이라고 추측됩니다. 그러나 이런 내부 원리를 잘 이해하는 것은 향후 성장하는 과정에서 더 큰 혼란을 느끼지 않기 위한 중요한 초석입니다.