javaScript/jsDeepDive

primitive타입, object타입 mutable(변경가능함)

부엉이사장 2023. 5. 13. 21:13

원시타입은 흔히 우리가 변수에 할당하는 스트링 넘버 등이 있고,

객체타입은 말그대로 객체타입이다. 어레이, 객체, 함수등이 있다.

원시타입과 객체타입은 근본적으로 메모리접근,참조방법에서 다르다.

 

 

# 원시타입의 메모리 활용방법

원시타입인 값은 그냥 뭐 스트링 숫자 이런거다..

뭐 이전 포스팅에서 오지게 설명했던 부분이다.

변수(식별자)는 메모리주소랑 연결되어있고 이 메모리주소는 실제 데이터가 들어있는 공간에 접근하여 데이터를 가져온다.

 

 

# 원시타입은 immutable(변경불가능한)값이다!!

let value = 'muzzi';
value = 'dori';

예시로 이 코드에서 메모리공간이 활용되는 것을 보자.

참고로 우리집 개들 이름이 무찌랑 도리다.. 앞으로 자주보게될거다.. 

맨처음 value를 선언할때 value랑 연결된 메모리주소1가 가르키는 메모리에는 undefined가 담겨진다(초기화)

그다음 value = 'muzzi'코드가 실행되어 메모리 주소 2랑 식별자 value가 연결되고, 이 메모리에는 'muzzi'라는 스트링 데이터가 담겨있다.

다시 value = 'dori'라는 코드가 실행되어 메모리주소 3에 식별자 value가 연결되고, 이 메모리에는 'dori'라는 스트링데이터가 담겨있다.

 

그렇다면 저 그림에서 최종적으로 value 식별자가 가르키는 메모리주소3에 연결된 'dori'데이터가 있을때 (코드 최종부분) 메모리주소1과 메모리주소2에 연결되어있던 메모리 undefined와 'muzzi'는 사라질까?

최종적으로 value에는 'dori'가 할당된다. 

하지만 식별자 value에 연결된 메모리주소1과 메모리주소 2가 끊기더라도 이곳에 담겨있던 undefined와 'muzzi'데이터는 사라지지않는다. 때문에 primitive타입(숫자, 스트링 등등)을 바로 immutable(변경불가능한) value라고 한다.

 

그냥 우리가 코드상으로 쉽게 생각할때 value의 값은 처음에 undefined => 'muzzi' => 'dori'로 두 번이나 변경이 되었다고 느끼지만, 정확히는 이 값들이 사라지지않고 최종적으로 'dori'라는 데이터가 들어있는 메모리주소3이랑 식별자 value가 연결이 되어버린거다. 자바스크립트 코드상 이 메모리 주소에 각각있는 undefined, 'muzzi', 'dori'라는 데이터는 우리가 컴퓨터 메모리를 뜯어서 메모리에 네오디늄자석을 존나 비비지 않는이상 변하지않는다. 코드상으로 메모리주소에 직접 접근해서 변경도 불가능하게 해놨다. (애초에 이 메모리주소를 명시해서 read하는것도 불가능함..)

그래서 자바스크립트가 안전한것이다. c언어같은건 가능하다고하는데 안해봐서 모르겠음..

 

그렇다면 저 메모리공간이 계속 영원한것도 아니다. 저렇게 연결이 끊겨서 아예 사용도 못하고 접근조차 못하는 값들을 garbage값, 즉 쓰레기값이라고 하는데, 자바스크립트 엔진이 garbage collector로 언젠간 이걸 지워준다.

 

 

# 원시값이 immutable값인 이유

위에 그림에서 한 메모리공간에 있는 값은 변하지 않는다. 이 공간에 있는 값은 'read only'인 읽기전용 값이다.

value 변수(식별자)에 연결된 메모리 공간의 주소가 바뀌는것이지 저 메모리에 각각 저장된 값들은 한번 입력되고 변경,수정,삭제가 되지 않는다. 자바스크립트 코드로도 바꿀수 없다.

때문에 원시값은 immutable값이라고 하는것이다.

 

 

 

# 객체타입의 메모리 활용방법

객체타입은 말그대로 객체이다. object는 당연하고 자바스크립트에서 array도 객체이다. 또한 함수도 객체다.

객체타입의 메모리공간 활용은 원시타입과 명백히 다르다.

맨처음 obj를 선언할때 초기화된 메모리주소 1에 연결이 된다.

그다음 {dog1 : 'muzzi', dog2 : 'dori'}라는 값을 obj변수에 할당할때, 위 그림처럼 메모리주소2에 obj가 연결된다. 에 메모리주소2에 연결된 메모리 데이터에는 메모리주소3이 담겨있고 이 메모리주소 3에 실제적인 객체데이터 {dog1 : 'muzzi', dog2 : 'dori'}가 들어있다.

 

