개발자 연쨘

[딥 다이브] 원시 값과 객체의 비교 본문

스터디/모던 자바스크립트 Deep Dive

[딥 다이브] 원시 값과 객체의 비교

연쨘 2024. 1. 21. 04:46

🐰 원시 값과 객체의 비교 🐰

자바스크립트는 크게 원시 타입과 객체 타입으로 구분할 수 있다.

  • 원시 타입의 값, 즉 원시 값은 변경 불가능한 값이다. 
    이에 비해 객체(참조) 타입의 값, 즉 객체는 변경 가능한 값이다.

  • 원시 값을 변수에 할당하면 변수에는 실제 값이 저장된다.
    이에 비해 객체를 변수에 할당하면 변수에는 참조 값이 저장된다.

  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달.
    (= 값에의한 전달)

    이에 비해 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달
    (= 참조에 의한 전달)

🐰 원시 값 🐰

변경 불가능한 값

원시 값은 변경 불가능한 값이다.
즉, 한번 생성된 원시 값은 읽기 전용 값으로서 변경 ❌
여기서 변경 불가능하다는것은 변수가 아니라 값에 대한 진술!
즉, 원시 값 자체를 변경할 수 없다는 뜻, 변수 값을 변경할 수 없다는것은 아니다.
변수는 언제든 재할당을 통해 변수 값을 변경(엄밀히 말하면 교체)할 수 있다.

변수의 상대 개념인 상수는 재할당 금지된 변수!
상수도 값을 저장하기 위한 메모리 공간이 필요하므로 변수라고 할 수 있지만,
상수는 재할당

// const 키워드를 사용해 선언한 변수는 재할당이 금지된다. 상수는 재할당이 금지된 변수일 뿐이다.
const o = {};

// const 키워드를 사용해 선언한 변수에 할당한 원시값(상수)은 변경할 수 없다.
// 하지만 const 키워드를 사용해 선언한 변수에 할당한 객체는 변경할 수 있다.
o.a = 1;
console.log(o); // {a: 1}

 
원시 값은 변경 불가능한 값 (읽기 전용 값)
변수에 새로운 원시 값을 재할당하면 메모리 공간에 저장되어 있는
재할당 이전의 원시 값을 변경하는게 ❌,
새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후,
변수는 새롭게 재할당한 원시 값을 가리키는것이다.
이때 변수가 참조하던 메모리 공간의 주소 바뀜! 
 
🐯 💬 : 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법❌
 
 

문자열과 불변성

원시 값을 저장하려면 먼저 확보해야 하는 메모리 공간의 크기를 결정해야한다.
이를 위해 원시 타입별로 메모리 공간의 크기가 미리 정해져 있다.
 
문자열은 0개 이상의 문자로 이루어진 집합을 말한다.
문자열은 몇 개의 문자로 이루어졌느냐에 따라 필요한 메모리 공간의 크기가 결정된다.

// 문자열은 0개 이상의 문자들로 이뤄진 집합이다.
let str1 = '';      // 0개의 문자로 이뤄진 문자열(빈 문자열)
let str2 = 'Hello'; // 5개의 문자로 이뤄진 문자열

 
이 같은  이유로 C에는 하나의 문자를 위한 데이터 타입(char)만 있을 뿐 문자열 타입은 존재❌
C에서는 문자열을 문자의 배열로 처리하고 자바에서는 문자열을 String 객체로 처리
 
 
자바스크립트는 원시 타입인 문자열 타입 제공!
이는 자바스크립트 장점 중 하나이다. 
자바스크립트 문자열은 원시 타입이며, 변경 불가능하다.
이것은 문자열이 생성된 이후 변경할 수 없음을 의미한다. 다음 코드를 보자!

let str = 'Hello';
str = 'world';

 
위코드에서 첫 번째 문이 실행되면 str 문자열 'Hello'가 저장된 첫 번째 메모리 셀주소를 가리킬것이고,
두 번째 문이 실행되면 이전에 생성된 문자열 'Hello'를 수정하는게 아니라,
새로운 문자열 'world'를 메모리에 생성하고 식별자 str은 새로운 문자열의 주소를  가리킨다.
이때 'Hello'와 'world' 모두 메모리에 존재하고 주소가 다르다.
 
