Immutability (불변성) 포스팅 리스트

- Immutability (불변성) 에 대한 정리 - 1

- Immutability (불변성) 에 대한 정리 - 2

- Immutability (불변성) 에 대한 정리 - 3

서두

한달전 쯤 Shallow Copy Deep Copy 에 대해 공부하고 정리하여 포스팅 한적이 있습니다.

 

생활코딩을 보며 Redux를 공부를 할려다 immutability 에 대해 아는것이 좋다고 하길래, immutability 즉 불변성에 대해 개발하며 자주 들어봤지만 크게 관심을 가지지 않았던터라 이번에 공부를 해두는게 좋다고 하여 이렇게 정리하게 되었습니다.

 

Immutability 이란 무엇인가?

imutability란 불변성 이라고 불립니다.

 

어원을 한번 따라가봅시당

mutate - 변화

mutable - 변화 가능한

mutability - 변화가 될수 있는

immutability - 변화가 될 수 없는

 

이렇게 하여 변화가 될 수 없다, 즉 불변성 이라는 의미를 가지게 됩니다.

 

let and const

let a = 1, const b = 1
a = 2

 

b = 2

let   a = 1;
const b = 1;

a = 2;
b = 2;  // ERROR :: Assignment to constant variable

console.log(a, b);

js의 변수와 상수에 대해 봅시다.

 

let 는 변수 입니다, 그래서 a 변수에 1을 넣은 뒤 a 변수에 2롤 대입하면 2로 바뀝니다.

const 는 상수 입니다, 그래서 b 상수에 1을 넣은 뒤 b 변수에 2를 대입하면 에러가 나게 됩니다.

 

여기서 알수있듯이

변수변하는 상자,

상수변하지 않는 상자 라고 볼수있습니다.

 

이로써 상수와 같은 경우를 불변성 이라고 할수있겠네요.

 

let 과 const에 대한 이야기는 다음에 따로 포스팅 하여 올리겠습니다.

 

Data Type

프로그래밍 공부하다 보면 데이터 타입에 대한 이야기를 많이 듣게 됩니다.

 

JavaScript에는 Primitive (원시 타입) Object (참조 타입) 두가지가 있습니다.

 

Primitive Object
Number Object
String Array
Boolean Function
Null  
undefined  
Symbol  

 

간단한 예제를 보겠습니다.

let p1 = 1;
let p2 = 1;

console.log("p1: ", p1);			// p1: 1
console.log("p2: ", p2);			// p2: 1
console.log("p1 === p2: ", p1 === p2);		// p1 === p2 : true

let o1 = { name: "kim" };
let o2 = { name: "kim" };

console.log("o1: ", p1);			// o1: 1
console.log("o2: ", p2);			// o2: 1
console.log("o1 === o2: ", o1 === o2);		// o1 === o2 : false

p1 그리고 p2는 primitive 의 number 타입 입니다.

그래서 p1 과 p2 의 값이 같다고 나옵니다.

 

o1 그리고 o2는 object의 object 타입 입니다.

하지만 o1 과 o2 의 값이 다르다고 나옵니다.

 

그 이유에 대해 설명해보겠습니다.

Primitive Type (원시 타입)

let p1 = 1;
let p2 = 1;
let p3 = p1;

p3 = 2;

console.log(p1 === p2); 	// true
console.log(p1); 			// 1
console.log(p3);			// 2

p3 = p1
p3 = 3

기본적으로 primitive 는 원시 타입이기 때문에 p1 과 p2 를 서로 비교했을때 결과는 무조건 true가 나오게 됩니다.

그리고 p3 변수에 p1 변수를 대입하게 되면 1 이라는 값을 가지게 되고

p3 변수에 다시 2라는 값을 대입하게 되면 2가 되게됩니다.

 

Object Type (객체 타입)

let o1 = { name: "kim" };
let o2 = { name: "kim" };

console.log(o1 === o2); // false

하지만 object 인 경우 값이 아닌 주소값을 가지고 있기 때문에 o1 과 o2 를 서로 비교했을때 false가 나오게

