좀 열심히 뜯어본 자스는 이상해 (JS is weird)

Gomi·2023년 1월 4일
1

🤔 면접에서 이런걸 내더라

최근 면접에서 자바스크립트 연산과 형변환 문제를 받게되었다.
뭐 애초에 면접이 너무 떨려서 풀지 못함은 물론이요 나중에 답 물어볼 생각도 못했다.
이런 질문이 있었다는 것조차 기억해내지 못하다가 우연히

JS is Weird

사이트를 보게되었는데,
정리해두면 괜찮겠다 싶어서 정리해보기로했다.

📜 문제와 해설


1. true + false

answer : 1

더하기 연산에서 string이 없을 경우 boolean은 숫자로 변환된다.


2. [,,,].length

answer : 3

배열은 세개의 빈 슬롯으로 구성된다. 마지막은 트레일링 콤마.


3. [1, 2, 3] + [4, 5, 6]

answer : "1,2,34,5,6"

배열의 덧셈은 배열을 문자열로 바꾼 뒤 concat으로 동작한다.
간혹 "1,2,3,4,5,6"의 답을 얻기위해
[1, 2, 3] + [, 4, 5, 6]
이나
[1, 2, 3, ""] + [4, 5, 6]
같은 코드를 설계하는 건 가독성을 해친다.

원래 의도에 가장 근접한 접근은
[...[1, 2, 3], ...[4, 5, 6]]
정도 되겠다.

4. 0.2 + 0.1 === 0.3

answer : false

자바스크립트의 원시타입 중 Number는 64비트 부동소수점 형식의 2진수로 표시된다.
일부 소수는 10진법에서 2진법으로 변환될 때 무한소수가 되어버려
사용자의 의도와 다르게 계산될 수 있다.


5. 10,2

answer : 2

쉼표 연산자라고 불리는 이것은
'각각의 피연산자를 왼쪽에서 오른쪽 순서로 평가하고, 마지막 연산자의 값을 반환합니다.' 라는 사전적 정의를 갖고있다.
쉼표 연산자라는 이름은 잘 모른채 여러 변수를 다중으로 선언할 때 주로 보게됐었다.
마지막 값이 리턴된다는 특징을 알게되었다.


6. !!""

answer : false

NOT 연산자는 값을 반대의 boolean값으로 변환한다.
값이 boolean 자료형이 아니라면 Truthy 혹은 Falsy 변환을 거친다.
느낌표가 두개라면 반대의 반대, 즉 값의 boolean 평가값이 그대로 정답이된다.


7. +!![]

answer : 1

[]는 truthy하다. 고로 !![]는 true로 평가되며
true값에 단항 더하기 +를 붙이면 숫자 표현인 1로 변환된다.


8. parseInt(0.0000005)

answer : 5

parseInt(numericalString, radix)에서 numericalString 부분은
string이 아닐 경우 string 형식으로 변환한다.
요 블로그에 number to string에 대한 여러가지 실험이 있는데

String(0.05);     // => '0.05'
String(0.005);    // => '0.005'
String(0.0005);   // => '0.0005' 
String(0.00005);  // => '0.00005'
String(0.000005); // => '0.000005'
String(0.0000005); // => '5e-7'

다음과 같이 문자열 변환에서 소수 일곱번째 자리부터 지수표기법으로 전환된다.
신기한 것은 블로그 마지막에parseInt(999999999999999999999)에 대한 질문이 있는 것이다.

자바스크립트의 Number 최대범위인 2^53 -1(9007199254740991)을 아득히 초과했는데,
콘솔로 여러가지를 찍어보았다.

String(9007199254740991);     // => '9007199254740991'
String(90071992547409919);    // => '90071992547409920'
String(900719925474099199);   // => '900719925474099200' 
	.
	.
	.
String(999999999999999999999);  // => '1e+21'
String(111111111111111111111);  // => '111111111111111110000'
String(1111111111111111111111);  // => '1.1111111111111111e+21'

2^53 -1 부터는 반올림으로 부정확한 처리를 하다가
10^21이상의 수에서는 exponential 표기법을 쓰고 있다.