문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 문자에 접근 가능!
* 이터러블(Iterable)은 반복 가능한 객체,  for문을 사용하여 각 요소에 대해
반복 작업을 수행할 수 있는 객체를 의미
 

유사 배열 객체

유사 배열 객체란 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고,
length 프로퍼티를 갖는 객체를 말한다.

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

let str = 'string';

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

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

 

let str = 'string';

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

console.log(str); // string

 
str[0] = 'S'; 이문은 실행되지 않는다.
문자열은 변경 불가능한 값이기 때문에 한번 생성된 문자열은 변경할 수 없다.
 
변수에 새로운 문자열을 재할당하는것은 가능!
이것은 기존 문자열을 변경하는 것이 아니라 새로운 문자열을 새롭게 할당하는것이다.

 

값에 의한 전달

다음 예제를 살펴보자!

let score = 80;
let copy = score;

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

score = 100;

console.log(score); // 100
console.log(copy);  // ?

 
 
이때 마지막 copy는 80이 할당될 것이다.
이때 새로운 숫자 값 80이 생성되어 copy 변수에 할당된다.
 
이처럼 변수에 원시 값을 갖는 변수를 할당하면 할당받는 변수(copy)에는
할당되는 변수(score)의 원시값이 복사되어 전달된다 (=값에 의한 전달)
위 예제의 경우 copy 변수에 원시 값을 갖는 score 변수를 할당하면
할당받는 변수(copy)에는 할당되는 변수(score)의 원시 값 80이 복사되어 전달된다.

let score = 80;

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

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

 
이때 score변수와 copy변수는 숫자 값 80을 갖는다는 점에서는 동일하다.
하지만 scroe변수와 copy변수의 값 80은 다른 메모리 공간에 저장된 별개의 값이다. (주소가 다름)
 
 
이제 score 변수의 값을 변경해 보자.

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

 
score변수와 copy변수의 값 80은 다른 메모리 공간에 저장된 별개의 값!
따라서 score 변수의 값을 변경해도 copy변수의 값에는 영향❌

참고로 파이썬은 변수에 원시 값을 갖는 변수를 할당하면 두 변수가 같은 원시 값을 참조하다가(같은 주소),
어느 한쪽이 재할당이 이뤄졌을 때 비로소 새로운 메모리 공간에 재할당된 값이 저장되도록 동작한다.

 
 
값에 의한 전달이라는 용어는 자바스크립트를 위한 용어가 아니므로 오해가 있을 수 있다.
엄격하게 표현하면 변수에는 값이 전달되는 것이 아니라 메모리 주소가 전달되기 때문이다.
이는 변수와 같은 식별자값이 아니라 메모리 주소를 기억하고 있기 때문이다.
 
*식별자 : 어떤 값을 구별해서 식별해낼 수 있는 고유한 이름, 메모리 주소를 기억
즉, 메모리 주소에 붙인 이름
 

let x = 10;

 
위 예제의 경우 할당 연산자는 숫자 리터럴 10에 의해 생성된
숫자 값 10이 저장된 메모리 공간의 주소를 전달!
이로써 식별자 x는 메모리 공간에 저장된 숫자 값 10을 식별!
 
 

let copy = score;

 
위 예제의 경우 score는 식별자 표현식으로서 숫자 값 80으로 평가!
이때 두가지 평가 방식이 가능하다.

  • 새로운 80을 생성해서 메모리 주소를 전달하는 방식,
    할당 시점에 두 변수가 기억하는 메모리 주소가 다르다.
  • socre의 변수값 80의 메모리 주소를 그대로 전달하는 방식,
    할당 시점에 두 변수가 기억하는 메모리 주소가 같다.

이처럼 "값의 의한 전달"은 사실 메모리 주소를 전달하는것이다.
단, 전달된 메모리 주소를 통해 메모리 공간에 접근하면 값을 참조할 수 있다.
 
중요한 것은 변수에 원시 값을 갖는 변수를 할당하면 변수 할당 시점이든,
두 변수 중 어느 하나의 변수에 값을 재할당하는 시점이든
결국은 두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어
어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할 수 없다는 것이다.


