WTFJS 해설 - 3
WTFJS - 3
지난 포스팅에 이어서 계속 정리해보도록 하겠습니다.
Examples
[]
and null
are objects
typeof []; // -> 'object'
typeof null; // -> 'object'
// however
null instanceof Object; // false
💡 해설
typeof
unary operator는 특정 타입에 대한 결괏값을 명세에서 정의하고 있습니다. null
의 경우 'object'
로, undefined
의 경우 'undefined'
로 정의되어 있다는 점을 기억하면 될 것 같습니다.
null
과 undefined
는 자바스크립트에서 값이 없음을 나타내는데, 일반적으로 null
은 개발자의 의도로 값이 없음을 나타낼 떄 사용하며, undefined
는 시스템적으로 값이 없을 때 나오는 값으로 생각하는 것이 좋습니다.
즉, 개발자가 코딩할 때 undefined
를 특정 변수에 할당하거나 함수의 매개변수 등으로 넘겨주는 일은 없는게 좋습니다.
Magically increasing numbers
999999999999999; // -> 999999999999999
9999999999999999; // -> 10000000000000000
10000000000000000; // -> 10000000000000000
10000000000000000 + 1; // -> 10000000000000000
10000000000000000 + 1.1; // -> 10000000000000002
💡 해설
자바스크립트에는 일반적인 integer
타입의 숫자는 없습니다. 모든 숫자는 float
으로 다뤄집니다. 따라서, 상식적인 크기의 숫자 안에서는 마치 정수형을 사용하는 것처럼 쓸 수 있으나, 위의 예제처럼 그 범위가 일반적인 정수형의 범위를 넘어서면 float
의 특성으로 정밀도에 따라 값의 손실이 발생할 수 있음을 염두해둬야 합니다.
Precision of 0.1 + 0.2
0.1 + 0.2; // -> 0.30000000000000004
0.1 + 0.2 === 0.3; // -> false
💡 해설
위의 섹션과 같은 내용입니다. 자세한 내용은 부동 소수점에 대해 자료를 찾아보시기 바랍니다.
Patching numbers
Number.prototype.isOne = function() {
return Number(this) === 1;
};
(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0).isOne(); // -> false
(7).isOne(); // -> false
💡 해설
자바스크립트는 prototype
기반의 언어입니다. 각 값들은 고유의 prototype
을 갖고 있습니다. 숫자 값들은 Number
객체의 일종이므로, Number.prototype
에 정의된 프로퍼티/메서드들을 사용할 수 있습니다.
하지만 자바스크립트 표준 객체들의 prototype
을 확장하는 것은 좋지 않은데, 나중에 언어 명세의 변경에 의해 새로운 prototype
프로퍼티/메서드가 추가되는 경우 문제가 발생할 수 있기 때문입니다.
Comparison of three numbers
1 < 2 < 3; // -> true
3 > 2 > 1; // -> false
💡 해설
연산자의 우선순위가 같은 경우, 왼쪽부터 오른쪽으로 해석됨을 생각해보면 쉽게 예상할 수 있는 결과입니다.
비교 연산자들의 경우 그 결괏값은 boolean
형입니다.
3 > 2 > 1
(3 > 2) > 1
// boolean과 숫자의 비교
true > 1
// 숫자로 형변환 되어 계산
1 > 1 // -> false
Funny math
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
💡 해설
계속 나오는 내용입니다. 산술 연산자가 숫자가 아닌 다른 타입의 데이터에 사용되었을 때, 자바스크립트는 이를 어떻게 변환시키는지를 알고 있으면 됩니다.
덧셈(+
)연산자의 경우, 두 피연산자중 하나가 문자열이면 다른 하나를 문자열로 변경한 뒤, 문자열 결합 연산자로 동작한다는 점과 객체의 경우 valueOf
, 그래도 숫자가 아니면 toString
을 호출한다는 점을 숙지하고 있으면 됩니다.
당연한 결과는 제외하고 알아볼만한 내용들만 알아보겠습니다.
'3' - 1
3 - 1 // -> 2
'3' + 1
'3' + '1' // -> '31'
[] + []
'' + '' // -> ''
{} + []
{ /* 객체 리터럴이 아닌 code block으로 해석됨 */ } + []
+[]
+''
0 // -> 0
[] + {}
'' + '[object Object]' // -> '[object Object]'
문제가 되는 연산은 {} + {}
입니다. 이 연산의 결과는 자바스크립트 엔진에 따라 다릅니다. 크롬의 V8엔진의 경우
위에서 볼 수 있듯이, 앞의 객체를 코드 블록으로 해석하지 않고 valueOf
와 toString
을 이용하여 문자열 결합연산자로 동작하게 됩니다.
파이어폭스의 SpiderMonkey 엔진의 경우
위와 같이 앞의 객체를 코드 블록으로 여전히 해석하고, 뒤의 객체를 +{}
와 같이 +
를 unary operator로 해석하여 NaN
이 됩니다.
계속해서 알아보도록 하겠습니다.
'222' - -'111'
'222' - (-111)
(222) - (-111)
222 + 111 // -> 333
+
를 제외한 다른 산술 연산자들은 문자열을 숫자로 변환시킨다는 점을 꼭 기억해야 합니다.
남은 배열 연산의 경우, 이전 포스팅의 It's fail 섹션에서 지겹게 알아보았으므로 넘어가도록 하겠습니다.
Addition of RegExps
// Patch a toString method
RegExp.prototype.toString = function() {
return this.source;
}
/7/ - /5/; // -> 2
💡 해설
toString
을 재정의 하여 어떻게 숫자 연산을 하는지에 대한 내용입니다. RegExp
의 source
프로퍼티에는 말 그대로 RegExp
의 소스가 들어있습니다. RegExp
의 경우, /pattern/
과 같이 /
를 이용하여 RegExp
리터럴을 만들 수 있는데, toString
을 이용하여 source
(/pattern/
에서 pattern
코드)를 얻어오고 이를 리턴하도록 재정의하면 나머지는 자연스럽게 동작합니다.
RegExp.prototype.toString = function() {
return this.source;
}
/7/ - /5/
'7' - '5'
7 - 5 // -> 2
Strings aren't instances of String
'str'; // -> 'str'
typeof 'str'; // -> 'string'
'str' instanceof String; // -> false
💡 해설
이는 언뜻 보면 이해가 안 갈수도 있습니다. 왜냐하면, 보통 문자열과 관련된 메서드를 호출할 때 우리는 자연스럽게 호출했었기 때문입니다.
'abc'.indexOf('b'); // -> 1
indexOf
메서드는 String.prototype
에 정의되어 있는 메서드입니다. 따라서, 'abc'
는 String
오브젝트임이 확실합니다. 그렇지 않다면 String.prototype
에 정의되어 있는 indexOf
를 사용하지 못할것이기 때문입니다.
이는 반은 맞고 반은 틀린 얘기입니다. 프로그래밍에서 리터럴(literal), 혹은 상수 표현(constant expression)은 언어의 명세에 따라 엔진이 해석하기 때문입니다.
예를 들어, 타입이 있는 언어 중 Go
언어를 살펴봅시다.
var a = 10
var b = 20
Go
는 컴파일되는 정적 타입 언어입니다. 정적 타입 언어의 특징은 변수에 타입이 정해져있다는 것입니다. 하지만 위에서 볼 수 있듯이 타입 없이 변수를 선언/초기화 할 수 있습니다.
물론 타입을 지정해서 변수를 선언/초기화할 수도 있습니다.
var a int = 10
var b int = 20
이러면 a
, b
는 int
타입이 됩니다. 그렇다면 이는 어떨까요?
var a float64 = 10
var b float64 = 20
똑같은 10
20
이라는 숫자 상수지만, a
b
는 정수형이 아니게 됩니다.
그러면 맨 위의 타입 없이 var
로 선언한 변수는 무슨 타입일까요? 이는 Go
에서 이를 어떻게 만들었냐에 따라 달라질 것입니다. 일반적인 경우 Go
는 해당 값을 int
타입으로 받아들입니다.
그렇다면 아래의 예제를 봅시다.
fmt.Println(10 + 10.2)
10
은 정수형처럼 생겼고 10.2
는 부동 소수점처럼 생겼습니다. 아시다시피 이 연산의 결과는 float64
타입입니다. 그렇다면, 10 + 10.2
가 계산되었을 때 10
이 정수형에서 float
형으로 형변환이 발생한 것일까요? 그건 아닙니다. 컴파일러가 이 코드를 해석할 때, 맥락을 살펴보고 10
을 float
형으로 결정내린 뒤 덧셈을 한 것입니다.
자바스크립트도 이와 비슷합니다.
'abc'.indexOf('b'); // -> 1
이 코드의 경우, 'abc'
와 같은 문자열 리터럴을 만났을 때, 이 값을 String
객체라고 생각하지 않습니다. 그냥 String
타입일 뿐입니다. 그리고 나서 .indexOf
코드, 즉 메서드 호출 코드를 만나면, 자바스크립트는 'abc'
가 String
타입이므로 이를 String Wrapper Class
를 이용하여 객체로 변환시킨 뒤, .indexOf
를 호출합니다.
instanceof
연산자에서는 단지 이러한 뒷내용, 즉 Wrapper Class
로 감싸는 행위가 발생하지 않기 때문에 'abc' instanceof String
이 false
가 되는 것입니다.
Calling functions with backticks
이 내용은 ES6
의 Template Literals에 관한 내용으로 넘기도록 하겠습니다.
3편을 마치며
아마도 10부작이 될 것 같은 불길한 예감이 듭니다... 다음 포스팅에서 뵙겠습니다!
(마찬가지로, 틀린 내용이 있다면 피드백을 주시면 감사하겠습니다.)
Author And Source
이 문제에 관하여(WTFJS 해설 - 3), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@undefcat/WTFJS-해설-3저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)