원시 값과 객체의 비교

솜사탕·2023년 5월 2일
1

JavaScript

목록 보기
7/23

데이터 타입

자바스크립트가 제공하는 타입은 총 7가지 이며 크게 "원시 타입" 과 "객체 타입"으로 구분한다.
(숫자, 문자열, 불리언, null, undefined, 심벌(ES6), 객체 타입)

원시 타입이란 ?

원시 값은 변경 불가능한 값

원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 저장

객체 타입이란?

객체는 변경 가능한 값

객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값이 저장된다.


즉 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 "원시 값이 복사되어 전달"되며 이를 "값에 의한 전달"이라 하며 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 "참조 값이 복사되어 전달"되고
이를 "참조에 의한 전달"이라 한다.

원시 값

말 그대로 변경 불가능한 값을 말한다.

아래 예제를 보자

let num = 10;
num = 15;

console.log(num) // 15

변경이 잘만 되는데 왜 변경이 불가능한 값이라고 하는걸까 ?
여기서 말하는 원시 값은 변수를 향한 원시 값을 말하는게 아닌
변수에 할당되는 자체의 그 값을 말한다.
즉 10, 15를 원시 값이라 한다.

만일 10, 15를 다른 숫자인 1, 5로 대체하거나 그 값 자체를 변경할수 없다.

즉 말 그대로 변경 불가능한 값을 말한다는 부분에 잘 짚고 넘어가야 할 부분이

변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 메모리 공간을 식별하기 위해 붙인 이름이며
값은 변수에 저장된 데이터로서 표현식이 평가되어 생성된 결과를 말한다.

즉 여기서 말한 원시 값은 변수를 말하는게 아닌 값에 대한 진술이다.

그로 인해 원시 값은 변경 불가능한 값, 읽기 전용 값이며, 어떤 일이 있어도 불변하기 때문에
원시 값의 특성은 데이터의 신뢰성을 보장한다.

원시 값을 할당한 변수에 새로운 원시 값을 재할당하면 메모리 공간에 저장되어 있는 재할당 이전의 원시 값을 변경하는 것이 아닌 새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후, 변수는 새롭게 재할당한 원시 값을 가리킨다.
이때 변수가 참조하던 메모리 공간의 주소가 바뀐다.

var score; // undefined > 0x000000F2(메모리 주소)
score = 80; // 80(할당) > 0x00000A2A2(메모리 주소)
score = 90; // 90(재할당) > 0x0000C777(메모리 주소)

메모리 주소가 변경되는 이유는 할당된 저 값들이 원시 값이므로 변경 불가능한 값이기 때문에 메모리 주소가 변경된다.

만일 원시 값이 변경 가능한 값이라면 변수에 새로운 값을 할당 했을때, 변수가 가리키던 메모리 주소는 공간의 주소를 바꿀 필요 없이 원시 값 자체를 변경하면 그만이다.
이렇게 되면 변수가 참조하던 메모리 공간의 주소가 바뀌지 않는다.

var score; // undeifned > 0x00000012345(메모리 주소)
score = 50; // 50(할당) > 0x00000012345(메모리 주소)
score = 100; // 100(재할당) > 0x00000012345(메모리 주소)

다만 원시 값은 변경 불가능한 값이기 때문에 직접 변경할수 없으며, 원시 값을 재할당할 경우 새로운 메모리 공간을 확보하고 재할당한 값을 저장 후 변수가 참조하던 메모리 공간의 주소를 변경하는데

이러한 특성을 "불변성" 이라고 한다.

문자열과 불변성

원시 값을 저장하려면 먼저 확보해야하는 메모리 공간의 크기를 결정해야 한다.
이를 위해 원시 타입별로 메모리 공간의 크기가 미리 정해져 있으며
ECMAScript 사양에 문자열 타입(2바이트) 숫자 타입(8바이트) 이외의 원시 타입은 크기를 명확히 규정하고 있지 않다.