🐰 객체 🐰

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

🐯💬 : 객체는 원시 값과 같이 확보해야 할 메모리 공간의 크기를 사전에 정해 둘 수 없다.
 
객체는 복합적인 자료구조이므로 객체를 관리하는 방식이 원시 값과 비교해서
복잡하고 구현 방식도 브라우저 제조사마다 다를 수 있다.
 
원시 값은 상대적으로 적은 메모를 소비하지만 객체는 경우에 따라 크기가 매우 클 수도 있다.
즉, 원시 값과 비교할 때 객체를 생성하고 프로퍼티에 접근하는게 비용이 많이 드는 일이란뜻!
 
따라서 객체는 원시 값과는 다른 방식으로 동작하도록 설계되어 있다.
원시 값과의 비교를 통해 객체를 이해해보자!!
 

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

자바스크립트 객체는 프로퍼티 키를 인덱스로 사용하는 해시 테이블(연관배열)
이라고 생각할 수 있다.

대부분의 자바스크립트 엔진은 해시 테이블과 유사하지만
높은 성능을 위해 일반적인 해시 테이블 보다 나은 방법으로 객체를 구현한다.
해시 테이블


자바, C++ 같은 클래스 기반 객체지향 프로그래밍 언어

사전에 정의된 클래스를 기반으로 객체(인스턴스)를 생성한다. 
즉, 객체를 생성하기 이전에 이미 프로퍼티와 메서드가 정해져 있으며
그대로 객체를 생성한다는 뜻이다.
(객체가 생성된 이후에는 프로퍼티를 삭제하거나 추가 ❌)

하지만 자바스크립트는 클래스 없이 객체를 생성할 수 있으며
객체가 생성된 이후라도 동적으로 프로퍼티와 메서드 추가 ⭕
이는 사용하기에는 편하지만, 성능 면에서는 이론적으로
클래스 기반 객체지향 언어의 객체보다 생성과 프로퍼티 접근에
비용이 더 많이 드는 비효율적 방식!

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

히든 클래스는 자바와 같이 고정된 객체 레이아웃(클래스)과 유사하게 동작한다.  


 
히든클래스가 무엇인지 GPT한테 물어본내용 ▼

더보기

1. 히든 클래스 디자인 패턴:
특정 속성이나 메서드를 외부에서 직접 접근할 수 없도록 은닉하고자 할 때 사용할 수 있는
디자인 패턴이 있습니다. 이를 "히든 클래스" 패턴이라고 부를 수 있습니다.
일반적으로 클로저를 활용하여 구현됩니다.

function createHiddenClass() {
  let privateVariable = 10;

  function getPrivateVariable() {
    return privateVariable;
  }

  function setPrivateVariable(value) {
    privateVariable = value;
  }

  return {
    getPrivateVariable,
    setPrivateVariable
  };
}

const myHiddenClass = createHiddenClass();
console.log(myHiddenClass.getPrivateVariable()); // 10
myHiddenClass.setPrivateVariable(20);
console.log(myHiddenClass.getPrivateVariable()); // 20

 

위의 예제에서 privateVariable외부에서 직접 접근할 수 없으며, 오직 getPrivateVariablesetPrivateVariable 함수를 통해서만 접근 가능합니다.

 

2. ES6의 Symbol을 이용한 은닉: 

ES6에서 도입된 Symbol을 사용하여 속성을 외부에 감추는 방법도 있습니다.
이를 통해 특정 속성이름을 노출시키지 않고 사용할 수 있습니다.

const myObject = (() => {//익명함수 시작
  const hiddenSymbol = Symbol('hidden');

  return {
    [hiddenSymbol]: 'This is a hidden value',
    getHiddenValue: () => myObject[hiddenSymbol]
  };
})();//익명함수 끝

console.log(myObject.getHiddenValue()); // This is a hidden value
//익명함수에 정의된 클로저
//클로저는 외부 함수의 변수에 접근할 수 있는 함수