이걸 명확히 알 수 있는 방법이 있다.

const obj = {dog1 : 'muzzi', dog2 : 'dori'};
obj_copied = obj

이렇게 객체를 선언 할당하고 새로운 변수(식별자) obj_copied에 그대로 복사하였다. 이 코드는 어떻게 작동될까?

obj_coied라는 식별자에 복사되어 할당되는값은 객체데이터 {dog1 : 'muzzi', dog2 : 'dori'}이게 아니라, obj식별자에 연결된 메모리주소2에 있는 메모리주소3데이터가 복사가 된다. 

 

 

그럼 참조할땐 이렇게 작동한다.

obj를 참조할때는 식별자 obj에 연결된 메모리주소2의 메모리공간에 있는 '메모리주소3'에 접근한다. 그다음 메모리주소3에 있는데이터 {dog1 : 'muzzi', dog2 : 'dori'}데이터에 접근한다.

obj_copied를 참조할때는 식별자 obj_copied에 연결된 메모리주소4의 메모리공간에 있는 '메모리주소3'에 접근한다. 그다음 메모리주소3에 있는데이터 {dog1 : 'muzzi', dog2 : 'dori'}데이터에 접근한다.

 

이를 명확히 알 수 있는 코드가 있다.

const obj = {dog1 : 'muzzi', dog2 : 'dori'};
obj_copied = obj

obj.member3 = 'cockroach';

console.log(obj);
console.log(obj_copied);

위 코드는 obj라는 객체에 member3이라는 키를갖는 프로퍼티를 추가하였다.

중요한건 우리는 obj_copied에는 이 프로퍼티를 추가하지 않았다.

그럼 obj를 참조할때, obj_copied를 참조할때 어떻게 메모리에서 데이터를 가져올까?

어느시점부터 집에 바퀴벌레가 생겼다..

위그림의 obj와 obj_copied에는 메모리주소x가 연결되어있고, 이 메모리주소x에는 새로 프로퍼티가 추가된 {dog1 : 'muzzi', dog2 : 'dori', member3 : 'cockroach'}라는 객체데이터가 있다.

때문에 우리는 분명히 obj_copied가 아닌 obj에만 새로운 프로퍼티를 추가했지만, 참조할때는 같은 메모리공간에 있는 {dog1 : 'muzzi', dog2 : 'dori', member3 : 'cockroach'} 데이터를 가져오게된다.

때문에 콘솔로 각각 찍어보면

console.log(obj); //바퀴벌레가 추가된 객체를 가져옴
console.log(obj_copied); //바퀴벌레가 추가된 객체를 가져옴

둘다 같은 객체데이터를 가져오게된다.

 

+ 메모리주소 x?

객체데이터의 각 프로퍼티에 또 메모리주소가 연결된다고는 어렴풋이 들었는데 그림으로 표기하기엔 일단 복잡하고.. 엔진마다 다르다고 한다. 그래서 내가 생각한 구조는..

이렇게 obj에 연결된 메모리에는 각각 key1과 key2가 있고 이 프로퍼티의 키값을 식별자로 치는 각각의 메모리공간이 있는지 질문을 했었음.. 근데 이건 뭐 확답을 못들었다.. 위그림이 정확하지 않을수도있으니 그냥 흘려들어도 된다.

 

 

# 각각 선언된 객체는 똑같이 생겼어도 서로 다른 값이다.

위 메모리 구조를 이해했다면 이 파트도 어렵지 않은 내용이다.

const obj1 = {};
const obj2 = {};

obj1과 obj2는 서로 똑같이 생긴 빈 객체이다.

그렇다면 

console.log(obj1 === obj2);

는 뭐가뜰까? 상식적으로 일치연산자 ===는 타입과 값을 모두 비교하는데 타입은 객체타입, 값은 그냥 빈객체이므로 true가 떠야한다고 생각한다.

 

메모리 구조상으로 생각하면 위 코드는 그림으로 이렇게 나타난다.

obj1 식별자는 메모리주소1에 연결되어있고 이 메모리주소1의 메모리공간에는 메모리주소2가 담겨있다. 그리고 이 메모리주소2의 메모리공간에는 빈객체 {}이 담겨있다.

obj2 식별자는 메모리주소3에 연결되어있고 이 메모리주소3의 메모리공간에는 메모리주소4가 담겨있다. 그리고 이 메모리주소4의 메모리공간에는 빈객체 {}이 담겨있다.

 

따라서 obj1 === obj2를 비교했을때는 비교가 이렇게 된다\

obj1식별자에 연결된 메모리주소1의 메모리공간의 '메모리주소2' 데이터와