됩니다.

let o3 = o1
o3.name = "Lee"

let o1 = { name: "kim" };
let o2 = { name: "kim" };
let o3 = o1;

o3.name = "Lee";

console.log(o1 === o3);		// true
console.log(o1);			// { name: 'Lee'}
console.log(o3);			// { name: 'Lee'}

이제 o3 변수를 생성하여 o1를 대입해보겠습니다.

그럼 o3 변수는 o1과 같은 객체가리키고 있습니다.

 

주소를 가리키고 있기 때문에 만약 o3에서 속성값인 name이라는 값을 변경하게되면 같은 주소를 공유하고

있는 o1변수에도 똑같이 데이터가 변하게 됩니다.

 

의도 하였다면 아주 좋은 용도 이지만 만약 이게 의도되지 않은 버그가 된다면 프로그램에 아주 치명적이게

됩니다.

 

오늘은 letconst 의 차이, 그리고 primitive 그리고 object 데이터 타입에 대해 알아보았습니다.

 

다음 포스팅에선 object 객체를 컨트롤할때 원본 데이터가 변하지 않게 즉 immutability (불변성) 을 가질수 있는 방법이 어떠한것들이 있는지에 대해 알아보겠습니다.

 

COPY

프로그래밍을 공부하다보면 shallow copy(얕은 복사) deep copy(깊은 복사)를 경험하신적이 있으실것입니다.
c언어 에서는 포인터, java에서는 reference variable 등등 을 관리하고 데이터 처리를 할때 많이 만나게 됩니다.
대표적인 예를 javascript 에서 확인해보겠습니다.

 

Example ) Shallow Copy - 1

// A 라는 사람이 메모장을 쓴다고 했을때 가정을 해봅시다.
const memoA = { author: 'Person A', content: 'plan to make a coffee' };

// 그리고 B 라는 사람이 A 라는 사람의 메모장을 복사하여 받은 뒤 수정한다고 해봅시다.
const memoB = memoA;

// B라는 사람은 author, content 의 내용만 지우고 B 사람의 이름과 메모를 적습니다.
memoB.author  = 'Person B';
memoB.content = 'take a bus';

// memoA의 값 까지 바뀌는 현상이 발생 했습니다.
console.log('memoA, ', memoA); // memoA, {author: 'Person B', content: 'take a bus'}
console.log('memoB, ', memoB); // memoA, {author: 'Person B', content: 'take a bus'}

// memoA 와 memoB가 같다고 나옵니다.
console.log(memoA === memoB)   // true

// 이러한 현상을 reference copy (주소 복사), shallow copy(얕은 복사) 라고 하는데 왜 이런 문제가 발생할까요?
const aVar = 10;

상수로 aVar 데이터를 만들고 10의 값을 넣는다면 다음과 같이 aVar 변수 안에 10이라는 데이터가 존재합니다.

하지만 reference 데이터들인 Object.. Array.. 인 경우에는 다르게 동작을 합니다.

 

const memoA = {author: 'Person A', content: 'Plan to make a coffee'};

바로 객체의 주소를 가진다는 점입니다.

자바로 예를 든다면 참조 변수, 참조 한다고 합니다

const memoB = memoA;

그래서 만약 memoB 변수를 만들고 memoA 변수를 바로 대입을 하여 복사를 한다면 shallow copy (얕은 복사) 가 되어버립니다.

memoB.author  = 'Person B';
memoB.content = 'Take a bus';

만약 이 상황에서 memoB의 객체 프로퍼티의 값을 변경한다면 어떻게 될까요?

memoB의 객체 프로퍼티 값들이 변경 되면서 memoA 또한 같은 주소를 가지고 있기 때문에 memoB만 변경을 했을뿐인데 memoA 까지 데이터가 변하게 된 이유 입니다.

 

이걸 방지하기 위해선 Deep Copy (깊은 복사) 를 해줘야 합니다.

 

Example ) Shallow Copy - 2

const memoA = { author: 'Person A', content: 'plan to make a coffee' };
const memoB = {...memoA};