console.log(myObject[Symbol('hidden')]); // undefined
//Symbol('hidden')을 사용하여 객체에 직접 접근하려 시도하고 있지만,
//이는 새로운 Symbol이므로 앞서 정의한 hiddenSymbol과는 다르기 때문에 undefined를 출력합니다. 
//외부에서는 직접 접근할 수 없는 특성을 확인할 수 있습니다.

 

위의 예제에서 hiddenSymbol외부에서 접근할 수 없는 키로 사용되며,
getHiddenValue를 통해만 해당 값에 접근할 수 있습니다.

 

변경 가능한 값

객체(참조) 타입의 값, 즉 객체는 변경 가능한 값이다.
먼저 변수에 객체를 할당하면 어떤 일이 일어날까?

let person = {
  name: 'Lee'
};

 
원시 값을 할당한 변수가 기억하는 메모리 주소를 통해
메모리 공간에 접근하면 -> 원시 값에 접근 가능!

🐯💬 : 즉, 원시 값을 할당한 변수는 원시 값 자체를 값으로 갖는다.
하지만 객체를 할당한 변수가 기억하는 메모리 주소를 통해
메모리 공간에 접근하면 참조 값에 접근할 수 있다. 
 
*참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체이다.
 
 
다음 그림을 보면 객체를 할당한 변수에는 생성된 객체
실제로 저장된 메모리 공간의 주소가 저장되어 있다.
이값을 참조값이라고 한다! 변수는 이 참조 값을 통해 객체에 접근

객체의 할당

 
 
원시 값을 할당한 변수를 참조하면 메모리에 저장되어 있는 원시 값에 접근한다!
하지만 객체를 할당한 변수를 참조하면 메모리에 저장되어 있는
참조 값을 통해 실제 객체에 접근한다!

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

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

 
일반적으로 원시 값을 할당한 변수의 경우 변수는 값이 무엇이라고 말할 수 있다! 
하지만 객체를 할당한 변수의 경우 "변수는 객체를 참조하고 있다",
"변수는 객체를 가리키고 있다"고 표현한다.
 
위 예제에서는  person 변수는 객체{name : 'Lee'}를 가리키고(참조하고) 있다.
 
원시 값은 변경 불가능한 값이므로 원시 값을 갖는 변수 값을 변경 하기 위해서는 => 재할당 !
객체는 변경 가능한 값 => 객체를 할당한 변수는 재할당 없이 객체를 직접 변경
 
 
🐯💬 : 객체는 재할당 없이 프로퍼티를 동적으로 추가할 수도 있고
프로퍼티 값을 갱신할 수도 있으며, 프로퍼티 자체를 삭제할 수도 있다.

let person = {
  name: 'Lee'
};

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

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

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

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

하지만 객체는 변경 가능한 값이므로 메모리에 저장된 객체를 직접 수정 ⭕
이때 객체를 할당한 변수에 재할당을 하지 않으므로 객체를 할당한 변수의 참조 값은 변경❌
 

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

 
 
 
객체를 생성하고 관리하는 방식은 매우 복잡하며 비용이 많이 듦!
객체를 변경 할 때마다 원시 값처럼 이전 값을 복사해서
새롭게 생성한다면 명확하고 신뢰성이 확보되겠지만,

객체는 크기가 매우 클 수도 있고, 원시 값처럼 크기가 일정하지도 않으며,
프로퍼티 값이 객체일 수도 있어서 복사해서 생성하는 비용이 많이 듦
=> 즉, 메모리의 효율적 소비가 어렵고 성능이 나쁨.
 
따라서 메모리를 효율적으로 사용하기 위해서
객체는 변경 가능한 값으로 설계되어 있다.
메모리 사용의 효율성과 성능을 위해
어느 정도의 구조적인 단점을 감안한 설계라고 할 수 있다.
 
 
객체는 이러한 구조적 단점에 따른 부작용이 있다.
그것은 원시 값과는 다르게 여러 개의 식별자가
하나의 객체를 공유할 수 있다는것이다.

let obj1 = { value: 10 };
let obj2 = obj1;//동일한 메모리 위치를 갖게됨

obj2.value = 20;//여기서 값바꾸면 obj1도 바뀜

