현록

JavaScript ==와 === 차이

JavaScript의 =====는 두 값이 같은지 비교하고 boolean 값을 반환한다.

핵심 차이는 비교하기 전에 타입을 바꾸는지에 있다.

==는 타입이 다르면 규칙에 따라 값을 변환한 뒤 비교할 수 있다.

===는 암시적 타입 변환을 하지 않고 타입과 값을 함께 비교한다.

일반적인 애플리케이션 코드에서는 ===를 기본으로 사용하면 비교 과정을 예측하기 쉽다.

== 동등 연산자

==는 동등 연산자라고 부른다.

두 항의 타입이 같으면 대체로 ===와 비슷한 방식으로 값을 비교한다.

1 == 1 // true
'hello' == 'hello' // true
true == true // true

두 항의 타입이 다르면 JavaScript는 동등 비교 알고리즘에 따라 한쪽 또는 양쪽을 변환한다.

'1' == 1 // true
0 == false // true
'' == 0 // true
null == undefined // true

결과만 보면 다른 타입의 값이 같다고 판단되는 것처럼 보인다.

실제로는 비교 중에 숫자 변환 등이 일어난 결과다.

=== 일치 연산자

===는 일치 또는 엄격한 동등 연산자라고 부른다.

두 항의 타입이 다르면 값을 변환하지 않고 바로 false를 반환한다.

'1' === 1 // false
0 === false // false
'' === 0 // false
null === undefined // false

타입이 같으면 값을 비교한다.

1 === 1 // true
'hello' === 'hello' // true
true === true // true

코드를 읽는 사람은 어떤 타입 변환이 일어날지 추적할 필요가 없다.

비교할 값의 타입이 잘못되었다면 그 문제가 숨겨지지 않고 비교 결과에 드러난다.

문자열과 숫자 비교

HTML 폼에서 읽은 input.value는 숫자처럼 보여도 문자열이다.

<input id="quantity" type="number" value="3" />
const input = document.querySelector('#quantity')

console.log(input.value) // '3'
console.log(input.value == 3) // true
console.log(input.value === 3) // false

==를 쓰면 문자열 '3'이 숫자 3으로 변환되어 잘못된 타입을 눈치채지 못할 수 있다.

숫자로 다룰 값이라면 의도를 드러내도록 먼저 변환하는 편이 낫다.

const quantity = Number(input.value)

console.log(quantity === 3) // true

이렇게 하면 변환은 Number()에서, 비교는 ===에서 담당한다.

변환이 실패할 수 있다면 Number.isNaN() 등으로 결과를 검증해야 한다.

boolean과 숫자 비교

==에서 boolean이 다른 타입과 비교되면 true1, false0으로 변환될 수 있다.

true == 1 // true
false == 0 // true
false == '' // true

사용자가 입력한 값이 빈 문자열인데 false와 같다고 판단되면 조건문의 의도가 흐려진다.

const value = ''

if (value == false) {
  console.log('빈 값인지 false인지 알기 어려움')
}

빈 문자열인지 확인하려면 그 값을 직접 비교한다.

if (value === '') {
  console.log('빈 문자열')
}

boolean 조건이 필요하면 Boolean(value) 또는 조건문의 truthy와 falsy 규칙을 명확히 선택한다.

null과 undefined 비교

nullundefined==에서 서로 같다고 판단되는 특별한 경우다.

null == undefined // true
null === undefined // false

value == nullnullundefined를 둘 다 확인하는 짧은 패턴으로 의도적으로 쓰이기도 한다.

function hasValue(value) {
  return value != null
}

hasValue(null) // false
hasValue(undefined) // false
hasValue(0) // true
hasValue('') // true

이 패턴은 nullundefined만 한 번에 제외한다는 의도가 분명할 때 사용할 수 있다.

팀의 lint 규칙이 ==를 금지하거나 코드를 처음 읽는 사람이 오해할 수 있다면 두 조건을 직접 쓰는 편이 명확하다.

const isMissing = value === null || value === undefined

짧은 코드보다 팀이 합의한 읽기 방식을 우선한다.

빈 문자열과 배열의 변환

==의 객체와 원시값 비교에서는 객체가 원시값으로 변환될 수 있다.

[] == '' // true
[] == 0 // true
[1] == 1 // true

빈 배열 []은 문자열로 변환하면 ''이 된다.

