[SEB_FE_45] 2023.04.26 / 원시 자료형과 참조 자료형

Kay·2023년 4월 26일
0

📖 [강의 내용 및 개념 정리]
목차

  • 원시 자료형과 참조 자료형
    • 원시 자료형
      • 원시 자료형의 특징
    • 참조 자료형
      • 참조 자료형의 특징
  • 원시 자료형은 변경 불가능한 값(immutable value)이다?
  • 원시 자료형과 참조 자료형의 복사 및 재할당 관련 차이점
    • 원시 자료형의 복사 -> 값 복사
    • 참조 자료형의 복사 -> 주소 복사
  • 참조 자료형의 재할당 문제를 해결하기 위해
    • 얕은 복사
      • spread operator
      • 배열의 내장함수
      • Object.assign()
    • 깊은 복사
      • JSON.stringify()와 JSON.parse()
      • 외부 라이브러리 사용
  • 끝나면서 추천하는 아티클

원시 자료형 (primitive data type)

  • 원시 타입은 모두 하나의 값을 담고 있음
  • 예) 문자, 숫자, null, undefined, symbol …

원시 자료형의 특징

  • 원시 자료형을 변수에 할당하면 메모리 공간에 값 자체가 저장된다.
  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값 자체가 복사되어 전달된다.
  • 원시 자료형은 변경 불가능한 값(immutable value)이다. 즉, 한 번 생성된 원시 자료형은 읽기 전용(read only) 값이다.

참조 자료형 (reference data type)

  • 참조 타입은 변수에 할당할 때에는 값이 아닌 '주소'를 저장
  • 예) 배열, 객체, 함수

참조 자료형의 특징

  • 참조 자료형을 변수에 할당하면 메모리 공간에 주소값이 저장된다.
  • 참조 값을 갖는 변수를 다른 변수에 할당하면 주소값이 복사되어 전달된다.
  • 참조 자료형은 변경이 가능한 값(mutable value)이다.

변수는 주소를 저장하고, 주소는 특별한 동적인 데이터 보관함에 보관되는데 이 데이터 보관함을 메모리 힙 이라고 합니다.
값을 재할당 할 경우 주소는 참조한 모든 값이 영향을 받습니다. 즉, 값이 공유됩니다.

(출처) tistory - 원시 타입(primitive type) vs 참조 타입(preference type) 요약

원시 자료형은 변경 불가능한 값(immutable value)이다?

위 원시 자료형 특징에서 원시 자료형은 변경할 수 없는 값이라고 했다.
그렇다면 어떻게 재할당을 할 수 있는 것일까?

재할당을 한 경우 값이 변경된 것처럼 보이는 것이다.

let str = 'hello';
str = 'world';

이 경우 메모리 내부에는 원시 값을 저장하기 위한 새로운 공간을 확보한 뒤, 그 공간에 str이라는 변수명 이름을 붙이고 'world'를 추가로 저장하는 것이다.

즉, 변수에 다른 값을 재할당해도 원시 값 자체가 변경된 것이 아니라 새로운 원시 값을 생성하고, 변수가 다른 메모리 공간을 참조한다.(불변하는 읽기 전용 데이터)

사용하지 않는 값은 가비지 콜렉터가 자동으로 메모리에서 삭제한다.

원시 자료형과 참조 자료형의 복사 및 재할당 관련 차이점

원시 자료형의 복사 -> 값 복사

let num2 = num;
num2 = 5;

num2의 값은 num과 같은 3이 할당되었다.
그림 속 원시 자료형 스택에 변수 명: num2, 변수 값: 3 이 하나가 더 생겼을 것이다.

이후 5로 재할당되었다.
스택 속 변수 명: num2인 것을 찾고 변수 값: 5로 재할당해주었을 것이다.
num의 변수 값은 여전히 3이다.

참조 자료형의 복사 -> 주소 복사

let numArr = arr;
numArr.push(7);

numArr에는 변수 arr이 할당되었다.
변수 arr에는 주소: 1 값을 저장하고 있다.
즉, 스택에는 변수명: numArr, 주소: 1 이 하나 더 생겼을 것이다.

이후 numArr에 7이라는 값을 추가하였다.
스택 속 변수 명: numArr을 찾고, 주소: 1을 따라가 해당 주소에 저장된 값들에 7을 추가해주었다.