이와 관련한 ECMAScript 내용을 살펴보면 String(value)는 다음 알고리즘을 따른다.

String(value)의 2.b 규칙에 따라 ToString(value)를 거친다.

ToString(Number)는 Number::toString ( x )을 반환하는데
결국 우리가 넣은 숫자들은 이 규칙을 따르는 것이다.

Number::toString ( x )의 핵심은 결국 다음과 같다.

x가 10^(-6) 과 10^21 사이에 있다면 일반적인 표현을 사용하고
그 외에는 the code unit 0x0065 (LATIN SMALL LETTER E)를 포함한
string-concatenation 이 발생한다 (지수 표기법)

또한 소수 작업을 할때는 정수로 변환하거나, Math 라이브러리 사용을 권장한다.


9. true == "true"

answer : false

ESLint 플러그인 같은 코딩 컨벤션에 익숙해지면 일치 연산자(strict equality, ===)만 쓰게 되어 동등 연산자는 머릿속에서 지워진다. 동등 연산자는 일치 연산자와 비교하는 대상의 자료형이 다를 경우 형변환을 한 후 비교한다는 차이가 있다.

형변환 알고리즘은 다음을 따른다.

살펴보자면

  1. 너무나 당연하게 같은 자료형의 같은 놈들은 true, 아니면 false이다. 참조타입은 참조에 따른 비교를 실시한다.
    (다만 NaN == NaN // => false 이다.)

  2. null과 undefined의 비교는 true이다.

  3. Number와 String의 비교는 String을 Number로 형변환 후 비교한다.

  4. Boolean과 다른 자료형의 비교는 Boolean을 Number로 형변환 후 다시 비교한다.

  5. Object와 Number혹은 String의 비교는 Object를 원시타입으로 변환 후 다시 비교한다.

문제의 경우는 4번 규칙에 의해 true를 1로 변환한 후 (1 == "true")
3번 규칙에 의해 (1 == "NaN")이 되어 답이 false가 된다.

(숫자로 변환할 수 없는 String 값을 Number로 형변환 시 NaN이 된다. 고로 Number("true")는 NaN이다.)


10. 010 - 03

answer : 5

오우쒸 말도안돼

다음 문서에 따르면 strict mode가 아닐 때, 0으로 시작하면서 뒤에 8미만의 숫자들로 구성된 표현은 8진법을 따른다.(parsed as octal number)

ex) 0777 == 511 // => true (0777 = 7x(8^2) + 7x(8^1) + 7x(8^0) = 511)

010은 그럼 8이고 03은 3이겠네?
실화다.


11. "" - - ""

answer : 0

암시적 형변환은 +의 경우 string-concatenation이 수학적 덧셈에 우선하여
String으로의 형변환이 우선권을 가진다.

다른 연산자(-,/,*,% 등)는 Number로의 형변환이 우선시된다.
따라서 -연산자에 의해 "" - - "" 은 0 - - 0으로 변하고
0 - -0이 된다.

따라서 답은 0

주의사항으로 -를 붙여서 --로 표현하면 --0 부분에서 syntax error가 발생한다.
--(감소연산자)는 뒤의 대상으로 값이아닌 참조변수가 와야하기 때문.


12. null + 0

answer : 0

+연산자는 string이 없으면 number가 우선 시 된다.
Number(null) => 0


13. 0/0

answer : NaN

0으로 나누기 문제는 검색해보면 여러 수학적 논의가 쏟아지더라....
프로그래밍에서는 정지를 막기 위한 처리를 해놨다고 생각하면 될 거 같다.

JS에서는 나름 극한의 개념을 갖고 n/0을 다음과 같이 처리한다.
다른 언어에서는 0으로 나누는 행위 자체가 예외를 발생시키기도 하지만
JS는 그 특성상 일단 값을 내어준다.

n>0 => Infinity
n=0 => NaN
n<0 => -Infinity

그리고 ECMA Script3 까지는 NaN과 Infinity가 수정 가능했지만
ES5부터 읽기 전용 상수가 되었다고한다.


14. 1/0 > 10 ** 1000

answer : false

