11. 모던 자바스크립트 Deep Dive(원시 값과 객체의 비교)

⛅ 원시타입 vs 객체타입

&nbsp 변경 가능 유무 메모리 공간에 저장되는 값 변수 할당 방식
원시 타입 변경 불가능 실제 값 저장 참조 값 저장
(값에 의한 전달)
객체 타입 변경 가능 참조 값 저장 참조 값 복사
(참조에 의한 전달)

⛅ 원시 값

  • 변경 불가능한 값 === 한번 생성된 원시 값은 읽기 전용
  • 원시 값 자체를 변경할 수 없다는 것이지 변수 값을 변경할 수 없다는 것이 아님!
  • 원시 값을 할당한 변수에 새로운 원시 값을 재할당하면 메모리에 저장되어있던 원시값이 변경되는 것이 아닌 새로운 메모리 공간에 저장한 값을 가리키는 것 => 메모리 공간의 주소가 바뀌는 것임
  • 변수가 참조하던 메모리 공간의 주소를 변경하는 것을 불변성 이라고 함
// 메모리 공간을 확보한뒤 kim이 저장되고,
// name이라는 식별자는 kim이 저장된 메모리 공간의 첫 번째 메모리 셀 주소를 가리킴
var name = 'kim';

// kim을 없애는 것이 아닌 donggyun을 새로운 메모리에 저장하고
// name 식별자는 donggyun 메모리의 주소를 가리키는 것 뿐임
name = 'donggyun';

// 유사 배열 객체 사용
console.log(name[0]); // d

// 원시 값인 문자열이 객체처럼 동작
console.log(name.length); // 8
console.log(name.toUpperCase); // DONGGYUN

// 문자열은 유사 배열이므로 객체가 아니라 원시 값임
// 불변성에 따라서 인덱스에 직접 값을 할당해도 변하지않음
name[0] = 'K';
console.log(name); // donggyun
❓유사 배열 객체란❓

- 배열처럼 인덱스 프로퍼티 값에 접근 가능
- length 프로퍼티를 갖는 객체
- for 문으로 순회 가능

🚨 원시 값을 객체처럼 사용하면 원시값을 감싸는 래퍼 객체로 자동 변환됨!

⛅️ 값에 의한 전달

// 우선 이 예제를 통해 값에 의한 전달을 이해해보자
var score = 80;
var copy = score;

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

score = 100;

console.log(score); // 100
console.log(copy); // ?
  • socre 변수와 copy 변수의 관계
    • 1️⃣ score 변수는 0x01 메모리 주소를 갖고있음(80을 가진)
    • 2️⃣ copy 변수에 score 변수의 0x01 메모리 주소(80을 가진)를 할당
    • 3️⃣ score 변수에 100 할당 === 새로운 메모리주소 0x02(100을 가진)가 할당됨
    • 4️⃣ score는 0x02(100) 주소를 가진 변수, copy는 0x01(80) 주소를 가진 변수
    • 🚨 따라서 score 변수의 값이 바뀐다고해서 copy의 값이 바뀌는 것이 아니다!! 🚨

⛅️ 객체

  • 프로퍼티 개수가 정해져 있지 않음
  • 동적으로 프로퍼티 추가 가능
  • 값에도 제약이 없음
  • 변경 가능한 값임 === 이미 생성된 값에 접근하여 값 변경 가능
  • 원시 값 처럼 크기가 일정하지도 않고, 프로퍼티 값이 객체일 수도 있기 때문에 생성하는 비용이 많이 듬 => 메모리의 효율적 소비가 어렵고 성능이 나쁘다.
  • 따라서 객체는 변경 가능한 값으로 설계 되어있음(메모리 사용의 효율성과 성능을 위함)
  • ( + 여러 개의 식별자가 하나의 객체를 공유 가능!! )
