[Modern JS Deep Dive] CH11-원시 값과 객체의 비교

Boo Sung Jun·2022년 6월 24일
0

JavaScript

목록 보기
3/27
post-thumbnail

Modern JavaScript Deep Dive 스터디 - CH11 원시 값과 객체의 비교

참고 자료: ⟪모던 자바스크립트 Deep Dive⟫"(이웅모 지음,위키북스, 2020)


1. 원시 타입(Primitive type) vs 객체 타입(Object/Reference type)

---원시 타입객체 타입
데이터
타입
숫자, 문자열, 불리언,
null, undefined, symbol
object
값 변경
여부
변경 불가
(immutable value)
변경 가능
(mutable value)
값 저장
방식
실제 값 저장참조 값 저장
값 전달
방식
값이 복사되어 전달
(pass by value)
참조 값이 복사되어 전달
(pass by reference)

2. 원시 값(primitive value)

  • 원시 타입의 값 == 변경 불가능한 값 == read only

변수(variable) vs 값(value)

  • 변수: 하나의 값을 저장하기 위해 확보한 메모리 공간. 또는 메모리 공간을 식별하기 위해 붙인 이름
  • : 변수에 저장된 데이터

=> "변경 불가능하다"는 말은 변수가 아니라 값에 대한 것.

"원시 값은 변경 불가능하다."
== "원시 값 자체를 변경할 수 없다"
!= "변수 값을 변경할 수 없다"

변수(variable) vs 상수(constant)

  • 변수: 언제든지 "재할당"을 통해 변수 값을 변경(교체) 가능
  • 상수: 단 한 번만 할당이 허용 -> 변경(교체) 불가

1) 문자열과 불변성(immutability)

  • 원시 값을 저장하려면 먼저 확보해야하는 메모리 공간의 크기를 결정

    문자열 타입, 숫자 타입은 ECMAScript 사양에 의해 2바이트, 8바이트 할당.
    그외 원시 타입은 브라우저에 따라 달라짐

  • 문자열은 0개 이상의 문자(character)로 이루어진 집합
  • 1개의 문자는 2바이트의 메모리 공간에 저장 -> 문자열에 포함된 문자 개수에 따라 필요한 메모리 공간 크기가 달라짐
    ex) "a" -> 2bytes, "abc" -> 6bytes
  • 다른 언어와는 달리 JavaScript의 문자열은 원시 타입
  • 문자열은 유사 배열 객체이면서 이터러블 -> 각 문자에 접근 가능, for 문으로 순회 가능, length 프로퍼티 가짐

    유사 배열 객체(array-like object)
    배열처럼 인덱스로 프로퍼티 값에 접근할 수 있으며, length 프로퍼티를 갖는 객체

문자열은 원시값? 객체?
문자열은 원시값
But 객체처럼 사용하면 래퍼 객체로 자동 변환 되어 사용
21.3절 참고

var str = 'string';

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

console.log(str); // string

2) 값에 의한 전달(pass by value)

  • 변수에 원시 값을 갖는 변수를 할당 -> 할당되는 변수의 원시 값이 복사되어 전달
  • 사실 값에 의한 전달은 JavaScript 용어가 아니므로 오해가 있을 수 있음.
    • 변수에는 이 전달되는 것이 아니라 메모리 주소가 전달
      즉, 식별자는 값이 아닌 메모리 주소를 가진다.
    • 식별자가 기억하고 있는 메모리 주소를 통해 메모리 공간에 저장된 값에 접근하는 방식
  • 공유에 의한 전달(pass by sharing)이라고도 표현
var score = 80;
var copy = score;

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

score = 100;

console.log(score); // 100
console.log(copy);  // 80
console.log(score === copy); // false
  • ECMAScript 사양에 변수를 통해 메모리를 어떻게 관리해야 하는지 명확하게 정의되어 있지 않음 -> 자바스크립트 엔진을 구현한 제조사에 따라 내부 동작 방식에 차이 존재

3. 객체

  • 객체의 프로프티 개수는 정해지지 않음 -> 원시 값처럼 확보해야 할 메모리 공간의 크기를 사전에 정할 수 없음
  • 객체는 프로퍼티 키를 인덱스로 사용하는 해시 테이블로 볼 수 있음. 그러나 사실 대부분의 자바스크립트 엔진은 높은 성능을 위해 해시 테이블과 유사하지만더 나은 방법으로 객체 구현.
  • 클래스 기반 언어의 객체보다 생성과 프로퍼티 접근에 비용이 더 많이 발생 -> 성능 면에서 비효율적
  • 크롬 V8 JavaScript Engine에서는 동적 탐색(dynamic lookup) 대신 히든 클래스(hidden class)방식 사용 -> C++ 수준의 성능

1) 변경 가능한 값(mutable value)

  • 객체는 변경 가능한 값
  • 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근 -> 원시 값이 아닌, 참조 값(reference value)에 접근
  • 참조 값(reference value)은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체
  • 객체를 할당한 변수는 재할당 없이 객체를 직접 변경 가능
    -> 재할당 없이 프로퍼티를 동적으로 추가, 갱신, 삭제 가능

    객체를 변경할 때마다 복사해서 새롭게 생성한다면 명확하고 신뢰성이 확보되지만, 객체는 크기가 매우 클 수 있고, 원시 값처럼 크기가 일정하지도 않고, 프로퍼티 값이 객체일 수도 있음
    -> 복사 비용이 늘어 메모리의 효율적 소비가 어렵고 성능이 나빠짐

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

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

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

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

2) 참조에 의한 전달(pass by reference)

  • 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달
    -> 두 개의 식별자가 하나의 객체를 공유
var person = {
  name: 'Lee'
};

// 참조값을 복사(얕은 복사). copy와 person은 동일한 참조값을 갖는다.
var 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"}
  • 참고1: 자바스크립트 Array는 객체의 한 종류 -> 함수에 매개변수로 전달 시, 참조 값이 전달됨
function foo(arr){
  arr.push(100);
}

var myArr = new Array();
foo(myArr);  // 함수 인자로 전달

// 함수에서 실행한 결과가 원본 배열에도 적용됨
console.log(myArr);  // [100]
  • 참고2: React에서 주의사항

// ...

export function MyComponent() {
  
  const [shoppingCart, setShoppingCart] = useState(['apple', 'milk', 'cake']);
  
  // 장바구니에 물품 추가하는 함수
  function addProduct(product){
  
  // 잘못된 예시
  var temp = shooppingCart;
  temp.push(product);
  setShoppingCart(product);
  // shoppingCart는 배열의 참조 값을 가지고 있기 때문에 원소를 추가해도 값 변화 X
  // -> 리렌더링 안일어남
  
  // 올바른 예시
  setShoppingCart([...shoppingCart, product]);
  // shallow copy를 통해 새로운 배열을 생성 후에 변수에 재할당 -> 리렌더링 발생
}


  return (
    // ... 
  );
}
 
ReactDOM.render(
  <MyComponent />,
  document.getElementById('root');
);

//...

3) 얕은 복사(shallow copy) vs 깊은 복사(deep copy)

  • 얕은 복사: depth 가 1 까지만 복사. 객체의 중첩된 객체는 참조 값을 복사
  • 깊은 복사: 객체에 중첩되어 있는 객체까지 모두 복사
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

0개의 댓글