console.log(obj1.value); // 20

 
여기서 obj1obj2는 동일한 객체를 가리키고 있다.
따라서 obj2에서 객체의 속성을 변경하면 obj1에서도 동일한 변경이 반영된다.
이런 특성은 의도하지 않은 부작용을 초래할 수 있으며,
특히 객체의 상태가 여러 곳에서 수정될 수 있는 환경에서 주의가 필요하다.


"구조적 단점"은 자바스크립트에서의 객체의 특성 중 하나인 "참조에 의한 전달"
이것은 객체가 변수에 할당되면 해당 변수는 객체의 참조(메모리 상의 위치 정보)를 가지게 되어
실제로는 객체 자체가 변수에 직접 저장되는 것이 아니라, 
객체의 위치를 가리키는 포인터가 저장되는 것을 의미

그 결과로, 여러 개의 변수가 동일한 객체를 참조할 수 있다.
이를 "여러 개의 식별자가 하나의 객체를 공유한다"고 이해할 수 있다.
원시 값은 이와는 다르게 값 자체가 변수에 직접 저장되므로,
다른 변수들 간에는 서로 독립적인 값을 가지게 된다.

 
* 참조에 의한 전달은 객체가 변수에 할당될 때,
해당 객체의 실제 내용이 변수에 복사되는 것이 아니라,
객체의 참조(메모리 상의 위치 정보)가 변수에 저장되는 방식을 의미
 

얕은 복사와 깊은 복사

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

// 얕은 복사
const c1 = { ...o }; // 35장 "스프레드 문법" 참고
console.log(c1 === o); // false
console.log(c1.x === o.x); // true

// lodash의 cloneDeep을 사용한 깊은 복사
// "npm install lodash"로 lodash를 설치한 후, Node.js 환경에서 실행
const _ = require('lodash');
// 깊은 복사
const c2 = _.cloneDeep(o);
console.log(c2 === o); // false
console.log(c2.x === o.x); // false


const _ = require('lodash');는 Node.js 환경에서 Lodash 라이브러리를 사용하기 위해 모듈을 불러오는 구문

require 함수 Node.js에서 모듈을 불러올 때 사용,

lodash는 JavaScript 유틸리티 라이브러리로, 다양한 기능을 제공하는데,

이 중에서도 cloneDeep 함수는 깊은 복사를 수행하는 함수 !

 

const _는 Lodash 라이브러리를 가리키는 변수로서, 이후에 사용되는 _는 보통 Lodash 라이브러리의 함수들을

호출할 때 사용되는 관례적인 이름이다.

 

코드에서 _를 사용하는 이유는 일반적으로 길게 쓰기 번거롭게 느낄 수 있는 Lodash 함수들을

간결하게 호출하기 위함이다.

 

여기에서는 _.cloneDeep(o);를 사용하여 객체 o를 깊은 복사한 결과를 반환하고 있다.
_.cloneDeep 함수는 객체의 모든 내부 구조를 재귀적으로 복사하여

새로운 객체를 생성하므로, 원본 객체와는 별개의 메모리 공간을 사용하게 된다.



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

하지만 얕은 복사는 객체에 중첩되어 있는 객체의 경우 참조 값을 복사
즉, 얕은 복사는 중첩된 객체까지는 완전히 복사를 안해준다는것.
최상위 요소만 복사한다고 기억하는게 편함!
//얕은복사
const originalArray = [1, 2, [3, 4]];
const shallowCopy = [...originalArray];

shallowCopy[2][0] = 99;

console.log(originalArray); // [1, 2, [99, 4]]


깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사해서 원시값처럼
완전한 복사본을 만든다는 차이가 있다.
//깊은복사
const originalArray = [1, 2, [3, 4]];
const deepCopy = JSON.parse(JSON.stringify(originalArray));

deepCopy[2][0] = 99;

console.log(originalArray); // [1, 2, [3, 4]]



다음과 같이 원시 값을 할당한 변수를 다른 변수에 할당하는 것 => 깊은복사
객체를 할당한 변수를 다른 변수에 할당하는 것 => 얕은 복사
const v = 1;

