지난 포스팅에 이어서 계속 정리해보도록 하겠습니다.
[]
is truthy, but not true
!![] // -> true
[] == true // -> false
지난 포스팅에서 자바스크립트의 모든 데이터 타입들은 boolean
컨텍스트에 있을 때, truthy
falsy
값으로 해석된다고 했습니다. 그리고 이 때 중요한 점은 단순히 평가가 될 뿐, 형변환이 일어난다고는 하지 않았습니다.
위와 같은 내용을 기억하고 있다면, 위의 문제는 쉽게 이해할 수 있습니다.
[]
는 !
unary operator와 만나 boolean
컨텍스트에 있게 되고, []
는 truthy
하므로 ![]
는 false
입니다. 그리고 false
를 다시 부정하면 true
가 됩니다. unary operator인 !
연산의 결괏값은 boolean
이므로, 값 자체가 배열에서 true
로 바뀐 것입니다.
하지만 [] == true
의 경우 저번 포스팅에서 계속 다뤘듯, []
는 truthy
한 값일 뿐이지, true
인 boolean
은 아닙니다. 따라서 두 피연산자의 형태가 달라 숫자로 형변환이 발생하며, []
는 0
, true
는 1
로 형변환되어 그 결과는 false
입니다.
null
is falsy, but not false
!!null; // -> false
null == false; // -> false
0 == false; // -> true
'' == false; // -> true
null
은 falsy
한 값입니다. 그런데 같은 falsy
값인 0
과 ''
의 경우, false
와 비교하면 그 결과가 true
이지만 null
은 false
입니다.
이는 null
과 undefined
의 경우 조금 특별하게 비교되기 때문인데, 언어의 명세를 보면 확실히 이해할 수 있습니다.
null
과 undefined
를 비교할 때 기억해둘 점은
null
혹은 undefinded
라면 true
입니다. (명세 1번 항목)null
과 undefined
를 서로 비교하는 경우 true
입니다. (명세 2, 3번 항목)false
입니다. (명세 13번 항목)document.all
is an object, but it is undefineddocument.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'
document.all === undefined; // -> false
document.all === null; // -> false
document.all == null; // -> true
역사적인 이유로 이런 이해할 수 없는 결과가 나오므로, 굳이 이해 할 필요는 없습니다. 자세한 내막에 대해서는 원문의 설명을 참고하시면 됩니다.
하지만 여기에서 알아야 될 중요한 점은,
표준은 권고사항 이라는 것입니다. 물론 참된 개발자라면 표준을 지킬 수 있도록 최대한 노력해야하지만, 표준대로 구현이 안되어 있을 수 있다는 점도 어느 정도 항상 염두해둬야 합니다. 결국, 구현이라는 것은 구현자가 구현한 대로 동작하기 때문입니다.
Number.MIN_VALUE > 0; // -> true
MIN_VALUE
는 음수가 아니라 0보다는 아주 살짝 큰 부동형 소수라는 점을 기억하시면 됩니다. 자바스크립트에서 가장 작은 값(음수)은 Number.NEGATIVE_INFINITY
임을 기억하시면 됩니다.
위 내용은 버그였기 때문에 굳이 정리하지 않겠습니다.
class Foo extends null {}
new Foo() instanceof null;
// > TypeError: Super constructor null of Foo is not a constructor
Foo
는 null
을 부모로 상속합니다. 이 경우, Object.getPrototypeOf(Foo.prototype)
의 결과는 null
입니다.
이 때, Foo
를 인스턴스화 시키는 경우 자식 클래스 생성자는 부모 클래스 생성자를 super
로 호출해야 합니다. 이 때, 부모가 null
이므로, 위와 같은 에러가 발생합니다.
이 문제의 경우, 최초에 해당 섹션이 정의되었을 땐 버그라고 생각했으나 @geekjob유저가 버그가 아님을 밝혔습니다.
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
자주 나오는 내용입니다. [1, 2, 3].valueOf()
는 자기 자신이므로 결국 [1, 2, 3].toString()
이 호출되는데, 배열의 toString
의 기본 동작은 join(',')
과 같습니다. 따라서 '1,2,3'+'4,5,6'
이 되므로 '1,2,34,5,6'
이 됩니다.
let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'
[, , ,]
는 언뜻 보면 4개의 요소가 들어있는 것처럼 보이지만 많은 언어에서 배열에 trailing comma를 허용하며 자바스크립트 역시 마찬가지이므로, 요소는 3개입니다.
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
해설하기에 앞서, 배열이 숫자로 다뤄질 때의 메커니즘을 좀 더 자세히 서술해보겠습니다.
- 객체가 숫자로 다뤄질 때에는
valueOf
가 호출됩니다.- 배열의
valueOf
는 그 자신(this
)입니다.- 여전히 숫자가 아니므로
toString
이 호출됩니다.- 배열의
toString
기본 동작은.join()
입니다.join()
결과 문자열을 다시 숫자로 변환합니다.
- 요소가
null
undefined
[]
인 경우, 해당 요소들은 빈 문자열인''
로 변환됩니다.
이 점을 생각하고 위의 케이스들을 하나하나 살펴보겠습니다.
[] == ''
'' == '' // -> true
[] == 0
'' == 0
0 == 0 // -> true
[''] == ''
'' == '' // -> true
[0] == 0
'0' == 0
0 == 0 // -> true
[0] == ''
'0' == '' // -> false
[''] == 0
'' == 0
0 == 0 // -> true
[null] == ''
'' == '' // -> true
[null] == 0
'' == 0
0 == 0 // -> true
[undefined] == ''
'' == 0
0 == 0 // -> true
[undefined] == 0
'' == 0
0 == 0 // -> true
[[]] == 0
'' == 0
0 == 0 // -> true
[[]] == ''
'' == '' // -> true
// 중첩된 배열 요소의 경우, 코드가 길어짐을 방지하기 위하여
// `[[]]`를 하나의 단계로 표현하겠습니다.
[[[[[[]]]]]] == ''
[[[['']]]] == ''
[['']] == ''
'' == '' // -> true
[[[[[[]]]]]] == 0
[[[['']]]] == 0
[['']] == 0
'' == 0
0 == 0 // -> true
[[[[[[null]]]]]] == 0
[[[['']]]] == 0
[['']] == 0
'' == 0
0 == 0 // -> true
[[[[[[null]]]]]] == ''
[[[['']]]] == ''
[['']] == ''
'' == '' // -> true
[[[[[[undefined]]]]]] == 0
[[[['']]]] == 0
[['']] == 0
'' == 0
0 == 0 // -> true
[[[[[[undefined]]]]]] == ''
[[[['']]]] == ''
[['']] == ''
'' == '' // -> true
중첩된 배열의 경우, 핵심은 toString
을 호출해보면 되는 것입니다.
예를 들어, [[[false]]] == 0
의 경우 false
인데, 언뜻 보면 true
일 것 처럼 보이지만
[[[false]]] == 0
['false'] == 0
'false' == 0
NaN == 0 // -> false
위와 같은 이유로 false
입니다.
undefined
and Number
Number(); // -> 0
Number(undefined); // -> NaN
단순하게 생각해서, Number
생성자의 매개변수에 기본값이 설정되어 있다고 보시면 됩니다. 사실 ES6
로 넘어오면서 자바스크립트 역시 함수 매개변수에 기본값을 설정할 수 있게 되었으므로, 당연하게 여겨질 수 있습니다.
parseInt
is a bad guyparseInt('f*ck'); // -> NaN
parseInt('f*ck', 16); // -> 15
parseInt
는 +
unary operator처럼 전체를 숫자로 해석하는 것과는 다르게, 매개변수로 받은 값을 앞에서부터 차례대로 숫자로 해석합니다. 그리고 더 이상 숫자로 해석되지 않은 경우, 지금까지의 결과로 숫자를 만들어냅니다.
그리고 자바스크립트에서는 다른 언어와 마찬가지로 여러 진수 표현의 리터럴 값들이 존재합니다. 16진수의 경우 0x
prefix로 표현하고, 8진수의 경우 0
prefix로 표현합니다.
16진수의 경우 9
다음은 a
b
c
... 로 표현된다는 점과 parseInt
의 두번째 매개변수로 진수값을 정해줄 수 있다는 점을 생각해보면, 예측되는 결과일 것입니다.
parseInt
에서 알아둬야 할 다른 특징은, 매개변수로 받은 값을 항상 문자열로 해석한다는 점입니다. 저번 포스팅에서도 알아보았듯, 자바스크립트에서 어떤 데이터가 문자열로 표현되어야 하는 경우 toString
이 호출된다고 했습니다. 따라서
parseInt({ valueOf: () => 2, toString: () => '1' }); // -> 1
위의 예제코드에서 볼 수 있듯, parseInt
는 그 결과가 Integer
인 만큼 파싱해야 하는 값이 숫자로 들어오면 이를 그대로 해석할 것처럼 보이지만 그렇지 않습니다. 이는 부동소수점 형인 매개변수를 넘기는 다음 아래의 예제에서도 알 수 있습니다.
parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5
이는 각각의 숫자의 결과가 toString
으로 표현되었을 때를 생각해보면 쉽게 이해할 수 있습니다.
true
and false
true + true; // -> 2
(true + true) * (true + true) - true; // -> 3
이는 많이 다뤘던 내용입니다. true
는 숫자로 계산되어야 할 때 1
이 되므로, 이를 생각해보면 당연한 결과입니다.
// valid comment
<!-- valid comment too
역사적인 이유로 자바스크립트에서는 HTML
에서의 주석 형태 역시 주석으로 받아들이며, 이러한 내용이 명세에도 나와있습니다(브라우저 한정).
재밌는 점은, node.js
는 크롬의 V8
엔진으로 만들어진 런타임 환경이므로, node.js
역시 이러한 HTML
형태의 주석 역시 주석으로 인식한다는 점입니다.
NaN
is typeof NaN; // -> 'number'
NaN은 숫자입니다.
이제 대략 1/3을 정리한 것 같습니다... 정말 많은 내용이 있네요. WTFJS의 내용들은 사실 개발할 때 크게 의식하지 않아도 되는 내용들이 많이 있습니다. 중요한 내용도 있긴 하지만요.
WTFJS를 정리하면서 얻을 수 있는 장점은 언어 명세페이지를 봐야되는 이유를 만들어 주는 점 같습니다. 또한 재미도 있죠. 이는 WTFJS의 탄생 동기이기도 합니다.
그럼 앞으로도 계속 꾸준히 정리해보도록 하겠습니다. 다음 포스팅에서 뵙겠습니다.
(마찬가지로, 틀린 내용이 있다면 피드백을 주시면 감사하겠습니다.)