본문 바로가기

JS

(자바스크립트) 배열에서 데이터의 불변성, immutable & mutable

immutable 하게 리턴하는 Array 내장함수에는 arr.slice, arr.concat 등이 있고

mutable 하게 리턴하는 것들에는 arr.push, arr.pop, arr.shift, arr.unshift, arr.splice 등이 있다.

 

데이터 불변성 여부를 따져야 하는 경우

배열 관련한 알고리즘 문제를 풀다보니 처음 입력받은 데이터를 변경하지 말아야 하는 경우

혹은 데이터를 변경해되 괜찮은 경우들이 종종 보인다.

 

 

예를 들면 주의 사항에 아래와 같은 것들이 있다.

 

  1. 기존 배열에 주어진 요소가 추가된 상태(주소값 동일)로 리턴해야 합니다.
  2. 입력받은 배열을 수정하지 않아야 합니다(immutability).

1번 문구의 의미는 기존 배열에 새로운 요소가 추가되었기에 처음의 배열 데이터는 수정이 되어서

리턴해야 한다는 것으로 처음 배열 데이터는 변경이 가능하다는 의미이다.

2번 문구는 위와 반대로 처음 입력받은 배열은 수정하지 않고 새로운 배열을 만들어서 처리를 해야

한다는 것으로 처음 배열 데이터는 변경이 불가하다는 의미이다.

 

 

이런 경우 처음에 입력받은 배열에 요소를 추가하게 되면  기존의 배열은 수정이 되게 된다. (mutable)

그런데 기존의 배열은 수정하지 않고 새로운 배열을 만들어야 하는 경우도 있다. (immutable)

 

원시 자료형과 참조 자료형 그리고 불변성

자바스크립트에서 데이터는 원시 자료형과 참조 자료형으로 구분된다.

(용어 정리 - 원시 자료형 데이터는 기본형 데이터로, 참조 자료형 데이터는 참조형 데이터라고도 불린다.)

 

원시 자료형 데이터(숫자, 문자열, boolean, null, undefined, symbol)는 불변의 값이다.

원시 자료형 데이터 외에 모든 데이터의 자료형은 object(객체)이다. 즉 변경이 가능한 값이다.

 

반면 참조 자료형 데이터(객체, 배열)의 기본적인 성질은 가변의 값이지만 시스템에서 해당 자료의 쓰임에 따라

함수에 의해 불변하게 처리되거나 가변하게 처리되어야 하는 경우들이 있다. 

 

그런데 위와 같은 가변적인 참조 자료형은 데이터 처리시에 더더욱 주의를 기울어야 한다.

왜냐하면 시스템 안에서 코드를 따라 쓰여진 참조 자료형의 데이터의 요소가 변경되어

시스템 전체에 영향을 줄 수 있기 때문이다. 

 

좀더 전문가 답게 이야기 하자면  “레퍼런스를 참조한 다른 객체에서 객체를 변경”하기 때문이다.

그렇기 때문에 우리는 현재 내가 참조하고 있는 참조형 데이터의 불변성 여부를 늘 체크해야 하는 것이다.

 

원시 자료형 데이터에서 데이터의 불변성

let str = 'The developer as top-tier';
str = 'not a top, just second-tier';

첫번째 코드가 실행되면

문자열(string) 'The developer as top-tier'은

메모리(memory)에 생성(이를 할당이라고 한다)되고

변수명(혹은 식별자[identifier]) str은

문자열 'The developer as top-tier'의 메모리에서의 주소를 가리킨다.

 

그리고 두번째 코드가 실행되면

변수명 str에 있는 'The developer as top-tier'가 수정되는 것이 아니고

새로운 문자열 'not a top, just second-tier'이 메모리에 할당되고

변수명 str은 새로운 문자열을 가리키는

주소가 되는 것이다.

 

두 개의 string 데이터는 모두 메모리에 존재하고 있다. 

변수명 str은 가리키는 주소만 변경된 것이다.

이처럼 원시 자료형은 데이터 자체는 불변하다. 

 

참조 자료형 혹은 객체에서 데이터의 불변성

우리는 지금 배열 데이터에서의 데이터의 불변 여부를 살펴 보고 있다.

앞에서 참조 자료형 데이터는 기본적으로 가변의 값이라고 했다.

 

그러나 배열 데이터에서 데이터 자체를 변경하지 않고

배열 데이터의 요소만 변경해야 하는 경우가 있다.

(메모리 영역으로 보자면 처음 배열 데이터가 메모리에 저장되고

새로운 배열 데이터가 다른 메모리에 저장되는 것이다.)

 

let arr = ['i', 'my', 'me', 'mine'];
let arrAno = arr.slice(0, 2);//'i', 'my';

위 코드에서 Array 내장함수(혹은 메소드) slice는

변수에 저장된 문자열을 변경한 것이 아니라

새로운 배열 데이터를 만들어서 메모리에 생성하고

변수명 arrAno로 식별하게 만든 것이다.

이렇게 번거롭게 한 이유는 문자열은 변경불가능하기

때문이다. (string is immutable)

 

배열에서 immutable 유지를 위해 - immutable 함수와 mutable 함수 혼용의 예

우선 짚고 넘어가야 하는 것은 아래와 같다.

immutable 하게 리턴하는 Array 내장함수에는 arr.slice, arr.concat 등이 있고

mutable 하게 리턴하는 것들에는 arr.push, arr.pop, arr.shift, arr.unshift, arr.splice 등이 있다.

그렇다면 위의 메소드들을 혼용해서 사용할 수도 있다는 것을 염두하고 코드를 짜도록 하자.

아래는 사례이다.

 

문제

배열과 요소를 입력받아 맨앞에 새로운 요소가 추가된 새로운 배열을 리턴해야 합니다.

주의 사항

  • 입력받은 배열을 수정하지 않아야 합니다(immutability).

 

function addToFrontOfNew(arr, el) {
  const newArr = arr.slice();
  newArr.unshift(el);
  return newArr;
}

간단하지만 새로운 방식을 구사한다.

조건은 기존에 입력 받은 배열은 수정하지 않는다 즉 immutable해야 한다.

arr.slice는 immutable 하다.

newArr로 가리키는 arr.slice의 배열은 기존의 배열 arr과는 다른

새로운 메모리에 생성된 배열이다.

 

새롭게 생성된 배열 newArr을 mutable한 newArr.unshift 함수로

앞쪽 인덱스에 추가해주었다. 이때 newArr은 배열 자체의 요소가

추가되면서 배열 데이터가 변경되었다.

 

마치며

지금까지 간략하게 데이터의 불변성과 배열(Arr) 내장 함수에서 

immutable & mutable 특징을 살펴 보았다.

 

참조형 자료 데이터는 객체라고도 불리고 이 객체의 데이터 성질은

기본적으로 가변의 값이다.

 

그런데 목적에 따라 배열의 값을 변경하지 않아야 할 때도 있다.

그러므로 목적에 맞는 함수를 사용하자.

 

안 그러면 전체 시스템에서 데이터가 의도치 않게

바뀌어서 처리되는 혼돈이 상황을 맞이하게 된다.