memoB.author  = 'Person B';
memoB.content = 'take a bus';

console.log('memoA, ', memoA); // memoA, {author: 'Person A', content: 'plan to make a coffee'}
console.log('memoB, ', memoB); // memoB, {author: 'Person B', content: 'take a bus'}

console.log(memoA         === memoB)         // false
console.log(memoA.author  === memoB.author)  // false
console.log(memoA.content === memoB.content) // false

내가 생각한 해결 방법 1. [ ES6 ] Spread Operator (스프레드 연산자) - Deep Copy가 아닙니다.

ES6 에서 나온 Spread Operator 을 사용하여 Deep Copy(깊은 복사)를 구현 할 수 없습니다.

 

Spread Operator 을 이용한다면 1차원 배열 or 객체만 데이터 복사가 됩니다.

다차원 배열, 객체로 들어간다면 Shallow Copy(얕은 복사)가 되어 버립니다.

 

const memoA = { 
  author: 'Person A', 
  content: ['one', 'two', 'three', 'four', 'five'] 
};

const memoB = {...memoA};

memoB.author  = 'Person B';
memoB.content[1] = 'a';

console.log('memoA, ', memoA); // memoA, {author: 'Person A', content: ['one', 'a', 'three', 'four', 'five']}
console.log('memoB, ', memoB); // memoB, {author: 'Person B', content: ['one', 'a', 'three', 'four', 'five']}

console.log(memoA         === memoB)         // false
console.log(memoA.author  === memoB.author)  // false
console.log(memoA.content === memoB.content) // true

이렇게 1차원적인 객체 또는 배열은 원시 데이터로 복사가 되지만 다차원 배열 또는 객체의 경우 값이 변경이 되어버립니다.

Example ) Deep Copy

해결 방법 1. JSON 메소드 이용하기

const memoA = { 
  author: 'Person A', 
  content: ['one', 'two', 'three', 'four', 'five'] 
};

const memoB = JSON.parse(JSON.stringify(memoA));

memoB.author  = 'Person B';
memoB.content[1] = 'a';

console.log('memoA, ', memoA); // memoA, {author: 'Person A', content: ['one', 'two', 'three', 'four', 'five']}
console.log('memoB, ', memoB); // memoB, {author: 'Person B', content: ['one', 'a', 'three', 'four', 'five']}

console.log(memoA         === memoB)         // false
console.log(memoA.author  === memoB.author)  // false
console.log(memoA.content === memoB.content) // false

 const memoB = JSON.parse(JSON.stringify(memoA));  이런식으로 JSON 메서드를 이용하여 string으로 바꾼다음 다시 json.parse로 바꿔서 원시 데이터 처럼 데이터를 복사 할 수 있습니다.

 

해결 방법 2. lodash

import _ from 'lodash';

const memoA = { 
  author: 'Person A', 
  content: ['one', 'two', 'three', 'four', 'five'] 
};

const memoB = _.cloneDeep(memoA);

memoB.author = 'Person B';
memoB.content[1] = 'a';

console.log('memoA, ', memoA); // memoA, {author: 'Person A', content: ['one', 'two', 'three', 'four', 'five']}
console.log('memoB, ', memoB); // memoB, {author: 'Person B', content: ['one', 'a', 'three', 'four', 'five']})

console.log(memoA         === memoB)         // false
console.log(memoA.author  === memoB.author)  // false
console.log(memoA.content === memoB.content) // false

자바스크립트에서 유명한 라이브러리중 하나인 lodash 를 이용하여 Deep Copy를 지원하는 메소드가 있습니다.

cloneDeep(); 이라는 메소드 입니다.

lodash는 _ 이라는 걸로 불러와서 많이들 쓰는것 같아서 _로 불러왔고 _.cloneDeep() 하여 인자에 복사할 데이터를 넣게 되면 Deep copy 를 할수있습니다.

 

Github: https://github.com/GangOn0215/dev-til/blob/main/JavaScript/Copy.md

+ Recent posts