그 다음 비교 대상에 따라 빈 문자열이 다시 숫자 0으로 변환될 수 있다.

이러한 결과는 규칙을 알면 설명할 수 있지만 업무 코드에서 즉시 의도를 읽기는 어렵다.

배열이 비었는지 확인하려면 길이를 직접 비교한다.

const items = []

if (items.length === 0) {
  console.log('빈 배열')
}

자동 변환 결과에 기대기보다 확인하려는 속성을 직접 쓰는 편이 낫다.

객체 비교

=====는 둘 다 객체의 내용을 자동으로 깊게 비교하지 않는다.

두 항이 객체라면 같은 객체를 참조하는지 확인한다.

const first = { id: 1 }
const second = { id: 1 }
const same = first

first == second // false
first === second // false
first === same // true

firstsecond는 속성과 값이 같지만 서로 다른 객체다.

내용을 비교하려면 필요한 속성을 직접 비교해야 한다.

const isSameUser = first.id === second.id

복잡한 객체라면 도메인에 맞는 비교 함수나 검증 라이브러리를 사용한다.

NaN 비교

NaN은 숫자 타입이지만 자신을 포함한 어떤 값과도 == 또는 ===로 같다고 판단되지 않는다.

NaN == NaN // false
NaN === NaN // false

값이 NaN인지 확인하려면 Number.isNaN()을 사용한다.

const result = Number('hello')

Number.isNaN(result) // true

전역 isNaN()은 먼저 숫자 변환을 할 수 있으므로 숫자 NaN을 정확히 확인하려면 Number.isNaN()이 더 명확하다.

Object.is와의 차이

JavaScript에는 ===== 외에 Object.is()도 있다.

Object.is()는 대부분의 경우 ===와 같은 결과를 내지만 NaN과 부호가 다른 0을 다루는 방식이 다르다.

Object.is(NaN, NaN) // true
Object.is(0, -0) // false

NaN === NaN // false
0 === -0 // true

일반적인 조건문에서는 ===로 충분한 경우가 많다.

NaN을 같은 값으로 다루거나 0-0을 구분해야 할 때 Object.is()의 차이를 알고 사용한다.

명시적 타입 변환

다른 타입의 값을 비교해야 한다면 ==에 변환을 맡기기보다 의도한 타입으로 먼저 바꾸는 편이 좋다.

const queryPage = '3'
const currentPage = 3

Number(queryPage) === currentPage // true

문자열을 비교하려면 문자열로 변환한다.

const productId = 42
const selectedId = '42'

String(productId) === selectedId // true

이런 코드는 비교 시점에 어떤 변환을 원했는지 직접 보여 준다.

입력 경계에서 타입을 정리하면 이후 로직은 ===만으로 단순하게 작성할 수 있다.

API 응답, URL query string, input.value, localStorage 값처럼 문자열로 들어오기 쉬운 경계를 특히 주의한다.

조건문의 비교 기준

boolean을 직접 true와 비교할 필요가 없는 경우도 많다.

if (isLoggedIn === true) {
  showProfile()
}

값이 항상 boolean임이 보장된다면 다음처럼 쓸 수 있다.

if (isLoggedIn) {
  showProfile()
}

반대 조건은 !isLoggedIn으로 표현한다.

그러나 0, '', null, undefined를 서로 다른 상태로 구분해야 한다면 단순한 truthy 검사 대신 ===로 필요한 값을 정확히 비교한다.

if (count === 0) {
  showEmptyState()
}

조건문은 값이 존재하는지와 특정 값과 같은지를 구분해야 한다.

선택 기준

기본은 ===!==를 사용한다.

타입이 다르면 원하는 타입으로 명시적으로 변환한 뒤 ===로 비교한다.

nullundefined를 함께 확인하는 의도가 분명하고 팀 규칙에서 허용한다면 value == null을 제한적으로 쓸 수 있다.

인터뷰나 코드 퍼즐을 위해 ==의 모든 변환 결과를 외우기보다 실제 코드에서 변환 지점을 명확하게 만드는 편이 중요하다.

변수를 어떻게 선언할지도 함께 정리하려면 JavaScript var let const 차이를 볼 수 있다.

참고 자료

