자바스크립트 동등 비교

gonn-i·2025년 3월 7일

해당 게시글은 모던 리액트 딥다이브를 읽고, 작성한 정리글입니다.

1.1 자바스크립트의 동등 비교

리액트 컴포넌트 랜더링이 일어나는 이유 중 하나가, props의 동등 비교에 따른 결과이다.
props의 동등 비교는 객체의 얕은 비교를 기반으로 이뤄지기 때문에 중요하다.

(1) 리액트의 가상 DOM과 실제 DOM 비교,
(2) 리액트 컴포넌트가 랜더링할지 판단하는 방법,
(3) 변수나 함수의 메모이제이션 등 거의 모든 작업이 동등 비교를 기반으로 이루어진다.

1.1.1 자바스크립트의 데이터 타입

JS 에서 타입은 크게 원시타입과 객체타입으로 나눠지며, 두 타입을 기준으로 하위엔 다음과 같이 나눠볼 수 있다.

  • 원시 타입(primitive type) : 객체가 아닌 모든 타입
    • undefined

      • 선언 이후에 임의의 값을 할당하지 않는 경우, 자동으로 undefined 가 할당된다. 즉, 선언되었지만 할당되지 않은 값을 의미
    • null

      • 아직 값이 없어거나 비어있는 경우로, 명시적으로 비어있음을 나타내는 값을 의미
      • typeof 에 null 타입이 아닌 object로 반환된다고 한다 (이후 null 반환 시도가 이뤄졌으나 호환성 문제로 실패)
    • boolean

      • falsy 로 표현되는 값

        타입설명
        falseBooleanfalse는 대표적인 falsy한 값
        0, -0, 0n, 0x0nNumber, BigInt0은 부호나 소수점 유무에 상관없이 falsy
        NaNNumberNaN(Not a Number)은 falsy한 값
        "" / '' / ``String문자열이 falsy하기 위해서는 반드시 공백이 없는 빈 문자열이어야 함
        nullnullnull은 falsy한 값
        undefinedundefinedundefined는 falsy한 값
      • truthy 로 표현되는 값 : falsy가 아니면 참으로 판명. 특히, 객체와 배열은 내부 값의 존재 여부과 관계업이 모두 truthy ([] {})

    • number

      • 다른 언어들과는 다르게, 정수와 실수를 구분하지 않는다.
        			// ECMAScript 표준 기준 표현가능했던 범위 (bigInt 등장전)
        const maxinteger = Math.pow(2, 53)
        maxinteger - 1 === Number.MAX_SAFE_INTEGER // true
        const mininteger = -(Math.pow(2, 53) - 1)
        mininteger === Number.MIN_SAFE_INTEGER // true
    • bigint

      • number 형의 제한을 극복하기 위해 등장한, 타입

        // 사용할땐, 숫자 끝에 n을 붙여주거나 BigInt 함수를 이용한다.
        const number = 9007199254740992
        const bigint = 9007199254740992n
        
        typeof number // number
        typeof bigint // bigint
        number == bigint // true
        number === bigint // false(타입이 달라서)
    • string

      • 한쌍의 작은 따옴표('), 혹은 큰 따옴표("), 또는 템플릿 리터럴 표현방식인 백틱 (`) 으로 표현된다.
        (템플릿 리터럴: 줄바꿈이 가능하고, 문자열 내부에 표현식을 쓸 수 있음)
      • 문자열은 원시타입이라, 변경 불가! (b라는 변수에 foo 넣어두고 b[0]='H' 이런식으로 변경할 수 없음)
    • symbol

      • 중복되지 않는 어떠한 고유값을 나타내기 위해서 만들어진 타입!
  • 객체 타입(object/reference type)
    • object (배열 / 함수 / 정규식 / 클래스 )
      • 객체 타입은 참조를 전달한다는 점이 특징!

1.1.2 값을 저장하는 방식의 차이

원시타입과, 객체타입의 큰 차이점 -> 값을 저장하는 방식

원시타입불변형태의 값으로 저장, 그래서 할당 시점에 메모리영역을 차지함

객체타입참조를 저장하고 있기에, 변경 가능한 형태로 저장됨

// 내부 형태, 값이 같아도 객체는 참조를 저장하기 때문에 두 함수는 동등 비교시, false! 
var hello = {
  greet: 'hello, world',
};

var hi = {
  greet: 'hello, world',
};

// 객체 간의 동등 비교는 false
console.log(hello === hi); // false

// 원시값인 내부 속성값은 같음을 알 수 있다.
console.log(hello.greet === hi.greet); // true

1.1.3 다른 비교 공식, Object.is

==Object.is 차이

== 비교는 강제로 형변환을 한후에, 타입이 다를경우 둘을 맞춰주고 비교가 이루어지는 반면, Object.is=== 와 같이 이러한 작업을 하지 않음 (타입이 다르면, 그냥 false)

===Object.is 차이

Object.is=== 와 달리, +0, -0을 구분한다.
NaNNumber.NaN 도 마찬가지 이며,
NaN0/0 도 구분하는 식의, 정밀 비교가 가능하다
(ES6에서 동등 비교 === 한계를 극복하기 위해 도입됨)

하지만 객체 비교에는 큰 차이가 없다!

1.1.4 리액트에서의 동등비교

리액트에서 사용되는 동등비교는 Object.is를 사용한다. 구형 브라우저의 경우 (ES6 미지원) polyfill을 사용한다고 한다.
(폴리필 코드: 최신 JavaScript 기능이 구형 브라우저에서 동작하지 않을 때, 이를 대신 구현하여 제공하는 코드)

// 폴리필 코드
function is(x: any, y: any) {
  return (
    // x === y 두 값이 완전히 같으면 기본적으로 true
    // x === 0인 경우 1 / x === 1 / y로 +0과 -0을 구별
    // NaN !== NaN이 항상 true이라 x와 y가 둘 다 NaN이라면 true를 반환
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
  );
}

const objectIs: (x: any, y: any) => boolean =
  typeof Object.is === 'function' ? Object.is : is;

리액트에서는 Object.is로 먼저 비교를 진행한 후에, 객체 간 얕은 비교를 한번 더 수행한다.
이때, 객체 간 얕은 비교란 객체의 첫번째 깊이에 존재하는 값만 비교하는 방법이다.

깊은 비교는 객체 내부의 모든 속성을 재귀적으로 확인해야 하지만, 이는 리소스가 많이 드는 일이다. 따라서, 객체에서 첫번째 deps의 키를 꺼내서, 비교하고자 하는 객체에 같은 키가 있는지, 그리고 그 값이 같은 지를 확인한다.

리액트는 객체 비교에 있어서 불안정성이 있기에, 위의 비교 방법은 리액트 개발자로서 알아두면 좋을 것 이다!! 뒤에서 나올, useMemo나 useCallback의 필요성을 활용하기 위해 잘 알아두자!

profile
https://gonnn-i.tistory.com/

0개의 댓글