[생활코딩] JavaScript Immutability - 1. 변수 할당 방식 비교

full_accel·2021년 1월 17일
0

JavaScript Immutability

목록 보기
1/3
post-thumbnail

생활코딩의 JavaScript Immutability 강의를 정리하여 작성한 내용입니다.


1. 변수 할당 방식 비교

  • JavaScript의 변수는 크게 Primitive 타입과, Object 타입으로 나뒨다.
  • JavaScript 내부적으로 Primitive 타입 변수와 Object 타입 변수를 메모리에 저장하는 방식이 다르다.

1-1. 초기 값의 비교

a===b에서 ===(동등비교연산자)가 truereturn한다는 의미는 a와 b가 메모리상의 같은 공간(같은 값)을 가리킨다는 의미이다.

var p1 = 1 에서, 재할당하는 방법을 제외하면 p1에 담긴 1을 변경할 방법은 없다.

반면에
var o1 = {name: 'kim'}에서, 재할당하지 않고도 o1에 담긴 {name:'kim'}을 변경할 수 있다.

o1.name = 'park'

var p1 = 1;  // primitive type
var p2 = 1;
console.log(p1, p2, p1===p2); // 1 1 true

var o1 = {name:'kim'};  // object
var o2 = {name:'kim'};
console.log(o1, o2, o1===o2); // {name: "kim"} {name: "kim"} false

1-2. 객체의 가변성

새로 변수 var p3 = p1을 선언하면 p1, p2와 동일한 메모리상의 주소를 바라보고 결과적으로 같은 값( number 1)을 가지게 된다.

변수 p3에 2라는 값을 재할당하면 p3는 이제 p1, p2와는 다른 메모리 공간을 바라보고 다른 값(number 2)를 가진다.

var o3 = o1로, var o3를 선언하면서 변수 o1을 할당하면 o3o1과 동일한 메모리공간을 바라보고 동일한 값({name:'kim'})을 가지게 된다.

★ 여기가 하일라이트, 심장이 쫄깃해지는 포인트

var o3 = o1으로 o1을 할당한 변수 o3.(점) 찍고 접근하여 프로퍼티 name의 값을 'lee'로 수정하면 o3가 바라보고 있던 메모리공간에 담겨있는 {name:'kim'}{name:'lee'}로 변경된다.

문제는 동일한 메모리공간을 바라보고 있는 가만히 있던 변수 o1의 값 또한 {name:'lee'}로 변경된다는 것이다.

o1 입장에서는 황당하다. 자신은 아무것도 하지 않았는데 자신의 값이 변경되었다! (더글라스 크락포드 옹께서 왜 책 제목을 「자바스크립트는 왜 그 모양일까?」로 지었는지 알 것도 같다...)

1-3. 객체의 복사

변수 o1이 본래 가지고 있던 값을 "불변(immutable)하게 하려면 어떻게 해야 하는가?
o1의 값을 복사하여 그 값을 다른 변수에 할당한다.

var o1 = {name:'kim'};
var o2 = Object.assign({}, o1); // 간단한 Object.assign 사용법 참조
o2.name = 'lee';
console.log(o1, o2, o1 === o2); // {name: "kim"} {name: "lee"} false

간단한 Object.assign 사용법

Object.assign(target, ...sources)

  • target: 대상 객체, 리턴될 객체
  • sources: 하나 이상의 출처 객체, 대상 객체와 합쳐질 객체
  • 반환 값: target(대상 객체)
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };

const returnedTarget = Object.assign(target, source1, source2);

console.log("target: ", target);  // target:  {a: 1, b: 2, c: 3}

console.log("returnedTarget: ", returnedTarget);  // returnedTarget:  {a: 1, b: 2, c: 3}

참조

MDN web docs: Object.assign()

1-4. 중첩된 객체의 복사

Nested object

객체는 프로퍼티로 구성되어 있는데, 그 프로퍼티의 값 중 하나 이상이 객체인 경우 발생할 수 있는 문제.

