자바스크립트에서 ([] == 0)이 true인 이유

Myungho·2020년 4월 18일
22

본 글은 아래에서 참고하여 작성되었습니다. 내용에 대해 더 자세하게 알고싶으신 분은 아래의 링크를 참고해주세요.
Academind - Avoiding Javascript Type Conversion Issues
Javascript MDN - Truthy/Falsy


자바스크립트에서 은 이상합니다. 타입을 가지고 있지 않지만 필요에 따라 타입이 변환되기도 하고, [] == 0이 true이고, 심지어 if([]) console.log('이게 왜 되지?');도 정상적으로 동작합니다. 도대체 자바스크립트의 은 어떻게 생겨먹었길래 이런 이상한 현상이 목격되는 것일까요?

[] == 0 // true

if([]) {
  console.log('어, 이게 왜 되지?'); // 출력 O
}

if(0) {
  console.log('어, 이게 왜 안되지?'); // 출력 X
}

이 모든 것은 연산자에 따른 형변환과 값의 truthy, falsy 구분에 의한 현상입니다.
형변환이 어떻게 일어나길래 이상하게 보이는지 한 번 알아보도록 합시다.


값의 종류 🔱

자바스크립트에는 두 가지 종류의 값이 있습니다. 하나는 Primitive Value(원시값)이고, 다른 하나는 Reference Value(참조값)입니다.

원시값에는 number, string, boolean, null, undefined가 있고,
참조값에는 array, function, object가 있습니다.


연산자에 따른 형변환 ➕

자바스크립트는 연산자에 따라 형변환이 다르게 일어납니다. 또, 값이 원시값이냐 참조값이냐에 따라서도 다른 형변환이 일어나죠. 예시를 통해 알아보겠습니다.

1 + '1' // '11'

위 예시는 numberstring+ 연산했을 때의 결과입니다. 결과는 '11' 문자열이 나왔습니다.
이 결과는 값의 형변환에 의한 결과로 볼 수 있습니다. 위 예시에서는 number 1string 1로 변환이 되고, 두 문자열을 합친 새로운 문자열을 반환한 것으로 볼 수 있습니다.

1 + '1' => '1' + '1' => '11'

+ 연산자는 다른 수학 연산자와 다르게 덧셈 연산 외에 두 문자열을 합치는 특별한 연산을 가지고 있습니다. 그렇다면 왜 덧셈이 아닌 두 문자열을 합치는 연산을 선택한 것일까요?

Number('alphabetString'); // NaN
Number('123'); // 123

123.toString(); // '123'
1.23.toString(); // '1.23'

'hello ' + 'world' // 'hello world'
1 + 'hello' // '1hello'
'3' + 5 // '35'

모든 문자열이 숫자로 변환될 수 있는 것은 아니지만, 모든 숫자는 문자열로 변환될 수 있기 때문입니다. 따라서 + 연산에 문자열과 숫자가 연산될 경우, 숫자를 문자열로 변환 후에 연산을 하는 것입니다.


1 + true // 2
2 + false // 0

boolean은 오직 두 가지 값을 가지고 있습니다. 다양한 종류의 문자열과는 다르게 단 두 가지의 값을 가지고 있기 때문에 숫자로 형변환이 용이합니다. true는 1로, false는 0으로 변환하면 아주 간단하죠. 따라서 숫자와 boolean+연산을 할 때는 boolean을 숫자로 변환 후에 연산을 합니다.


그럼 +을 제외한 다른 수학 연산자에 대해서는 어떨까요?

4 - '1' // 3
1 * '2' // 2
2 / '1' // 2

'hello' - 'ello' // NaN

-, *, / 연산자는 오직 수학적 연산을 가지고 있습니다. 즉 피연산자로 숫자만 올 수 있는 것이죠. 따라서 연산에 사용되는 모든 값은 숫자로 변환 후에 연산을 하게 됩니다. 위 연산자 뿐만 아니라 다른 수학적 연산자 모두 동일하게 동작합니다. 이 때, 숫자로 변환될 수 없는 값은 NaN 값을 가집니다.


참조값의 형변환 ↪️

자바스크립트에서의 각 연산자는 원시값을 피연산자로 가지고 있어야 합니다. 즉, 참조값은 연산에 사용할 수 없다는 것이죠.

[] + 1 // '1'
[] == 0 // 0

하지만 array참조값인데 어떻게 위의 연산이 가능한 것일까요? 그것은 바로 참조값이 자바스크립트 엔진에 의해 자동으로 원시값으로 형변환이 되었기 때문입니다. 위 예시를 보면 arraystring으로 변환이 되는 것을 알 수 있습니다.

arraytoString() 함수를 가지는데, 이 함수는 각 원소를 ,로 구분하여 문자열로 만드는 함수입니다. 따라서 array가 연산에 사용됐을 때 내부적으로 toString() 함수가 호출되어 arraystring으로 바꾸고 연산을 하게 됩니다.

[] + 1 => [].toString() + 1 => '' + 1 => '1'

그럼 object는 어떨까요? object도 참조값이지만 array와 마찬가지로 연산에 사용되면 자동으로 원시값으로 형변환이 되어 연산됩니다.

const obj = {};
obj + 1; // '[object Object]1'

{} + 1; // '{}' 가 block으로 취급

놀랍게도 objecttoString() 함수가 존재하고, 그 결과값은 [object Object] 입니다. array와 동일하게 아래와 같은 과정을 거쳐 연산을 하게 됩니다.