관련 포스트
JavaScript var let const 차이 thumbnail
JavaScript var let const 차이
JavaScript var, let, const 차이를 함수와 블록 스코프, 재할당과 재선언, 호이스팅과 TDZ, 반복문 closure, 객체 변경 기준으로 정리합니다.
URL path parameter와 query string 차이 thumbnail
URL path parameter와 query string 차이
URL path parameter와 query string의 차이를 초보자 기준으로 정리합니다. 리소스 식별자, 조회 조건, URLSearchParams, 인코딩, API 설계 기준을 함께 봅니다.
쿠키와 localStorage 차이 thumbnail
쿠키와 localStorage 차이
쿠키와 localStorage의 차이를 초보자 기준으로 정리합니다. 서버 자동 전송, JavaScript 접근, 만료 시간, HttpOnly, Secure, SameSite, 로그인 상태 저장 기준을 함께 봅니다.
script async와 defer 차이 thumbnail
script async와 defer 차이
HTML script 태그의 기본 동작, async와 defer의 다운로드 방식, 실행 순서, DOMContentLoaded와의 관계를 초보자 기준으로 정리합니다.
CORS 에러와 해결 기준 thumbnail
CORS 에러와 해결 기준
브라우저의 Same-Origin Policy, CORS 응답 헤더, preflight, 서버에서 해결해야 하는 이유를 초보자 기준으로 정리합니다.
fetch와 axios 차이 thumbnail
fetch와 axios 차이
fetch와 axios의 차이를 초보자 기준으로 정리합니다. 설치 여부, JSON 처리, HTTP 에러 처리, timeout, interceptor, 언제 어떤 방식을 쓰면 좋은지 함께 봅니다.
Vue 3 입문기 thumbnail
Vue 3 입문기
Vue의 메인 버전이 3가 된지 꽤 됐다. 작성 당시에는 Nuxt 3가 RC 단계였기 때문에 실서비스에 바로 적용하기에는 조심스러웠다. 그래서 궁금하고 심심하던 참에 간단한 Todo App을 만들어봤다.
marked.js renderer custom 하기 thumbnail
marked.js renderer custom 하기
marked.js renderer custom 방법을 정리합니다. Renderer의 역할, image와 link 렌더러 커스텀, alt와 rel 처리, sanitize-html 같은 HTML 정화 주의점까지 함께 봅니다.
가독성 있게 상수 넣기 thumbnail
가독성 있게 상수 넣기
오늘 회사 동료의 PR 리뷰 과정에서 좋은 기능을 공유해주셔서 TIL로 남겨봅니다. 아래와 같이 상수에 언더바(_)로 콤마처럼 구분을 시켜줄 수 있습니다. 앞으로 깔끔하고 좋은 코드를 작성하기 위해 자주 사용해야겠습니다 :)
TS에서 generic optional 하게 설정하기 thumbnail
TS에서 generic optional 하게 설정하기
오늘 next에서 `getStaticProps`와 `getLayout` 패턴을 함께 사용할 때, typescript generic을 넘겨주는 작업을 하고 있었는데, 기본값이 없다보니 기존 코드에 에러가 발생했었다. 이를 해결하기 위해 찾아보니 단순히 아래 예시처럼 `= {}`을 추가해주면 해결된다고 한다.
Backend에서 API Response가 snake_case인 경우엔? thumbnail
Backend에서 API Response가 snake_case인 경우엔?
안녕하세요. 프론트엔드 개발자의 경우, 가끔 백엔드의 API Response 값이 snake_case일 경우 어떻게 관리할지에 대해 고민에 빠지게 됩니다. 저도 오늘 같은 상황을 겪게 되었는데,  이번엔 네이밍 컨벤션을 맞춰주기로 했습니다. 컨벤션을 맞추는 데에는 여러가지 방법이 있겠지만, 고민 끝에 저는 axios의 interceptors를 통해 해결을 해보았습니다.
Promise 다루기 (feat. 병렬실행, 순차실행) thumbnail
Promise 다루기 (feat. 병렬실행, 순차실행)
오늘은 Promise를 통해 구문을 동기 처리 할 때, 여러 Promise들을 다루는 법을 소개해보겠습니다. Javscript를 작성하다 보면, 가끔 여러 Promise들을 다룰 때가 있습니다. 필자도 Nodejs 서버에서 동시에 여러 쿼리를 실행할 때 자주 마주쳤었는데요. 오늘은 어떻게 하면 Promise들을 유연하게 다룰 수 있는지 알아보겠습니다. 시작하기 앞서, 네 가지 Promise를 선언하고, 그들을 하나의 Array에 묶어보겠습니다.