Post

불변성과 얕은 복사, 깊은 복사

자바스크립트는 동적으로 타입을 결정하는 언어로, 변수를 선언할 때 타입을 명시하지 않는다.

이러한 특성 때문에 자바스크립트는 원시타입과 참조타입 두 가지 종류의 데이터 타입을 제공한다.

원시, 참조 타입

1. 원시 타입(Primitive Type)

1. 원시 타입 종류

  • 숫자(number)

  • 문자열(string)

  • 불리언(boolean)

  • null

  • undefined

  • 심볼(symbol)

2. 원시 타입의 불변성

불변성은 사전적 의미로 변하지 않는 성질을 뜻한다. 즉, 한번 생성된 데이터가 메모리에서 변경되지 않는 것이 불변성이다.

변수에 새로운 원시값을 할당하려면 기존의 메모리 공간을 바꾸는 것이 아니라, 새로운 메모리 공간에 새로운 원시값을 할당해야 헌다.

1
2
3
4
5
6
7
let a = 1;
let b = a; // 과정 1;

b = 2; // 과정 2;

console.log(a); // 1 출력
console.log(b); // 2 출력
과정 1과정 2
imageimage

2. 참조 타입(Reference Type)

1. 참조 타입 종류

-객체(object)

-배열(array)

-함수(function)

2. 참조 타입의 가변성

가변성은 사전적 의미로 변하는 성질을 뜻한다. 즉, 한번 생성된 데이터가 메모리에서 변경될 수 있는 것이 가변성이다.

객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다. 객체 데이터 자체를 재할당 없이 내부 프로퍼티를 변경,추가가 가능하다.

1
2
3
4
5
6
7
let a = { x: 1 };
let b = a; // 과정 1;

b.x = 2; // 과정 2;

console.log(a); // {x: 2} 출력
console.log(b); // {x: 2} 출력
과정 1과정 2
imageimage

위와 같이 a,b 모두 M1을 참조하며 b.x=2로 새로운 값 2가 M3에 할당되어 M1이 {x: M3}로 변경된다.

따라서 a, b출력 시 {x: 2}가 출력된다.

가변성은 다양한 상황에서 유용하지만, 예기치 않은 부작용(side effects)을 일으킬 수도 있다. 이러한 이유로, 함수형 프로그래밍에서는 불변성(immutability)을 선호한다.

  • 예기치 않은 부작용
    위와 같이 b속성의 값을 변경하려 했는데 원본 객체인 a의 속성까지 바뀌는 현상

불변성을 지키기 위해 참조 타입은 얕은 복사, 깊은 복사를 이용한다.




얕은 복사, 깊은 복사

1. 얕은 복사

참조형의 1차원 데이터만 복사한다.

1차원 데이터라는 것이 무엇인지 예제를 통해 살펴보겠다.

얕은 복사 1. Object.assign()

Object의 assign메소드를 통해 객체 데이터의 속성을 복사하여 새로운 데이터를 만든다.

1
2
3
4
5
6
7
let a = { x: 1 };
let b = Object.assign({}, a); // 과정 1

b.x = 2; // 과정 2;

console.log(a); // {x: 1} 출력
console.log(b); // {x: 2} 출력

얕은 복사 2. 전개 연산자(…)

새로운 객체를 생성하고 그 내부에서 전개 연산자로 a변수 작성

1
2
3
4
5
6
7
let a = { x: 1 };
let b = { ...a }; // 과정 1

b.x = 2; // 과정 2;

console.log(a); // {x: 1} 출력
console.log(b); // {x: 2} 출력
과정 1과정 2
imageimage

위의 예제는 1차원 데이터였기 때문에 얕은 복사를 해도 문제가 되지 않았다.

그렇다면 2차원 데이터일 경우에 얕은 복사를 하면 어떻게 되는지 알아보자.

1
2
3
4
5
6
7
let a = { x: { y: 1 } };
let b = { ...a }; // 과정 1

b.x.y = 2; // 과정 2;

console.log(a); //  { x: { y: 2 } } 출력
console.log(b); //  { x: { y: 2 } } 출력
과정 1과정 2
imageimage

a, b가 참조하는 메모리는 다르기 때문에 x의 값을 변경하는 것은 문제가 되지 않으나 a.x, b.x는 같은 메모리를 참조하고 있기 때문에 y값 변경 시 M2의 y가 M5로 변경되면서 a.x.y과 a.x.y가 같아지게 된다.

얕은 복사는 결과적으로 하나의 껍데기만 복사를 하는 것이기 때문에 1차원 데이터만 복사가 된다고 이야기 할 수 있다.

완전히 다른 데이터로 a와 b를 분리하기 위해서는 a, b 가 중간에 서로 연결된 부분을 완전히 해제해야한다.

그렇게하기 위해서는 깊은 복사가 필요하다.


2. 깊은 복사

참조형의 모든 차원 데이터를 복사한다.

깊은 복사 1. lodash의 cloneDeep

1
2
3
4
5
6
7
8
9
import cloneDeep from "lodash/cloneDeep";

let a = { x: { y: 1 } };
let b = cloneDeep(a); // 과정 1

b.x.y = 2; // 과정 2;

console.log(a); // { x: { y: 1 } } 출력
console.log(b); // { x: { y: 2 } } 출력
과정 1과정 2
imageimage

1차원의 객체 데이터를 복사하여 M4에, x가 바라보고 있었던 M2에 해당하는 객체 데이터를 복사하여 M5에 만들어준다.

위 처럼 기존 값의 모든 참조가 끊어졌다. 이렇게 깊은 복사를 사용하면 복사했을 때 값은 동일하지만, 객체 내부의 값을 변경해도 서로 영향을 주지 않고 격리된 값을 보장한다.




📑 참고 자료

JavaScript의 얕은 복사와 깊은 복사

프론트엔드 개발/JavaScript자바스크립트) 원시타입 VS 참조타입 / 얕은복사 VS 깊은 복사

[Javascript] 원시타입 vs 참조타입

This post is licensed under CC BY 4.0 by the author.