[JS] 원시값과 참조값의 차이가 뭔가요?

Muru·2024년 11월 4일
post-thumbnail

왜 이건 안되고

const num = 1;
num = 2;

이건 되는거임?

const arr = [1];
arr[0] = 2;

이런 질문들이 있을경우 다음 내용을 들여다 보자.

1. 원시값

원시 타입의 값 : 원시 값은 변경 불가능한 값. 한번 생성된 값은 읽기 전용 값으로서 변경할 수 없다. 이떄 원시 타입은 숫자, 문자열, 불리언, null, undefined, 심벌 타입이 있다.

그런데 이상하지 않은가?

let age = 20;
age = 21;

이런식으로 변경을 해왔는데 값을 변경할 수 없다니 무슨소린가???
여전히 맞는말이다. "값"자체는 변경 할 수 없고 불변한 속성을 가진다.
다만 재할당을 할 경우 변수는 메모리 자체를 이동하여 값을 새로 할당한다.

변수와 값을 할당하여 코드를보자.

myHome(변수)의 메모리 주소가 001 이라고 하고 4명(값)이 산다고 해보자

let myHome = 4; 

출산을 통하여 5명이되어 5로 할당했다.

myHome = 5;

그렇다면 myHome(변수)의 메모리주소는 여전히 001이고 값만 변경되었을까?
아니다. 출산을 통해 값이 변경이되었다면 이사를 가야 한다.
메모리주소 003(임의의값)으로 이동하여 5를 새로 할당해준다.

만약 임의의 변수에 대한 값을 다른 변수에 할당한다면 해당 변수는 어떻게 할당이 되는걸까? => 다른 메모리 주소에서 "값"만 복사를해와서 할당된다.

let origin = 100;
let copy = origin; // copy는 origin과 다른 메모리 주소에 "값"만 복사해왔다.
console.log(copy)  // 100


실제 값이 복사되어 전달되는것을 값에 의한 전달 이라고 한다.

참고로 const 키워드로선언한다면 해당 변수는 재할당이 불가능하다.

const num = 2;
num = 3; // 문법 에러 재할당 불가

2. 참조값

참조 값 : 생성된 객체가 저장된 메모리 공간의 주소 그 자체를 저장.
참조값은 가변성이 있다. 엄밀히 말하면 값의 내부속성이 가변성이 있는것이다.
참조타입은 Object, Array, Function등이 있다.

이때 반드시 오해하지 말아야할것이 있다.
가변성을 가지는것은 어디까지나 값(객체)의 내부속성을 의미한다.
원시값과 마찬가지로 값 자체에는 불변성을 가진다.
값의 내부속성은 변수의 메모리주소 변경없이 언제든지 바꿀수 있다


값의 내부속성은 메모리주소 변경없이 언제든지 바뀔 수 있다.
let ob1 = { name : "Kim"};
ob1.name = "Seung";
console.log(ob1.name) // Seung

	   ob1의 참조값에대한 내부속성을 변경해도 ob1자체의 메모리주소는 그대로다.

아예 새로운 객체로 재 할당 하는 경우는 원시값에서 그랬던것처럼 메모리 주소 변경이 발생한다.

let ob1 = { name : "Kim"};			// ob1의 메모리주소 001(임의의값)
ob1 = { name : "Seung", age :21 };	// ob1의 메모리주소는 002로 이동

새로운 객체로 재 할당 하는 경우는 매우 드물긴하다..




참조값을 가지는 변수도 const 키워드를 가질경우 새로운 참조값으로 재할당하는것 역시불가능하다.
const ob1 = { name : "Kim"};			// ob1의 메모리주소 001(임의의값)
ob1 = { name : "Seung", age :21 };		// 문법에러


const 키워드를 가지는 변수가 참조값을 가질때 해당 참조값의 내부속성을 변경하는것은 가능하다

const ob1 = { name : "Kim"};			// ob1의 메모리주소 001(임의의값)
ob1.name = "Seung"		
console.log(ob1.name) 					// Seung

여기서 우리가 const 키워드로선언한 배열이나 객체의 내부 요소들을 변경할 수 있는 이유다. 참조값을 통한 내부 속성을 바꾸는것이기 때문이다.

즉 const 키워드로 선언한 원시값과 참조값이 있을때 원시값의 재할당은 불가능하지만, 참조값은 간접적으로 변경이 가능한것이다.

3. 참조에 의한 공유

다음 코드의 실행 결과를 예측해보자

let ob1 = { name : "Kim"};  
let ob2 = ob1;
ob2.name = "끼얏호 바꿔버리기"
console.log(ob1.name);

콘솔 결과는 "끼얏호 바꿔버리기"가 나온다. ob1과 ob2는 서로 같은 메모리 주소를 가진다.
다수의 변수가 하나의 동일한 메모리 주소를 가리키는 상황을 참조에 의한 공유라고 한다.

4. 참조에 의한 공유가 싫어요!

4.1 : 얕은 복사와 깊은 복사

참조에 의한 공유가 싫어서 독립적인 속성을 띄고싶을 경우 어떻게 해야할까?

이럴땐 스프레드 연산자를 사용해보자.

// 종속적이지 않게하는 방법 !! 스프레드연산자를사용해보자
// 깊은 복사가 되었다.
let ob1 = { name : "Kim"};  
let ob2 = {...ob1}
ob2.name = "끼얏호 바꿔버리기"
console.log(ob1.name);

이렇게 모두 독립적일경우 "깊은 복사가 되었다". 라고 한다.

하지만 문제가 있다. 특정 상황에서 종속적이게 되버리는 경우가 있는데, 참조값 내부에 참조값이 중첩하여 존재할경우 해당 내부 참조값은 참조에 의한 공유가 발생한다. 즉 해당 내부 참조값은 복사될때 서로 공유된다. 이럴때 "얕은 복사가 되었다" 라고 한다

// 얕은 복사가 되었다.
let ob1 = { name : "Kim", addr : {first : "Seoul", second : "Suwon"}};  
let ob2 = {...ob1}
ob2.name = "끼얏호 바꿔버리기"
ob2.addr.first = "jeju"

console.log(ob1.name);          // Kim, 최상위 속성은 독립적이지만
console.log(ob1.addr.first)     // jeju, 내부에 또 참조값이 있을경우 해당 값은 참조에의한공유가 발생한다.

그러면 중첩 참조값이 존재할경우 깊은복사를 하는 방법이 무엇일까? 직접 재귀함수로 구현할 수는 있지만 쉬운 방법으로 세가지가 존재한다.

JSON.parse()
_.cloneDeep()
structuredClone()

그중에서 structuredClone()만 알아보자. 물론 해당 내장 함수가 만능인것은 아니다.
제약 조건(함수와 클래스 복사불가, 심볼 복사불가)도 존재한다.

// 중첩 참조값이 있음에도 불구하고 깊은 복사가 발생하였다.
let ob1 = { name : "Kim", addr : {first : "Seoul", second : "Suwon"}};  
let ob2 = structuredClone(ob1);
ob2.name = "끼얏호 바꿔버리기"
ob2.addr.first = "jeju"

console.log(ob1.name);         // Kim
console.log(ob1.addr.first)    // Seoul
profile
Developer

0개의 댓글