코드스테이츠 13일차

열공하는바보·2023년 4월 27일
0
  1. 놀랍게도 C/C++과는 완전히 다른 점이 있다.
    바로 원시형(primitive variable)의 불변성(Immutability)이다.

=> 근데, 의문점이 들 수 있다. 아니, let a = 3; a = 4; 가 가능하지 않느냐?
=> 그런데 또 한번 놀랍게도, 처음의 a의 주소가 0X003이었다면, 4가 할당되고 난 후는 0X004가 된다

=> 무슨 말이냐? 원시형의 값 자체는 변경될 수가 없단 뜻이다. 새로운 주소로 생겨난다는 뜻
=> 즉, 원시형이 많이 재할당 되면 메모리가 낭비될 수 있다는 뜻이다. 하지만...

  1. 원시형과 참조형 자체는 여타 언어들과 비슷하다. 다만,
    string은 원시형이다. 이것만 조심하자!
  1. JavaScript에서는 6개의 자료형(number, string, boolean, undefined, null, symbol)을 원시 자료형으로 구분합니다.
  1. 원시 자료형이 아닌 모든 자료형은 참조 자료형입니다. 여러 데이터를 한 번에 다룰 수 있는 배열, 객체가 대표적인 참조 자료형입니다. 함수도 참조 자료형으로 분류합니다.
  1. 원시 자료형의 특징
    원시 자료형을 변수에 할당하면 메모리 공간에 값 자체가 저장된다.
    원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값 자체가 복사되어 전달된다.
    원시 자료형은 변경 불가능한 값(immutable value)이다. 즉, 한 번 생성된 원시 자료형은 읽기 전용(read only) 값이다.
  1. 참조 자료형의 특징
    참조 자료형을 변수에 할당하면 메모리 공간에 주소값이 저장된다.
    참조 값을 갖는 변수를 다른 변수에 할당하면 주소값이 복사되어 전달된다.
    참조 자료형은 변경이 가능한 값(mutable value)이다.
  1. 변경 불가능한 값 vs 변경이 가능한 값
    한 번 생성된 원시 값은 변경할 수 없습니다. num이라는 변수를 선언하고 숫자 20을 할당해 보겠습니다.
여기서 변수 num에 할당된 값을 숫자 20 대신, 다른 값으로 변경할 수 있을까요? 
다른 값을 변수에 재할당하면 됩니다.

num = 30;
변수에 할당된 값이 20에서 30으로 변경되기 때문에 원시 자료형인 
숫자 타입의 값이 변경된 것처럼 보입니다. 
그렇다면 원시 자료형이 변경 불가능한 값이라는 것은 무슨 뜻인 걸까요? 
값을 재할당했을 때, 메모리에서 어떤 일이 일어났는지 확인해 보겠습니다.

num이라는 변수가 참조하던 공간에 들어 있던 20이 30으로 변경될 것 같지만, 메모리 내부에서는 이처럼 동작하지 않습니다.
메모리 내부에서는 30이라는 원시 값을 저장하기 위한 새로운 공간을 확보한 뒤, 그 공간에 num이라는 이름을 붙이고 30을 저장합니다.
이처럼 변수에 다른 값을 재할당해도 원시 값 자체가 변경된 것이 아니라 새로운 원시 값을 생성하고, 변수가 다른 메모리 공간을 참조합니다.

따라서 원시 자료형은 어떤 상황에서도 불변하는 읽기 전용 데이터입니다. 이는 원시 자료형이 높은 신뢰성을 가질 수 있는 요인이기도 합니다.
남아 있는 값 20은 어떻게 될까요? JavaScript 엔진은 이처럼 사용하지 않는 값을 자동으로 메모리에서 삭제합니다. 이런 기능을 가비지 콜렉터(garbage collector)라고 합니다. 그러나 가비지 콜렉터가 어느 시점에 진행되는지는 예측할 수 없습니다.

문자열은 원시 자료형이지만 배열처럼 인덱스로 문자열의 각 문자에 접근이 가능합니다.
console.log(str[0]) // 's'
console.log(str[2]) // 'a'
하지만 배열과는 달리 인덱스에 직접 다른 문자를 할당하여 값을 변경할 수 없습니다. 
문자열도 원시 자료형이기 때문에 값을 변경할 수 없기 때문입니다.

