HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
프로그래머스 프론트엔드 데브코스 2기
프로그래머스 프론트엔드 데브코스 2기
/
📰
CS 스터디
/
불변성

불변성

팀
끝
발표자
발표일
Sep 2, 2022
발표방법
당일 라이브

함수형 프로그래밍순수 함수불변성그렇다면 상태의 변경이란?const 키워드 변수원시 타입과 객체 타입원시 타입객체 타입그런데 불변성은 반드시 지켜야 하는가?지키면 좋은 점들이 있다일치 연산자 ===값에 의한 비교참조에 의한 비교만약 객체 타입에서 값에 의한 비교를 해야 한다면,만약 순환 참조 객체끼리 값에 의한 비교를 한다면,⇒ 불변성을 지켜 효율적인 일치 연산!불변성을 지키는 방법그런데 불변성이 무조건 옳은가?객체 지향 프로그래밍참고

 
 
 

함수형 프로그래밍


순수 함수

  • 수학의 함수를 프로그래밍으로 가져온 모델
  • 수학에서는 상태라는 개념이 없으므로, “상태를 변경한다”는 개념도 없음
  • 하지만 프로그래밍에서는 상태가 존재하며, mutation(변이)가 발생할 수 있음
    • ⇒ 그렇기에 함수형 프로그래밍에서는 불변성을 중요하게 여김
 

불변성

  • 상태를 변경하지 않는 것
 

그렇다면 상태의 변경이란?

  • 메모리에 저장된 값을 변경하는 모든 행위
    • let a; // 변수 선언으로 `a` 변수로 접근할 수 있는 메모리 공간을 마련 a = 1; // 마련된 메모리 공간에 `1`이라는 값을 할당 a = 2; // 메모리에 저장된 `1`값을 `2`라는 값으로 변경(재할당) = 상태 변경!
 

const 키워드 변수

  • const 키워드로 변수를 선언하여 재할당, 즉 상태 변경을 방어하여 불변성을 지키자! ⇒ ❌
 
 
 

원시 타입과 객체 타입


원시 타입

  • boolean null undefined number string symbol
  • 변경 불가능한 값 ⇒ 불변성 ⭕
    • const user = { name: "원시타입" }; const name = user.name; // 원시 타입 string 할당 user.name = "재할당"; console.log(name); // "원시타입" console.log(user.name); // "재할당"
  • 값에 의한 호출 방식
    • 새로운 메모리 공간에 값을 복사하여 할당한 후 함수로 넘기는 방식
 

객체 타입

  • Array Object 등등
  • 불변성이 깨질 수 있는 타입
  • 참조에 의한 호출 방식
    • 인자로 넘기는 변수가 가리키고 있는 메모리 공간의 주소를 함수로 넘기는 방식
  • 불변성을 지키고자 한다면, 객체 타입을 사용할 때에는 신경 써야 할 점들이 늘어남 😥
 
 
 

그런데 불변성은 반드시 지켜야 하는가?


지키면 좋은 점들이 있다

  • 무분별한 상태의 변경을 막아 상태를 추적할 수 있게 만듦 ⇒ 개발자 입장에서 디버깅이 쉬워짐!
  • 효율적인 일치 연산이 가능하여 참조 타입의 변화를 쉽게 알 수 있음
 
 

일치 연산자 ===


값에 의한 비교

  • 원시 타입에서의 일치 연산자
  • 값이 일치하는 지 확인
    • const 원시_1 = 1; const 원시_2 = 1; console.log(원시_1 === 원시_2); // true
 

참조에 의한 비교

  • 객체 타입에서의 일치 연산자
  • 참조가 일치하는 지 확인
    • const 객체_1 = { a: 1 }; const 객체_2 = { a: 1 }; console.log(객체_1 === 객체_2); // false

만약 객체 타입에서 값에 의한 비교를 해야 한다면,

  • 객체 타입은 값을 중첩할 수 있기에 얕은 비교가 아닌 깊은 비교를 해야 함
    • const valueEqual = (a, b) => { const a_keys = Object.keys(a); const b_keys = Object.keys(b); if (a_keys.length !== b_keys.length) return false; // 두 객체 간의 키 길이 비교 for (const key of a_keys) { if (!b_keys.includes(key)) return false; // 동일한 키를 가지는 지 확인 const a_value = a[key]; const b_value = b[key]; if (typeof a_value !== typeof b_value) return false; // 동일한 값 타입인지 확인 if (typeof a_value === "object") { return valueEqual(a_value, b_value); // 중첩 객체 값 비교를 위한 재귀 } return a_value === b_value; } }
      valueEqual 함수는 사실 깊은 비교를 정확하게 수행할 수 없다. 예외 조건이 있어 진정한(?) 깊은 비교는 코드가 더 복잡해진다 (ex. valueEqual({ a: Number.NaN }, { a: Number.NaN }); // false)
 

만약 순환 참조 객체끼리 값에 의한 비교를 한다면,

  • 무한 재귀 호출 😥
const a = {}; const b = {}; a.c = b; b.c = a; valueEqual(a, c);
notion image
 

⇒ 불변성을 지켜 효율적인 일치 연산!

  • 객체 타입의 경우 불변성을 지키지 않는다면,
    • 참조에 의한 비교로 객체가 변경 되었는지 확인할 수 없음 (값에 의한 비교로 변경 되었는지 확인 해야 함 😥)
      • const 객체_1 = { a: 2 }; const 객체_2 = 객체_1; 객체_1.a = 10000; // 상태 변경 = 불변성 훼손 console.log(객체_1 === 객체_2); // true
  • 불변성을 지킨다면,
    • 참조에 의한 비교로 객체가 변경 되었는지 확인할 수 있음
      • const 객체_1 = { a: 2 }; const 객체_2 = {...객체_1}; console.log(객체_1 === 객체_2); // false;
        사실 객체_1과 객체_2의 참조값만 변경 되었을 뿐 객체 값이 변경 되지는 않았다
 
 

불변성을 지키는 방법


  • 불변 객체로 만들기
    • Object.freeze, Object.seal
    • 중첩 객체는 불변 객체가 아님 😥
  • 방어적 복사를 통해 새로운 객체를 생성
    • Object.assign, 전개 연산자
    • 깊은 복사가 아닌 얕은 복사 😥
 
⇒ Immer.js 라이브러리나 Immutable.js 라이브러리를 사용하면 됨
 
 
 

그런데 불변성이 무조건 옳은가?


  • 불변성은 함수형 프로그래밍에서 중요한 개념일 뿐, 객체 지향 프로그래밍에서는 다른 관점!
 

객체 지향 프로그래밍

  • 상태를 private 접근 제한자로 외부에서 접근 및 변경할 수 없도록 관리하며, 메서드를 통해서만 상태를 변경할 수 있도록 설계
  • 만약 불변성을 지키기 위해 상태를 변경할 때마다 객체를 생성한다면, 객체 생성 비용에 따라 성능에 영향을 끼칠 수 있음
 
⇒ 상황에 맞게 잘 선택하자!
 
 
 

참고


  • https://evan-moon.github.io/2020/01/05/what-is-immutable/
  • https://poiemaweb.com/js-immutability
  • https://reactkungfu.com/2015/08/pros-and-cons-of-using-immutability-with-react-js/
  • https://simsimjae.tistory.com/346