WTFJS 해설 - 2

undefcat·2021년 4월 14일
0

WTFJS

목록 보기
2/8
post-thumbnail

WTFJS - 2

지난 포스팅에 이어서 계속 정리해보도록 하겠습니다.

Examples

[] is truthy, but not true

!![]       // -> true
[] == true // -> false

💡 해설

지난 포스팅에서 자바스크립트의 모든 데이터 타입들은 boolean 컨텍스트에 있을 때, truthy falsy 값으로 해석된다고 했습니다. 그리고 이 때 중요한 점은 단순히 평가가 될 뿐, 형변환이 일어난다고는 하지 않았습니다.

위와 같은 내용을 기억하고 있다면, 위의 문제는 쉽게 이해할 수 있습니다.

[]! unary operator와 만나 boolean 컨텍스트에 있게 되고, []truthy 하므로 ![]false입니다. 그리고 false를 다시 부정하면 true가 됩니다. unary operator인 ! 연산의 결괏값은 boolean이므로, 값 자체가 배열에서 true로 바뀐 것입니다.

하지만 [] == true의 경우 저번 포스팅에서 계속 다뤘듯, []truthy한 값일 뿐이지, trueboolean은 아닙니다. 따라서 두 피연산자의 형태가 달라 숫자로 형변환이 발생하며, []0, true1로 형변환되어 그 결과는 false입니다.

null is falsy, but not false

!!null; // -> false
null == false; // -> false
0 == false; // -> true
'' == false; // -> true

💡 해설

nullfalsy한 값입니다. 그런데 같은 falsy 값인 0''의 경우, false와 비교하면 그 결과가 true이지만 nullfalse입니다.

이는 nullundefined의 경우 조금 특별하게 비교되기 때문인데, 언어의 명세를 보면 확실히 이해할 수 있습니다.

nullundefined를 비교할 때 기억해둘 점은

  • 두 피연산자가 같은 null 혹은 undefinded라면 true입니다. (명세 1번 항목)
  • nullundefined를 서로 비교하는 경우 true입니다. (명세 2, 3번 항목)
  • 그 외에는 false입니다. (명세 13번 항목)

document.all is an object, but it is undefined

document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'

document.all === undefined; // -> false
document.all === null; // -> false

document.all == null; // -> true

💡 해설

역사적인 이유로 이런 이해할 수 없는 결과가 나오므로, 굳이 이해 할 필요는 없습니다. 자세한 내막에 대해서는 원문의 설명을 참고하시면 됩니다.

하지만 여기에서 알아야 될 중요한 점은,

표준은 권고사항 이라는 것입니다. 물론 참된 개발자라면 표준을 지킬 수 있도록 최대한 노력해야하지만, 표준대로 구현이 안되어 있을 수 있다는 점도 어느 정도 항상 염두해둬야 합니다. 결국, 구현이라는 것은 구현자가 구현한 대로 동작하기 때문입니다.

Minimal value is greater than zero

Number.MIN_VALUE > 0; // -> true

💡 해설

MIN_VALUE는 음수가 아니라 0보다는 아주 살짝 큰 부동형 소수라는 점을 기억하시면 됩니다. 자바스크립트에서 가장 작은 값(음수)은 Number.NEGATIVE_INFINITY 임을 기억하시면 됩니다.

function is not a function

위 내용은 버그였기 때문에 굳이 정리하지 않겠습니다.

Super constructor null of Foo is not a constructor

class Foo extends null {}
new Foo() instanceof null;
// > TypeError: Super constructor null of Foo is not a constructor

💡 해설

Foonull을 부모로 상속합니다. 이 경우, Object.getPrototypeOf(Foo.prototype)의 결과는 null입니다.

이 때, Foo를 인스턴스화 시키는 경우 자식 클래스 생성자는 부모 클래스 생성자를 super로 호출해야 합니다. 이 때, 부모가 null이므로, 위와 같은 에러가 발생합니다.

이 문제의 경우, 최초에 해당 섹션이 정의되었을 땐 버그라고 생각했으나 @geekjob유저가 버그가 아님을 밝혔습니다.

Adding arrays

[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'이 됩니다.

Trailing commas in array

let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'

💡 해설

[, , ,]는 언뜻 보면 4개의 요소가 들어있는 것처럼 보이지만 많은 언어에서 배열에 trailing comma를 허용하며 자바스크립트 역시 마찬가지이므로, 요소는 3개입니다.

Array equality is a monster

[] == ''   // -> 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 guy

parseInt('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으로 표현되었을 때를 생각해보면 쉽게 이해할 수 있습니다.

Math with true and false

true + true; // -> 2
(true + true) * (true + true) - true; // -> 3

💡 해설

이는 많이 다뤘던 내용입니다. true는 숫자로 계산되어야 할 때 1이 되므로, 이를 생각해보면 당연한 결과입니다.

HTML comments are valid in JavaScript

// valid comment
<!-- valid comment too

💡 해설

역사적인 이유로 자바스크립트에서는 HTML에서의 주석 형태 역시 주석으로 받아들이며, 이러한 내용이 명세에도 나와있습니다(브라우저 한정).

재밌는 점은, node.js는 크롬의 V8엔진으로 만들어진 런타임 환경이므로, node.js 역시 이러한 HTML 형태의 주석 역시 주석으로 인식한다는 점입니다.

NaN is not a number

typeof NaN; // -> 'number'

💡 해설

NaN은 숫자입니다.

2편을 마치며

이제 대략 1/3을 정리한 것 같습니다... 정말 많은 내용이 있네요. WTFJS의 내용들은 사실 개발할 때 크게 의식하지 않아도 되는 내용들이 많이 있습니다. 중요한 내용도 있긴 하지만요.

WTFJS를 정리하면서 얻을 수 있는 장점은 언어 명세페이지를 봐야되는 이유를 만들어 주는 점 같습니다. 또한 재미도 있죠. 이는 WTFJS의 탄생 동기이기도 합니다.

그럼 앞으로도 계속 꾸준히 정리해보도록 하겠습니다. 다음 포스팅에서 뵙겠습니다.

(마찬가지로, 틀린 내용이 있다면 피드백을 주시면 감사하겠습니다.)

profile
undefined cat

0개의 댓글