obj2식별자에 연결된 메모리주소3의 메모리공간의 '메모리주소4'만 비교하기떄문에 obj1===obj2는 false가 뜬다. 즉 연결된 객체데이터까지 안가고 단순 데이터, '메모리주소2', '메모리주소4'만 비교하기때문이다. 때문에 데이터는 똑같이 생겼지만 이 두가지 객체는 다른 객체이다.

 

 

 

# 그렇다면 원시값의 비교는?

const value1 = 'data';
const value2 = 'data';

value1의 식별자에 연결된 메모리주소1에 있는 데이터는 'data'이다.

value2의 식별자에 연결된 메모리주소2에 있는 데이터는 'data'이다.

 

console.log(value1 === value2);

위 코드는 value1의 식별자에 연결된 메모리주소1에 있는 데이터 'data'와 value2의 식별자에 연결된 메모리주소2에 있는 데이터 'data'를 비교하기때문에 이 비교는 true가 뜬다.

 

 

 

 

 

# 따라서 object타입의 데이터는 mutable 값이다!

const obj = {dog1 : 'muzzi', dog2 : 'dori'};

아까 예시에서 들었던 이 코드는 분명히 const로 변경불가능하게 선언을 하였지만

obj.dog1 = 'muzzi_clone'

코드로 프로퍼티에 접근하여 프로퍼티값을 변경/추가/삭제가 가능하다.

사실 이걸 정확히 이해하려면 위에서 프로퍼티 키를 식별자로하는 데이터 접근구조를 알아야하긴하다.. 

 

# 값에의한 전달, 참조에의한 전달

const value1 = 'data';
const value2 = value1;

위 코드처럼 원시값을 다른 변수에 담을때는

위 그림처럼 메모리안에있는 '값'을 복사를 하기때문에 이것을 '값에 의한 전달'이라고 한다.

 

반대로 객체를 복사할경우

const obj = {dog1 : 'muzzi', dog2 : 'dori'};
const obj_copied = obj;

위코드는 obj라는 객체를 obj_copied에 복사하는 코드다. 

이렇게 객체데이터를 다른 변수에 담을때는,

위 그림처럼 실제 객체데이터가 들어있는 메모리주소2(참조값)를 복사를한다.

이것을 참조에 의한 전달이라고 한다.

 

 

사실 이 값에의한전달, 참조에의한 전달이라는 용어는 이크마스크립트의 정식용어는 아니란다..

 

 

 

# 함수는 객체다. (번외)

함수는 객체다

함수문법을 배울때 대충 이런설명을 듣긴했지만 실제로 코드로 찍어보진 않았다.

이번에 코드를 찍어보자.

 

const fuc = () => {
  console.log("하잉");
};

이렇게 fuc라는 함수를 만들었다.

함수가 객체라면 함수에 프로퍼티를 추가할수 있지 않을까?

fuc.test = "hello";

이렇게 함수에다가 test를 키로 갖는 프로퍼티를 추가했다.

그럼 콘솔로 찍어볼까?

console.log(fuc.test); //hello 콘솔찍어줌

이렇게 함수를 객체로 생각하고 프로퍼티를 참조하면 객체처럼 동작한다.

 

이번포스팅에서 설명한 객체접근방식도 적용이 되겠지?

const fuc = () => {
  console.log("하잉");
};
let copy = fuc;

fuc.test = "hello";

console.log(fuc.test); //hello 콘솔찍어줌
console.log(copy.test); //hello 콘솔찍어줌

위 코드는 fuc라는 함수를 만들고 이 함수를 copy라는 식별자에 복사하였다.

그렇다면 위에 적었던것처럼 참조에의한전달로 객체의 참조값이 복사되어서 copy에 담겼을거다.

그 후 fuc에 프로퍼티 test : 'hello'를 추가했다. 여기서 중요한건 copy라는 함수에는 이 프로퍼티를 추가하지 않았다는것이다.

그리고 콘솔로그로 각각 찍어보면 둘다 프로퍼티 test : 'hello'가 담겨있다. copy에 추가하지 않은 프로퍼티가 담겨있는것이다.

이 말인 즉슨 함수는 객체고 함수의 복사도 객체의 복사랑 동일하게 작동한다는것이다.

 

 

# 최종적으로.

객체는 이렇게 참조에 의한 전달로 작동된다. 

자바스크립트로 코딩을 하다보면 객체는 필수적으로 사용하게되는데 이렇게 객체들이 수정 및 복사등등을 할때마다

각각의 데이터로 작동하게된다면 메모리 소모가 엄청 크게된다.

때문에, 이해하기어려운 구조적인 단점이 있지만 이걸 감안하고 이렇게 작동하게되는것이다.