💡 자바스크립트 객체의 관리 방식
   - 자바스크립트 객체 == 프로퍼티 키를 인덱스로 사용하는 해시 테이블(hash table)
   - 클래스 없이 객체 생성 가능
   - 동적으로 프로퍼티,메소드 추가 가능
   - V8 엔진에서는 프로퍼티에 접근하기 위해 동적탐색 대신 히든 클래스 방식을 사용(프로퍼티 접근 성능 보장)

⛅️ 변경 가능한 값

  • 객체를 할당한 변수가 기억하는 메모리 주소 공간에 접근하면 참조 값에 접근할 수 있음
  • 참조값 === 생성된 객체가 저장된 메모리 공간의 주소
  • 원시값을 할당한 변수를 참조 => 메모리에 저장되어 있는 원시 값에 접근
  • 객체를 할당한 변수를 참조 => 메모리에 저장되어 있는 참조 값을 통해 실제 객체에 접근
  • 객체를 할당한 변수는 재할당 없이 객체를 직접 변경 가능
  • 재할당 하지 않았으므로 객체가 변경이 되었더라도 참조 값은 변경 X
var person = { name: 'donggyun' };

person.name = 'kim';
person.hobby = 'guitar';

console.log(person); // {name:'kim', hobby:'guitar'};

⛅️ 얕은 복사 vs 깊은 복사

  • 얕은 복사: 객체를 한 단계까지만 복사 === 참조에 의한 전달
  • 깊은 복사: 객체에 중첩되어 있는 객체까지 모두 복사(프로퍼티 값이 객체인것 까지 복사 함)
  • 🚨 복사 했을때, 생성된 객체는 원본과 다른 객체이다 (참조 값이 다름!!) 🚨
const obj = { x: { y: 2 } };

// 스프레드 문법을 사용한 얕은 복사
const obj = { x: { y: 1, z: { say: 'hello' } } };
const copy = { ...obj };

copy.x.y = 100; // copy.x.y를 수정하면
console.log(obj.x.y); // 100 (원본 객체도 영향을 받음)

// lodash의 cloneDeep을 사용한 깊은 복사
const _ = require('lodash'); // lodash 라이브러리 사용
const obj = { x: { y: 1, z: { say: 'hello' } } };
const deepCopy = _.cloneDeep(obj);

deepCopy.x.z.say = 'hi';
console.log(obj.x.z.say); // 'hello' (원본 객체는 영향을 받지 않음)

// JSON을 사용한 깊은 복사
const obj = { x: { y: 1, z: { say: 'hello' } } };
const deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.x.y = 100; // deepCopy.x.y를 수정해도
console.log(obj.x.y); // 1 (원본 객체는 영향을 받지 않음)

⛅️ 참조에 의한 전달

  • 사실 자바스크립트에서 "값에 의한 전달" 과 "참조에 의한 전달"은 식별자가
    기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 면에서 동일하다.
  • 따라서 자바스크립트는 "값에 의한 전달"만 존재한다.
  • 즉 변수에 저장되어 있는 값이 원시 값이냐 참조 값이냐 의 차이!!
// 1️⃣ person 식별자에 정의된 리터럴 객체의 참조 값이 할당된다.
// 2️⃣ copy 객체에 person을 할당하는데 이때 person의 참조값을 복사해서 copy에 저장한다.
// 3️⃣ 결국 person과 copy가 동일한 참조 값을 가지게 된다.
// 4️⃣ 따라서 copy가 변경된다면 person 객체도 같이 변경이 된다.

var person = { name: 'donggyun' };
var copy = person;
  • 객체의 비교
var person1 = { name: 'donggyun' };
var person2 = { name: 'donggyun' };

console.log(person1 === person2); // false => 두 객체는 생긴건 같지만 서로 다른 참조 값을 가지고있음(다른 객체임)
console.log(person1.name === person2.name); // true => 프로퍼티 name은 두 객체 모두 'donggyun' 이라는 원시 값으로 평가 되므로 같다

 

📚 REFERENCE

  • 모던 자바스크립트 Deep Dive 11장 원시 값과 객체의 비교