원시 값인 문자열은 다른 원시 값과 독특한 특징이 있는데, 숫자 값은 1 ~ 9999 까지도 동일한 8 바이트가 필요하지만
문자열은 1개의 문자당 2바이트 공간을 필요로 하기 때문에 50개의 문자로 이뤄진 문자열은 100 바이트가 필요하다

원시 타입의 문자열 예시를 한번 보자

var text = "안녕하세요";
text[0] = "헐";

console.log(text) // 안녕하세요

문자열은 유사 배열 객체 이면서 배열처럼 인덱스를 통해 각 문자에 접근할수 있으며
length 프로퍼티를 갖기 때문에 유사 배열 객체이고, for 문으로 순회할수도 있다.

위 예제를 보았듯 text 변수에 이미 생성된 문자열의 일부 문자를 변경해도 반영이 되지 않는데
이는 문자열은 변경 불가능한 값이기 때문이다.
즉 한번 생성된 문자열은 읽기 전용 값이므로 변경할 수가 없다.

햇갈리지 말자 !
변수에 새로운 문자열을 재할당하는 것은 물론 가능하다 !

값에 의한 전달

아래 예제를 보자

var score = 80;
var copy = score;

console.log(score); // 80
console.log(copy); // 80

score = 100

console.log(score === copy) // true

score 변수에 숫자 값 80을 할당하고
copy 변수에 score 변수를 할당 했다.

copy = score에서 score는 변수 값 80으로 평가되기 떄문에 copy 변수에도
80이 할당 된다.
이때 새로운 숫자 값 80이 생성 되어 copy 변수에 할당 된다.
즉 변수에 원시 값을 갖는 변수를 할당하면 할당 받는 변수(copy)에는 할당 되는 변수(score)의 원시 값이 복사되어 전달 된다.
이를 "값에 의한 전달" 이라 한다.

이때 각 변수의 숫자 값 80을 갖는다는 점은 동일하지만
각 변수의 값 80은 다른 메모리 공간에 저장된 별개의 값이다.

var score = 80 // 0x0000012(메모리 주소)
var copy = score; // 0x000A1400(메모리 주소)
정리하자면 새로운 80을 생성 해서 메모리 주소를 전달한다.
즉 이 방식은 할당 시점에 두 변수가 기억하는 메모리 주소가 다르다.
(score, copy 변수를 보듯이 서로 메모리 주소가 다르다)

이처럼 "값의 의한 전달"도 사실은 값을 전달하는 것이 아니라
메모리 주소를 전달하며,
전달된 메모리 주소를 통해 메모리 공간에 접근하면 식별자의 값을 참조할 수 있다.

메모리 주소가 서로 다르기 때문에, 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할수 없다.

!참고사항
"값의 의한 전달" 용어는 ECMAScript 사양에는 등장하지 않으며
딥다이브에선 자주 사용하는 "값에 의한 전달"과 "참조에 의한 전달"이라는 용어를 사용하지만 "공유에 의한 전달"이라고 표현하는 경우도 있다.

객체

객체란 무엇일까?

객체는 프로퍼티의 개수가 정해져 있지 않으며, 동적으로 추가되고 삭제할수 있다.
또한 프로퍼티의 값에도 제약이 없음

변경 가능한 값

객체(참조) 타입의 값,
객체는 변경 가능한 값이다.

아래 예제를 보자

ver person = { // person 메모리주소 : 0x0000123
  name: "Lee",
};

원시 값을 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 원시 값에 접근할 수 있다.
즉, 원시 값을 할당한 변수는 원시 값 자체를 값으로 갖는다.
주소를 통해 메모리 공간에 접근하면 참조 값에 접근할수 있다.

그래서 이를 "참조 값"이라고 한다.
참조 값을 통해 객체에 접근할수 있다.

// 할당이 이뤄지는 시점에 객체 리터럴이 해석되고, 그 결과 객체가 생성된다.
var person = {
  name: "Lee"
};

