🖥️ Frontend/모던 자바스크립트 Deep Dive

11장 원시 값과 객체의 비교

우다 2024. 7. 6. 23:05
11장 원시 값과 객체의 비교

11.1 원시 값

원시 타입의 값객체(참조) 타입의 값
변경 불가능한 값변경 가능의 값
변수에 할당하면 확보된 메모리에는 실제 값이 저장된다.객체를 변수에 할당하면 확보된 메모리에는 참조 값이 저장된다.
값에 의한 전달
👉 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다.
참조에 의한 전달
👉 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다.
변수는 O값을 갖는다.
변수의 값은 O다.
변수는 객체를 참조하고 있다.
변수는 객체를 가리키고 있다.

11.1.1 변경 불가능한 값

✅ 원시 타입의 값

👉🏻
원시 값은 변경이 불가능하다는 것은 변수가 아니라 값에 대한 진술이다.

변수는 확보한 메모리 공간 자체이고 값은 변수에 저장된 데이터로 표현식이 평가되어 생성된 결과다.

만약 원시 값이 변경가능하다면 메모리 공간의 주소를 바꿀 필요 없이 원시 값 자체를 변경하면 그만이다.

하지만, 원시 값을 할당한 변수에 새로운 원시 값을 재할당하면 새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후, 새로운 메모리 공간의 주소를 가리킨다.

👉🏻
값의 이러한 특성을 불변성이라고 한다.

불변성을 갖는 원시 값을 할당한 변수는 재할당 이외의 변수 값을 변경할 수 있는 방법이 없다.
따라서
원시 값은 읽기 전용의 값으로 데이터의 신뢰성을 보장한다.

✅ 상수(const) vs 변경 불가능한 값

변수는 재할당을 통해 변수 값을 변경할 수 있지만 상수는 재할당이 금지된 변수이다.

변경 불가능한 갓은 원시 값 그 자체를 변경할 수 없다는 뜻이다.

11.1.2 문자열과 불변성

✅ 문자열

문자열은 0개 이상의 문자로 이루어진 집합을 말하며, 1개의 문자는 2바이트의 메모리 공간에 저장된다.

따라서 문자열은 몇 개의 문자로 이뤄졌느냐에 따라 메모리 공간의 크기가 결정된다.

필요한 메모리가 유동적으로 변하기 때문에 C에서는 문자열을 문자의 배열로, 자바에서는 문자열을 String으로 처리하지만 자바스크립트는 개발자의 편의를 위해 문자열 타입을 제공한다.

문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문자에 접근할 수 있다.

let str = 'string';

// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.
// 하지만 문자열은 원시 값이므로 변경할 수 없다. 이때 에러가 발생하지 않는다.
str[0] = 'S';

console.log(str); //string
👉🏻
자바스크립트의 문자열은 원시 타입이며, 변경 불가능하다.

따라서 이미 생성된 문자열의 일부 문자를 변경해도 반영되지 않는다.

하지만 변수에 새로운 문자열을 할당하는 것은 가능하므로 기존 문자열을 변경하고 싶다면 재할당을 해야 한다.

📚
유사 배열 객체

유사 배열 객체란 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말한다.
원시 값을 객체처럼 사용하면 원시 값을 감싸는 리퍼 객체로 자동 변환된다. 따라서 문자열이 객체처럼 동작한다.

let string = 'string';

// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.
console.log(str[0]) // s

// 원시 값인 문자열이 객체처럼 동작한다.
console.log(str.length) // 6
console.log(str.toUpperCase()) // STRING

11.1.3 값에 의한 전달

✅ 값에 의한 전달(= 공유에 의한 전달, 참조에 의한 전달)

변수에 원시 값을 갖는 변수를 할당하면 원시 값이 복사되어 전달된다.

사실 값에 의한 전달은 값을 전달하는 것이 아니라 메모리 주소를 전달한다.

전달된 메모리 주소를 통해 메모리 공간에 접근하여 값을 참조한다.

let score = 80

// copy 변수에는 score 변수의 값 80이 복사되어 할당된다.
let copy = score

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

// score 변수와 copy 변수의 값은 다른 메모리 공간에 저장된 별개의 값이다.
// 따라서 score 변수의 값을 변경해도 copy 변수에는 어떠한 영향도 주지 않는다.
score = 100;

console.log(score, copy); // 100 80
console.log(score === copy) // false
👉🏻
두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할 수 없다.

실제 자바스크립트 엔진을 구현하는 제조사에 따라 실제 내부 방식은 미묘한 차이가 있을 수 있다.

1. copy라는 메모리 공간을 만들고 값 80이 복사되어 전달되는 경우

2. 같은 공간을 바라보고 있다가 재할당되면 새로운 공간에 재할당된 값을 저장하는 경우(파이썬)

11.2 객체

✅ 객체

객체는 원시 값과 같이 확보해야 할 메모리 공간의 크기를 사전에 정해둘 수 없다.

따라서 객체는 원시 값과는 다른 방식으로 동작하도록 설계되어 있다

📚
자바스크립트 객체의 관리 방식

자바스크립트 객체는 프로퍼티 키를 인덱스로 사용하는 해시테이블과 유사하지만 더 나은 방법으로 객체를 구현한다.