ECMAScript의 Number Type 명세에 따르면 2^1024이상의 수는 지수 표기법으로도 표현되지 않고 Infinity로 변환된다.
13번 문제에도 설명했듯, 1/0은 Infinity이며 10 ** 1000도 Infinity로

1/0 === 10**1000 은 true이다. 문제의 답은 false


15. true++

answer : SyntaxError

문제 11번의 설명과 MDN-증가연산자 의 설명처럼
증감 연산자는 유효한 참조인 피연산자에 붙을 수 있다.
값인 true에 붙을 경우 SyntaxError가 발생한다.
해설지에는 여러가지 케이스들을 정리해줬다.

true++; // -> SyntaxError
1++; // -> SyntaxError
"x"++; // -> SyntaxError
null++; // -> SyntaxError
undefined++; // -> NaN
$++; // -> NaN
console.log++; // -> NaN
NaN++; // -> NaN

true 값을 가진 변수에 ++를 실시하면 2로 변환된다.

let _true = true;
_true++;
_true; // -> 2

16. "" - 1

answer : -1

암시적 형변환 규칙에 따라 - 연산자에서는 String과 Number가 있을 경우 Number로의 형변환이 우선시 된다.
따라서 0 - 1 => -1


17. (null - 0) + "0"

answer : "00"

암시적 형변환 규칙에 따라 - 연산에서 (0-0)으로 변환되고,
+ 연산자에서는 string-concatenation이 우선 발생하여
0 + "0"은 "00"이 된다.


18. true + ("true" - 0)

answer : "00"

암시적 형변환 규칙에 따라 ("true" - 0) 연산에서 "True"는 Number로 형변환된다.
Number("True")는 NaN이므로 이후의 연산은 모두 NaN으로 처리된다.


19. !5 + !5

answer : 0

논리연산자 NOT은 truthy 값을 false로, falsy 값을 true로 바꿔준다.
!5는 false로 변환된다.
+연산자에서 boolean은 Number로 변환되기때문에
답은 0 + 0 이 된다.


20. [] + []

answer : ""

3번 문제의 설명처럼
배열의 + 연산은 string-concatenation이 우선 발생함에 따라
"" + "" => ""가 된다.


21. 1 + 2 + "3"

answer : "33"

자바스크립트 구문은 왼쪽에서 오른쪽으로 실행된다.
+ 연산을 순서대로 실행하면 숫자 덧셈과 string-concatenation이 차례로 실행된다.


22. typeof NaN

answer : "number"

ECMAScript에 따르면
NaN은 Number이면서 "Not-aNumber"임이 정의되어있다.

Number의 특정 값(NaN, +0, -0)에 있어 이상한 것은 Object.is 메소드와 ===이 다르게 동작한다는 점이다.

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

23. undefined + false

answer : 0

String이 없기에 모두 Number로 형변환된다.
undefined는 falsy이지만 Number(undefined)는 NaN이기 때문에 이후의 계산은
NaN + 0 으로 정답은 NaN이 된다.


24. "" && -0

answer : ""

&& 연산자를 if문의 조건식에만 썼다면 놓치기 쉽지만, &&는 그 자체로는 boolean으로의 형변환이 없다.
왼쪽이 truthy하다면 왼쪽 값을, falsy하다면 오른쪽 값을 그대로 반환한다.
빈 문자열은 falsy값이므로 ""가 그대로 반환된다.


25. +!!NaN * "" - - [,]

answer : 0

MDN의 Logical NOT 설명을 보면 NOT연산자는 항상 따라오는 값을 boolean으로 형변환하며, 이때 NaN은 false로 평가한다.
Not연산자가 두개이므로 !!NaN은 false이다.

+는 더하기 연산자 외에도 단항 더하기(Unary plus)라는 것이 있다. Number외의 값 앞에 붙으면 Number로의 형변환을 일으킨다.

따라서 +!!NaN는 0이다.

다음으로 0 * ""은 곱하기 연산자의 Number 형변환 우선 규칙에 따라 0 * 0으로 처리한다.

마지막으로 0 - - [,]은 빼기 연산의 규칙에 따라 빈 배열을 0으로 형변환하고,
0 - -0의 연산이 이어진다.
따라서 답은 0

profile
터키어 배운 롤 덕후

0개의 댓글