// "깊은 복사"라고 부르기도 한다.
const c1 = v;
console.log(c1 === v); // true

const o = { x: 1 };

// "얕은 복사"라고 부르기도 한다.
const c2 = o;
console.log(c2 === o); // true

 
 

참조에 의한 전달

여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것이 무엇을 의미하는지,
이로 인해 어떤 부작용이 발생하는지 위에서 간단히 설명했지만!
더 자세히 알아보자.

let person = {
  name: 'Lee'
};

// 참조값을 복사(얕은 복사)
let copy = person;

 
객체를 가리키는 변수(원본, person)를 다른 변수(사본, copy)에 할당하면 
원본의 참조 값이 복사되어 전달 된다. => 참조에 의한 전달
 

참조에 의한 전달

 
그림처럼 원본 person을 사본 copy에 할당 ▶️ 원본 person의 참조 값이 복사 ▶️ copy에 저장
이때 원본 person과 사본 copy는 저장된 메모리 주소는 다르지만 동일한 참조 값을 갖음!
=> 원본 person과 사본 copy 모두 동일한 객체를 가리킴 !
 
이것은 두 개의 식별자가 하나의 객체를 공유한다는 것을 의미한다.
 
🐯💬 : 따라서 원본 또는 사본 중 어느 한쪽에서 객체를 변경하면
(변수에 새로운 객체를 재할당하는 것이 아니라 객체의 프로퍼티 값을 변경하거나 프로퍼티를 추가, 삭제)
서로 영향을 주고 받게됨!
 

let person = {
  name: 'Lee'
};

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

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

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

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

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

 
 

🐇 "값에 의한 전달"과 "참조에 의한 전달" 공통점과 차이점!


-공통점 : 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 면에서는 동일하다.
-차이점 : 식별자가 기억하는 메모리 공간, 즉 변수에 저장되어 있는 값이 원시 값이냐 참조 값이냐의 차이만 있다.
 
자바스크립트의 이 같은 동작 방식을 설명하는 정확한 용어가 존재하지는 않는다.
이런 이유로 "값에 의한 전달"이나 "참조에 의한 전달" 이라는 용어를 사용하지 않고,
"공유에 의한 전달" 이라고 하는 경우도 있다. => 하지만 이또한 공식적인 용어 ❌
 
따라서 내가 정리하는 부분에서는!
전달되는 값의 종류가 원시 값인지 참조 값인지 구별해서 강조하는 의미에서
"값에 의한 전달"과 "참조에 의한 전달"로 부르겠다.
다만 자바스크립트에는 포인터가 존재하지 않기 때문에
포인터가 존재하는 다른 프로그래밍 언어의 "참조의 의한 전달"과
의미가 정확히 일치하지는 않는다.
 
* 포인터 : 메모리 주소를 가리키는 변수, 주로 다른 변수의 메모리 위치를 참조하거나 저장하는 데 사용
 
 
마지막으로 퀴즈 하나를 풀어보자!

let person1 = {
  name: 'Lee'
};

let person2 = {
  name: 'Lee'
};

console.log(person1 === person2); // ①
console.log(person1.name === person2.name); // ②

 
=== 일치 비교 연산자 => 변수에 저장되어 있는 값을 타입 변환하지 않고 비교.
객체를 할당한 변수는 참조 값을 가지고 있고,
원시 값을 할당한 변수는 원시 값 자체를 가지고 있다.
 
따라서 === 일치 비교 연산자를 통해 객체를 할당한 변수를 비교하면
참조 값을 비교하고, 원시 값을 할당한 변수를 비교하면 원시 값을 비교한다.
 
객체 리터럴은 평가될 때마다 객체를 생성한다.
따라서 person1 변수와 person2 변수가 가리키는 객체는 비록 내용은 같지만
다른 메모리에 저장된 별개의 객체다.
 
즉 person1 변수와 person2 변수의 참조 값은 전혀 다른 값!! 
그러므로 ① false
 
하지만 프로퍼티 값을 참조하는 person1.name과 person2.name은 값으로 평가될 수 있는 표현식이다.
두 표현식 모두 원시 값 'Lee'로 평가 된다. 그러므로 ② true 

728x90