var o1 = { name:'kim', score:[1, 2] } 로 변수 var o1의 프로퍼티 name에 primitive 타입인 문자를 담고, 프로퍼티 score에 object 타입인 배열 [1, 2]를 담은경우:

  • 메모리 공간에 { name:'kim', score: (배열 [1, 2]가 저장된 메모리 주소) } 가 저장된다.
  • primitive 타입인 문자열을 담은 프로퍼티는 변수 var o1이 바라보는 메모리에 바로 저장된다.
  • object 타입인 배열을 담은 프로퍼티에 실제로 담긴 것은 배열 자체가 아닌 배열이 저장된 다른 메모리 공간의 주소이다. 프로퍼티는 이 주소를 "참조"한다.

변수 var o1의 값을 변수 var o2에서도 사용하고자 Object.assign()으로 o2에 할당한 경우:

  • var o2 = Object.assign({}, o1)
  • o2가 바로보는 메모리 공간에 o1에 저장되어 있던 값인
    { name:'kim', score: (배열 [1, 2]가 저장된 메모리 주소) }가 그대로 복사 된다.
  • 변수 var o2에 할당된 객체의 프로퍼티 score는 배열 [1, 2]에 대해서 변수 var o1에 할당된 객체의 프로퍼티 score가 참조하고 있는 메모리 주소를 참조하고 있다.
  • 즉, 개발자는 복사를 의도하였지만 배열 [1, 2]는 메모리 상에 하나만 존재하는 상태
  • 변수 var o2에서 .(점) 찍고 score 프로퍼티에 접근하여 값을 변경하면 변수 var o1score 프로퍼티의 값도 변경된다.

o2.score.psuh(3) 으로 변수 var o2의 프로퍼티 score의 값을 변경하여 가만히 있던 변수 var o1의 프로퍼티 score의 값이 변경되는 경우

복제하려는 객체의 프로퍼티 값 중에 객체가 있고, 복제된 객체의 수정으로 원본객체의 수정이 발생하지 않게 하려면(즉, 원본 객체의 불변성을 유지하려면) 복제 대상 객체의 프로퍼티의 객체 또한 복제를 해야 한다.

o2.score = o2.score.concat() 으로 프로퍼티 score의 값인 배열까지 복제를 하면 o2.score.push(3)로 프로퍼티 score의 값을 변경하여도 변수 var o1의 프로퍼티 score의 값에는 영향이 없다.(원본이 불변이다, 원본이 immutable 하다.)

배열에 대해서 Object.assign을 사용하면 배열의 기능들이 사라진다. 배열의 경우에는 concat(), slice(), Array.from() 등 복제를 하는 명령들을 사용.

간단한 concat 사용법

concat() 메서드는 인자로 주어진 배열이나 값들을 기존 배열에 합쳐서 새 배열을 반환:

  • 기존 배열을 변경하지 않음
  • 추가된 새로운 배열을 반환
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);

console.log(array3); // ["a", "b", "c", "d", "e", "f"]

참조

MDN web docs: Array.prototype.concat()

객체를 복사했지만, 객체 내부의 프로퍼티가 가진 객체(배열)는 복사되지 않은 경우

var o1 = {name:'kim', score:[1,2]};
var o2 = Object.assign({}, o1);
console.log(o1, o2, o1===o2, o1.score===o2.score); 
// {name: "kim", score: Array(2)} {name: "kim", score: Array(2)} false true

객체 내부의 프로퍼티가 가진 객체(배열)까지 완전히 복사한 경우

var o1 = {name:'kim', score:[1,2]};
var o2 = Object.assign({}, o1);
o2.score = o2.score.concat();
o2.score.push(3);
console.log(o1, o2, o1===o2, o1.score===o2.score);
// {name: "kim", score: Array(2)} {name: "kim", score: Array(3)} false false
profile
스스로 배운 것이 오래 간다.

0개의 댓글