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'로 정의되어 있다는 점을 기억하면 될 것 같습니다.

nullundefined는 자바스크립트에서 값이 없음을 나타내는데, 일반적으로 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엔진의 경우

위에서 볼 수 있듯이, 앞의 객체를 코드 블록으로 해석하지 않고 valueOftoString을 이용하여 문자열 결합연산자로 동작하게 됩니다.

파이어폭스의 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을 재정의 하여 어떻게 숫자 연산을 하는지에 대한 내용입니다. RegExpsource 프로퍼티에는 말 그대로 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, bint 타입이 됩니다. 그렇다면 이는 어떨까요?

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형으로 형변환이 발생한 것일까요? 그건 아닙니다. 컴파일러가 이 코드를 해석할 때, 맥락을 살펴보고 10float형으로 결정내린 뒤 덧셈을 한 것입니다.

자바스크립트도 이와 비슷합니다.

'abc'.indexOf('b'); // -> 1

이 코드의 경우, 'abc'와 같은 문자열 리터럴을 만났을 때, 이 값을 String 객체라고 생각하지 않습니다. 그냥 String 타입일 뿐입니다. 그리고 나서 .indexOf코드, 즉 메서드 호출 코드를 만나면, 자바스크립트는 'abc'String 타입이므로 이를 String Wrapper Class를 이용하여 객체로 변환시킨 뒤, .indexOf를 호출합니다.

instanceof 연산자에서는 단지 이러한 뒷내용, 즉 Wrapper Class로 감싸는 행위가 발생하지 않기 때문에 'abc' instanceof Stringfalse가 되는 것입니다.

Calling functions with backticks

이 내용은 ES6Template Literals에 관한 내용으로 넘기도록 하겠습니다.

3편을 마치며

아마도 10부작이 될 것 같은 불길한 예감이 듭니다... 다음 포스팅에서 뵙겠습니다!

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

좋은 웹페이지 즐겨찾기