const obj = {};
obj + 1 => obj.toString() + 1 => '[object Object]' + 1 => '[object Object]1'

== 연산에서의 형변환 ⚠️

다른 타입을 가진 두 값이 같은지 비교하기 위해서는 당연히 형변환이 선행으로 이루어져야 합니다. 각 피연산자가 == 연산에서 각각 어떻게 형변환이 되는지 알아봅시다. (=== 연산자는 형변환이 발생하지 않습니다. 자세한 내용은 이곳을 확인해주세요.)

if(1 == '1') {
  console.log('Hello World!'); // 출력 O
}

if(1 == '') {
  console.log('Hello World!'); // 출력 X
}

if(0 == '') {
  console.log('Hello World!'); // 출력 O
}

Number(''); // 0
0.toString(); // '0'

간단한 예시로 문자열과 숫자를 비교했을 때, 문자열이 숫자로 변환됨을 알 수 있습니다.
만약, 숫자가 문자열로 변환이 되는 것이라면 세 번째 if문이 동작하지 않아야하지만 동작하기 때문에 숫자가 문자열로 변환되는 것이 아님을 알 수 있습니다.


참조값==연산에 사용될 경우에는, 위에서 설명한 것과 마찬가지로 참조값이 우선 toString() 연산을 통해 원시값으로 형변환됩니다. 그리고 마찬가지로 위와 동일한 연산을 수행하게 됩니다.


[] == 0이 true인 이유 ✔️

우리는 이제 참조값이 어떻게 원시값으로 변환되는지, 그리고 === 연산에서 형변환이 어떻게 일어나는지 알았습니다. 이것들을 토대로 [] == 0가 어떻게 true가 되는지 알아보도록 하겠습니다.

if([] == 0) {
  console.log('어, 이게 왜 되지?');
}

// 1단계 - [] == 0 => '' == 0  :  배열 []이 빈 문자열 ''로 변환된다.
if('' == 0) {
  console.log('어, 이게 왜 되지?');
}

// 2단계 - '' == 0 => 0 == 0 : 문자열 ''이 숫자 0으로 변환된다.
if(0 == 0) {
  console.log('어, 이게 왜 되지?');
}

// 3단계 - 0 == 0 => true
if(true) {
  console.log('어, 이게 왜 되지?');
}

위 과정을 통해 [] == 0이 어떻게 true를 반환하는지 알게되었습니다.


Truthy와 Falsy 🔰

음.. [] == 0true인 것은 이제 알겠습니다. 그런데 왜 if([])는 동작하지만 if(0)는 동작하지 않는 것일까요? 이것을 이해하기 위해서는 TruthyFalsy에 대해 알아야 합니다.

TruthyFalsy는 자바스크립트에서 boolean을 기대하는 구문에서 각 값이 truefalse 중 어떤 값을 가지냐를 나타내는 값입니다. 즉, if, while 등의 구문에서 사용되는 값이 truefalse를 나타내는 것이죠.

먼저 Falsy한 값은 0, -0, 0n(bigint), '', null, undefined, NaN이 있습니다.
Truthy한 값은 Falsy한 값을 제외한 모든 값입니다.

0 // falsy => false
'' // falsy => false
123 // truthu => true
'Hello World' // truthy => true
[] // truthy => true - string으로 변환되지 않음
[1, 2, 3] // truthy => true - string으로 변환되지 않음

여기서 주의해야할 점은, truthyfalsy를 결정하는 과정에서는 형변환이 발생하지 않습니다.
즉, arraystring으로 변환되지 않습니다.

if([]) {
  console.log('어, 이게 왜 되지?'); // [] = truthy, 출력 O
}

if(0) {
  console.log('어, 이게 왜 안되지?'); // 0 = falsy, 출력 X
}

요약 📃

  • 문자열과 숫자의 + 연산은 숫자가 문자열로 형변환된다.
  • 문자열과 숫자의 + 연산을 제외한 모든 수학 연산에서 문자열이 숫자로 형변환된다.
  • 문자열과 숫자의 == 연산은 문자열이 숫자로 형변환된다.
  • 참조값은 연산을 위해 원시값으로 변환되어야 하며 그 값은 toString() 결과이다.
  • 따라서 [] == 0 의 값은 '' == 0 , 0 == 0 과정에 따라 true를 반환한다.

마치며 💬

지금까지 자바스크립트에서 형변환이 어떻게 동작하는지 알아보았습니다. 다른 타입에 대한 연산 결과를 직관적으로 보기에는 자바스크립트 엔진에 버그가 있나 싶을 정도로 이상한 결과를 보이지만 내부를 자세히 들여다 보았을 때는 꽤나 합리적인 결과인 것을 알 수 있습니다. 자바스크립트의 타입, 원시값과 참조값, 형변환 등 다양한 이론의 집합에 대한 결과가 [] == 0 => true 라는 작은 구문에 담겨있기 때문에, 이 과정을 이해한다면 코드에 버그가 발생할 가능성을 줄일 수 있을 것이라고 생각합니다.

이해가 되지 않거나 글에 오류가 있다면 댓글로 남겨주시길 바랍니다!
즐거운 자바스크립트 코딩하세요~

profile
자바스크립트로 개발하는 새내기입니다.

2개의 댓글

comment-user-thumbnail
2021년 11월 1일

너무 깔끔하게 정리해주셨네요!! 기승전결이 완벽해서 이해가 잘 됐습니다ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 9월 22일

GOOD

답글 달기