배열을 복사하는 방법은 크게 두 가지 방법이 있습니다.
배열 내장 메서드인 slice()를 사용하는 방법과 ES6에서
도입된 spread문법을 사용하는 방법입니다.

spread syntax는 ES6에서 새롭게 추가된 문법으로, spread라는 단어의 뜻처럼 배열을 펼칠 수 있습니다. 펼치는 방법은 배열이 할당된 변수명 앞에 ...을 붙여주면 됩니다.
배열을 펼치면 배열의 각 요소를 확인할 수 있습니다.

let arr = [0, 1, 2, 3];

console.log(...arr); // 0 1 2 3

spread syntax로 배열을 복사하기 위해서 배열을 생성하는 방법을 이해해야 합니다. 만약 같은 요소를 가진 배열을 두 개 만든 후 변수에 각각 할당한다면, 두 변수는 같은 주소를 참조할까요? 참조 자료형이기 때문에 각각 다른 주소를 참조합니다.

그렇다면 새로운 배열 안에 원본 배열을 펼쳐서 전달하면 어떻게 될까요? 원본 배열과 같은 요소를 가지고 있지만 각각 다른 주소를 참조하게 됩니다. 결과적으로 slice() 메서드를 사용한 것과 동일하게 동작합니다.

Object.assign()
객체를 복사하기 위해서는 Object.assign()을 사용합니다.

spread syntax
spread syntax는 배열뿐만 아니라 객체를 복사할 때도 사용할 수 있습니다.

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

이처럼 slice(), Object.assign(), spread syntax 등의 방법으로 참조 자료형을 복사하면, 중첩된 구조 중 한 단계까지만 복사합니다. 이것을 얕은 복사(shallow copy)라고 합니다.

깊은 복사
반면, 참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것은 깊은 복사(deep copy)라고 합니다. 그러나 JavaScript 내부적으로는 깊은 복사를 수행할 수 있는 방법이 없습니다. 단, JavaScript의 다른 문법을 응용하면 깊은 복사와 같은 결과물을 만들어 낼 수 있습니다.

JSON.stringify()와 JSON.parse()
JSON.stringify()는 참조 자료형을 문자열 형태로 변환하여 반환하고, JSON.parse()는 문자열의 형태를 객체로 변환하여 반환합니다. 먼저 중첩된 참조 자료형을 JSON.stringify()를 사용하여 문자열의 형태로 변환하고, 반환된 값에 다시 JSON.parse()를 사용하면, 깊은 복사와 같은 결과물을 반환합니다.

간단하게 깊은 복사를 할 수 있는 것처럼 보이지만, 이 방법 또한 깊은 복사가 되지 않는 예외가 존재합니다. 대표적인 예로 중첩된 참조 자료형 중에 함수가 포함되어 있을 경우 위 방법을 사용하면 함수가 null로 바뀌게 됩니다. 따라서 이 방법 또한 완전한 깊은 복사 방법이라고 보기 어렵습니다.

외부 라이브러리 사용
완전한 깊은 복사를 반드시 해야 하는 경우라면, node.js 환경에서 외부 라이브러리인 lodash, 또는 ramda를 설치하면 됩니다.

summary
배열의 경우 slice() 메서드 또는 spread syntax 등의 방법으로 복사할 수 있다.
객체의 경우 Object.assign() 또는 spread syntax 등의 방법으로 복사할 수 있다.
위 방법으로 참조 자료형을 복사할 경우, 중첩된 구조 중 한 단계까지만 복사된다. (얕은 복사)
JavaScript 내부적으로는 중첩된 구조 전체를 복사하는 깊은 복사를 구현할 수 없다. 단, 다른 문법을 응용하여 같은 결과물을 만들 수 있다.
대표적인 JSON.stringify()와 JSON.parse()를 사용하는 방법이 있지만, 예외의 케이스가 존재한다. (참조 자료형 내부에 함수가 있는 경우)
완전한 깊은 복사를 반드시 해야 하는 경우, node.js 환경에서 외부 라이브러리인 lodash, 또는 ramda를 사용하면 된다.

profile
안녕하세요

0개의 댓글