// person 변수에 저장되어 있는 참조 값으로 실제 객체에 접근한다.
console.log(person); // {name: "Lee}

객체를 할당한 변수의 경우 "변수는 객체를 참조하고 있다" 또는 "변수는 객체를 가리키고 있다"라고 표현한다.

위 예제는 person 변수는 {name:"Lee"}를 참조하고 있다.

원시 값은 변경이 불가능한 값이지만
객체는 변경이 가능한 값이다.
따라서 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할수 있으며
프로퍼티를 동적으로 추가할 수도 있고, 프로퍼티 값을 갱신할 수도 있다.
또한 프로퍼티 자체를 삭제도 가능하다.

// 프로퍼티 값 갱신
person.name = "Kim;

// 프로퍼티 동적 생상
person.address = "Seoul";


console.log(person); // {name: "Kim", address: "Seoul"}

여기서 새로운 점이 있는데,
원시 값은 불가능한 값이기 때문에 변수의 값을 변경하려면 재할당을 통해 메모리에 원시 값을 새롭게 생성해야 한다.
(메모리 주소가 변경됨)

다만 객체는 변경이 가능한 값으로 메모리에 저장된 객체를 직접 수정할수 있기 때문에 메모리 주소가 동일하다.

얕은 복사 깊은 복사

객페를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계 까지만 복사하는 것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 말한다.

// 얕은 복사
const o = { x: { y: 1 } };

const c1 = { ...o }
console.log(c1 === o) // false
console.log(c1.x === o.x); true

const _ = require("lodash");

// 깊은복사
const c2 = _.cloneDeep(o);

console.log(c2 === o); // false
console.log(c2.x === o.x) // false

얕은 복사와 깊은 복사로 생성된 객체는 원본과는 다른 객체다.
즉 원본과 복사본은 참조 값이 다른 별개의 객체다.

하지만 얕은 복사는 객체에 중첩되어 있는 객체의 경우 참조 값을 복사하고 깊은 복사는 객체의 중첩되어 있는 객체까지 모두 복사해서 원시 값처럼 완전한 복사본을 만든다는 차이가 있다.

참조에 의한 전달

여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것이 무엇을 의미하는지, 이로 인해 어떤 부작용이 발생 하는지 확인해 보자.

var person = { // person 메모리주소 (0x000123)
  name: "lee"
};

//참조 값을 얕은 복사로
var copy = person; // copy 메모리 주소 (0x005555)

{ name: "lee" } // 메모리 주소 (0x77777)

원본 person을 사본 copy에 할당하면 원본 person의 참조 값을 복사해서 person에 저장한다.

이때 person과 copy의 메모리주소는 다르지만 동일한 참조 값을 갖는다.
즉 person, copy는 모두 동일한 객체를 가리키며
이것은 두 개의 식별자가 하나의 객체를 공유한다.
따라서 원본 또는 사본 둘중 한쪽에서 객체를 수정할 경우
서로 영향을 주고 받는다.

var person = { // person 메모리주소 (0x000123)
  name: "lee"
};

//참조 값을 얕은 복사로
var copy = person; // copy 메모리 주소 (0x005555)

{ name: "lee" } // 메모리 주소 (0x77777)

// copy와 person은 동일한 메모리 주소의 객체를 가르킨다
console.log(copy === person) // true

// 두 식별자의 객체 내부 수정
copy.name = "Kim;
person.address = "Seoul"


console.log(person, copy) // {name: "Kim", address: "Seoul"}

결국 "값에 의한 전달"과 "참조에 의한 전달"은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 면에서 동일하다

다만 그 값이 원시 값 or 참조 값 이 차이 뿐
또한 자바스크립트에선 "참조에 의한 전달"은 존재하지 않고
"값에 의한 전달"만이 존재한다고 말할 수 있다.

ECMAScript사양에는 등장하지 않기 때문이다.

profile
공부공부공부공부공부공부

0개의 댓글