JavaScript

11. 객체(원시값과 객체 비교)

문정훈 2022. 5. 5. 15:46

1. 객체 리터럴 

1) 자바스크립트의 객체

● js에서는 원시값을 제외하고 함수, 배열, 정규 표현식 등 js의 모든 것은 객체로 이루어져있다. 

객체는 프로퍼티와 메소드로 구성된 집합체이다. 

객체 리터럴은 값으로 평가가 될 수 있는 표현식이다!

var person = {
    num : 0, 
    increase : function() {
    	this.num++;
    } 
};

js에서 객체와 함수는 밀접한 관계를 가지는데 함수로 객체를 생성(생성자 함수)하거나 함수 자체가 객체이다.(일급 객체)

 

● 메서드와 함수 구분

js에서 함수는 일급 객체이다. 함수는 값으로 취급되기 떄문에 객체의 프로퍼티의 값으로 사용될 수 있다.

프로퍼티 값이 함수인 경우 이를 메서드라고 한다. 이는 일반 함수와 구분해서 부르는 용어이다. 


 

2. 원시값과 객체 리터럴 비교

1) 원시 값(불변성)

● 원시 값을 저장한 표현식이 메모리에 어떻게 저장되고 표현식은 무엇을 가리키는가?

var score;
score = 80;
score = 90;

처음 위 코드가 평가 과정을 거치면 score에는 undefined가 할당된다. 그리고 런타임 시 score에 80이라는 값을 할당하는데 이때 새로운 메모리 주소 공간에 숫자 리터럴 80(2진 형태)을 저장한다. score라는 식별자에는 무엇을 가리키나?

score는 0x00001332라는 숫자 리터럴 80이 저장된 메모리의 주소를 가리킨다. 즉 score에는 80이 할당되는 것이 아니다.

 

이후 score에 90을 재할당한다면 기존의 숫자 리터럴 80을 저장한 0x00001332 메모리 주소 공간의 값을 바꾸는 것이 안라 새로운 메모리 주소 공간에 90을 할당하고 해당 메모리 주소를 score라는 식별자가 가리키게 된다. 

이 처럼 원시 값은 불변성의 특징을 가진다.

 

 

● 불변성이란?

원시 타입 값은 메모리에 한번 저장되면 변경 불가능한 불변성의 특징을 가진다. 즉 한번 생성된 원시값은 읽기 전용으로 변경하지 못한다. 

 

 

2) 원시 값 전달(call by value)

● call by value 

var score = 80;
var copy = score;
score = 100;

call by value, call by reference 두 용어는 c언어에서 다루는 용어이다. js에서는 이 두 용어를 사용하는 것이 올바르지 않다. 위 내용을 살펴보면, 

score에 80을 할당하는 표현식이 평가되는데 score는 0x00000F2라는 메모리 주소를 가리키고 있고 그 주소 공간에는 숫자 리터럴 80이 2진 형태로 저장되어 있다. 

이후 copy의 식별자에 score의 값을 전달하는데 c언어라면 call by value의 원칙에 따라 새로운 메모리 주소 공간(A라고 하겠다)을 할당하고 그 곳에 80을 복사한 뒤 copy는 A라는 주소를 가리키게 된다. 

하지만 js에서는 위 그림과 같이 copy = score까지만 실행될 때 두 copy는 score와 동일한 메모리 주소 공간을 가리키게 된다. 여기까지만 본다면 call by reference라고 착각할 수 있지만

score또는 copy의 값이 재할당되는 순간(score가 재할당된다고 하겠음) score는 새로운 메모리 주소 공간을 가리키고 그 곳에 새롭게 재할당되는 100이라는 값이 할당되게 된다. 

 

정리하면 copy = score라는 코드는 call by value가 일어나지 않고 copy는 score 가 가리키는 메모리 주소 공간을 가리키며 copy 또는 score의 값이 재할당 될 때 비로소 새로운 메모리 주소 공간을 가리키고 그 곳에 값을 재할당한다. 

이러한 동작 방식은 call by value 가 아니라 편의상 pass by sharing(공유에 의한 전달)이라고 부르는 것이 더 나아을 것 같다. 

pass by sharing 은 두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통하여 값을 변경하더라도 서로 간섭할 수 없게 된다. 

 

 

3) 객체 전달(call by reference)

● 객체는 변경 가능한 값이다. 

var person = {
	name : 'Kim'
};

var copy = person;

person 식별자에 객체를 할당하게 되는데 객체 리터럴에의해 생성된 객체는 언제 생성되냐? 위 전역 코드의 평가 과정에서 객체리터럴에 의해 객체가 실제로 생성되고 heap영역에 저장된다. 저장된 heap 영역의 메모리 주소를(0x00001332) person은 참조하게 된다. 

copy 식별자에 person 객체를 (얕은) 복사하게 되면 copy는 해당 객체의 가 저장된 메모리 공간의 주소(0x00001332)를 그대로 가리키게 된다. 

이는 여러 개의 식별자가 하나의 객체를 공유하게 되는 call by reference와 동일한 동작 방식이다. 

 

객체를 할당한 변수는 재할당을 하지 않고 객체를 직접 변경, 갱신, 삭제 등을 할 수 있다. 

 

 

● 얕은 복사와 깊은 복사

얕은 복사란 객체를 프로퍼티 값으로 갖는 객체의 경우 한 단계 까지만 복사하고 깊은 복사는 객체에 중첩된 객체까지 모두 복사한다. 

"복사 하였다" 의 말은 기존의 o의 객체의 복제본을 만들어 새로운 메모리 주소 공간에 그 객체가 복사된 것을 말한다. 

const o = {x : {y : 1}};

const c1 = { ...o }; //스프레드 문법으로 복사 
console.log(c1 === o}; //false
console.log(c1.x === o.x} //true

const d = require('lodash');
const c2 = d.cloneDeep(o);
console.log(c2 === 0); //false
console.log(c2.x === o.x); //false

c1 은 o라는 객체를 얕은 복사한 것이고 c2는 o 객체를 깊은 복사를 한 것이다. 

얕은 복사를 수행하면  o라는 객체가 복사되는데 그 내부의 프로퍼티의 값으로 가지는 객체들은 복사되지 않는다.

깊은 복사를 수행하면 o라는 객체가 복사됨은 물론 그 내부의 프로터티의 값으로 가지는 객체들 모두 복사되는 것이다. 

스프레드 문법을 사용한 복사 방법은 얕은 복사가 수행된다. 

 

※ 헷갈릴 내용 정리

var person = {
	name : 'Kim'
};

var copy = person;

copy = person 이것은 얕은 복사인가? 깊은 복사인가? -> 둘다 아니다.

{ name : 'Kim' }; 이라는 객체가 저장된 heap 영역의 메모리 주소가 0x00001331 이라고 한다면 

copy와 person은 모두 0x00001331를 가리키는 것이다.