numArr의 값만 변한 것이 아니라 arr의 값도 변화하였다.

위에서 설명했듯 값이 공유되는 것이다.

참조 자료형의 재할당 문제를 해결하기 위해

얕은 복사

  • slice(), Object.assign(), spread syntax 등의 방법으로 참조 자료형을 복사하면, 중첩된 구조 중 한 단계까지만 복사

spread operator

let numArr = [...arr];

spread operator를 사용하면 기존 불변성(immutable)을 유지하기 때문에 많이 사용하는 방법이다.

  • Spread Operator가 호출될 때 내부적으로는 iterator-looping 액션을 수행
    • 객체에 [Symbol.iterator] 프로퍼티가 존재한다면 이터러블한 것

배열의 내장함수

let numArr = arr.slice();

새로운 배열을 반환하는 함수들(slice, map, filter ...)등을 적절히 사용하는 것도 방법이다.

Object.assign()

let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = Object.assign({}, obj);

console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false

주의점!!)

얕은 복사의 경우 옮겨 담는 요소의 깊이(depth)는 철처히 1 depth

참조 자료형 내부에 참조 자료형이 중첩되어 있는 경우, slice(), Object.assign(), spread syntax를 사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사할 수 없다.
참조 자료형이 몇 단계로 중첩되어 있던지, 위에서 설명한 방법으로는 한 단계까지만 복사할 수 있다.

위 결과 이유에 대한 사진 설명

깊은 복사

  • JavaScript 내부적으로는 깊은 복사를 수행할 수 있는 방법이 없음

JSON.stringify()와 JSON.parse()

먼저 중첩된 참조 자료형을 JSON.stringify()를 사용하여 문자열의 형태로 변환하고, 반환된 값에 다시 JSON.parse()를 사용하기

  • JSON.stringify()의 과정에서 원시 타입인 string으로 변환되기 때문에 다시 파싱된 값은 완전히 새로운 값을 가지며, 깊은 복사와 같은 결과물을 반환

하지만 예외가 존재

  • BigInt, Function, undefined
  • 참고) BigInt를 JSON.stringify()하면 오류가 나는 이유
    • String을 BigInt의 형태로 JSON.parse()할 수 없기 때문에 미리 오류 발생

외부 라이브러리 사용

  • lodash의 cloneDeep 또는 ramda
  • JSON.stringify()JSON.parse()가 처리하지 못하는 예외 사항을 처리할 수 있음
import { equals } from 'ramda';

// 입력값이 같은지, 같이 않은지 체크하는 메서드
const checker = (...args) => console.log(equals(...args));

끝나면서 추천하는 아티클

medium - 깊은 복사와 얕은 복사에 대한 심도있는 이야기

"데이터를 깊게 & 얕게 때에 따라 잘 복사해야 한다"
자바스크립트의 내장 기능만으로는 얕은 복사밖에 수행할 수 없다.

  • spread operator, Array.prototype.slice(), Object.assign()

JSON.stringify()JSON.parse()을 통해 깊은 복사를 구현할 수 있으나 예외가 존재한다.

  • BigInt, Function, undefined

라이브러리를 사용한 깊은 복사

  • lodash와 ramda는 깊은 복사에 대한 목표를 달성하기 위해 퍼포먼스가 떨어지더라도 모든 경우의 수를 체크하여 정확성에 의미를 둠

자바스크립트에 왜 깊은 복사를 수행할 수 있는 메서드가 존재하지 않을까?
https://github.com/tc39/ecma262/issues/1319
-> 깊은 복사는 아직 일반적인?통용되는? 알고리즘이 언어에 아직 존재하지 않는 구조화된 복사가 아니다.
https://github.com/tc39/ecma262/issues/1840

결론: 깊은 복사를 할 수 있도록 언어 내부적으로 모델링되어 있지 않기에 내장 메서드 대신 외부 라이브러리를 통해서 깊은 복사를 구현할 수 있다.
하지만 가장 베스트는, 깊은 복사를 하기 전에 과연 깊은 복사를 무리하면서까지 해야하는지 아키텍쳐 관점에서 다시 한 번 생각해봄이 좋지 않을까

0개의 댓글