자바스크립트는 다른 언어와 다르게 클래스 없이 객체를 생성할 수 있으며 객체가 생성된 이후라도 프로퍼티와 메서드를 추가할 수 있다. 하지만 이는 편리하지만 성능상으로 비효율적이다.

따라서 V8 자바스크립트 엔진에서는 프로퍼티에 접근하기 위해 동적 탐색 대신 히든 클래스라는 방식을 사용해 C++ 객체의 프로퍼티에 접근하는 정도의 성능을 보장한다.

11.2.1 변경 가능한 값

✅ 참조 값

객체는 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값에 접근할 수 있다.

참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체다.

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

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

그림처럼 person 식별자가 가리키는 주소에는 객체가 저장되어 있는 메모리의 주소를 가리키고 있다.

👉🏻
따라서 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다.
즉,
재할당 없이 프로퍼티를 동적으로 추가/갱신/삭제할 수 있다.

원시 값은 변경 불가능한 값이므로 변수의 값을 변경하려면 재할당을 통해 메모리에 원시 값을 새롭게 생성한다.

하지만 객체는 변경가능한 값이므로 메모리에 저장된 객체를 직접 수정할 수 있다.

이 때, 객체를 할당한 변수의 참조 값은 변경되지 않는다.

let person = {
	name: 'woo'
}

// 프로퍼티 값 갱신
person.name = 'Woo';

// 프로퍼티 동적 생성
person.address = 'Yongin';

객체가 변경 불가능한 값이라면 변경할 때마다 원시 값처럼 값을 복사해서 새롭게 생성해야 할 것이다.

객체의 크기가 매우 클 수도 있고, 원시 값처럼 크기가 일정하지도 않으며, 프로퍼티 값이 객체일수도 있어서 복사해서 생성하는 비용이 많이 들 것이다.

따라서 객체가 변경 가능한 것은 효율성과 성능을 위해 어느 정도의 구조적 단점을 감안한 설계라고 볼 수 있다.

👉🏻
하지만 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 단점이 있다.

📚
얕은 복사 VS 깊은 복사

객체를 프로퍼티 값으로 갖는 경우 얕은 복사는 한 단계까지만 복사하는 것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 말한다.
const obj = {x: {y: 1}}

// 얕은 복사
const copy1 = {...obj}
console.log(obj === copy1) // false
console.log(copy1.x === obj.x) // true

// lodash의 cloneDeep을 사용한 깊은 복사
// 'npm install lodash'로 lodash 설치 후 실행
const _ = require('lodash');

// 깊은 복사
const copy2 = _.cloneDeep(obj);
consol.log(copy2 === obj) // false
consol.log(copy2.x === obj.x) // false

11.2.2 참조에 의한 전달

✅ 참조에 의한 전달

객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다.

let person = {
	name: 'woo'
}

// 참조 값을 복사(얕은 복사). copy와 person은 동일한 참조 값을 가진다.
let copy = person;

// copy와 person은 동일한 객체를 참조한다.
console.log(copy === person); // true

// copy를 통해 객체를 변경한다.
copy.name = 'Kim';

// person을 통해 객체를 변경한다.
person.addres = 'Seoul';

// copy와 person은 동일한 객체를 가리킨다.
// 따라서 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다.
console.log(person) // {name: "Kim", address: "Seoul"}
console.log(copy) // {name: "Kim", address: "Seoul"}

변수 copyperson의 참조 값을 복사해서 저장한다.

이때 원본 person과 사본 copy는 저장된 메모리 주소는 다르지만 동일한 참조 값을 가진다.

👉🏻
즉, 두 개의 식별자가 하나의 객체를 공유한다.
따라서 원본이나 사본 중 한쪽이 객체를 변경하면 서로 영향을 주고 받는다.

✅ 값에 의한 전달과 참조에 의한 전달의 공통점

식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사한다.

✅ 값에 의한 전달과 참조에 의한 전달의 차이점

식별자가 기억하는 메모리 공간에 저장되어 있는 값의 타입이 다르다.

값에 의한 전달참조에 의한 전달
원시 값참조 값

자바스크립트에는 포인터가 없기 때문에 다른 프로그래밍 언어의 ‘참조에 의한 전달’과 의미가 정확히 일치하지 않는다.

let person1 = {name: 'Woo'}
let person2 = {name: 'Woo'}

console.log(person1 === person2) // 1️⃣
console.log(person1.name === person2.name) // 2️⃣

person1person2 식별자가 가리키고 있는 메모리가 다르다. 따라서 1️⃣의 결과는 false

두 객체의 프로퍼티 name은 원시값으로 평가되어 원시 값을 비교하므로 같다. 따라서 2️⃣의 결과는 true

QUIZ

let person1 = {
	age: "27",
	name: {first: "Dayoung", second: "Woo"}
}

let person2 = {
	age: 27,
	name: {first: "Dayoung", second: "Woo"}
}

console.log(person1.age === person2.age)
console.log(person1.name === person2.name)
console.log(person1.name.first === person2.name.first)

'🖥️ Frontend > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글

12장 함수(1)  (3) 2024.07.20
10장 객체 리터럴  (0) 2024.06.24
09장 타입 변환과 단축 평가  (0) 2024.06.18
08장 제어문  (0) 2024.06.14
07장 연산자  (1) 2024.06.10