WTFJS 해설 - 완결

undefcat·2021년 4월 21일
0

WTFJS

목록 보기
8/8
post-thumbnail

WTFJS - 완결

드디어 마지막 편입니다. 지난 포스팅에 이어 계속 정리해보도록 하겠습니다.

Examples

arguments binding

function a(x) {
  arguments[0] = "hello";
  console.log(x);
}

a(); // > undefined
a(1); // > "hello"

💡 해설

자바스크립트 함수 내에서 사용할 수 있는 특별한 키워드들이 몇 개 있습니다. 가장 생각없이 사용하는 것은 this가 대표적이라고 할 수 있습니다.

이와 비슷하게, arguments라는 객체 역시 사용할 수 있습니다. 이 객체는 함수가 호출되었을 때 매개변수의 정보를 갖고 있는 객체입니다. 이 객체는 배열이 아닙니다. 하지만 Array-like 객체이므로, 매개변수의 위치값에 해당하는 프로퍼티(index)에 매개변수의 정보가 들어있습니다.

ES6부터는 Rest Parameter가 존재하므로, arguments를 사용할 일이 거의 없을 것입니다. 하지만 그 이전 버전에서 매개변수의 값이 동적으로 필요한 경우, arguments를 이용하여 그 정보를 받아오는 식으로 구현을 많이 했습니다.

예를 들어

function sum() {
  var ret = 0;
  
  for (var i = 0; i < arguments.length; i++) {
    ret += arguments[i];
  }
  
  return ret;
}

위와 같은 코드는 sum의 매개변수를 따로 선언하지 않았지만, 함수 호출시 넘겨진 매개변수의 정보를 arguments로 받아와서 그 합을 리턴하는 함수입니다.

An alert from hell

[666]["\155\141\160"]["\143\157\156\163\164\162\165\143\164\157\162"](
  "\141\154\145\162\164(666)"
)(666); // alert(666)

💡 해설

8진수 escape sequence를 이용한 트릭입니다. 각 문자열을 콘솔창에 찍어보면

'\155\141\160'; // -> 'map'
'\143\157\156\163\164\162\165\143\164\157\162'; // -> 'constructor'
'\141\154\145\162\164(666)'; // -> 'alert(666)'

위와 같이 됩니다. 따라서

[666]['map']['constructor']('alert(666)')(666);

위와 같이 되며, 이는

  1. 666이 들어있는 배열의 map 메서드
  2. map 메서드의 constructor, 즉 생성자 함수를 문자열 매개변수 alert(666)과 함께 호출
  3. 그 결과를 매개변수 666으로 또 한 번 호출

위와 같이 정리할 수 있습니다. 따라서

[666].map.constructor === Function; // -> true
const fn = Function('alert(666)'); // 함수 body가 'alert(666)'인 새로운 함수를 생성

fn(666); // 매개변수 666과 함께 호출. 하지만 매개변수 666은 무시된다

위와 같이 정리할 수 있습니다.

An infinite timeout

setTimeout(() => console.log("called"), Infinity); // -> <timeoutId>
// > 'called'

💡 해설

setTimeout의 두번째 매개변수인 ms는 4byte 정수 범위에서만 허용되므로, Infinity는 무시되고 즉시 호출됩니다.

Double dot

27.toString(); // > Uncaught SyntaxError: Invalid or unexpected token
(27).toString(); // -> '27'

💡 해설

대부분의 프로그래밍 언어에서는 변수의 시작문자로 숫자를 허용하지 않습니다. 그 이유는, 코드가 해석될 때 시작문자가 숫자면 해당 토큰을 숫자로 해석하기 때문에 변수에 허용하지 않는 것입니다.

또, 저희는 {}의 두 가지 의미를 지금까지 알아보았습니다. 하나는 코드블럭이고, 하나는 객체 리터럴입니다.

위의 예제 역시 마찬가지입니다. 27.toString()의 경우, 자바스크립트 엔진은 27.을 읽을 때 이미 이 값을 숫자로 해석합니다. 하지만 그 뒤에 toString이라는 문자열이 나와 이를 유효하지 않은 코드로 판단하는 겁니다.

따라서, 숫자를 Number Wrapper Class로 암묵적으로 캐스팅되게 하려면 괄호()를 이용하여 자바스크립트 엔진이 이를 올바르게 해석할 수 있게 도와주면 됩니다.

Extra Newness

class Foo extends Function {
  constructor(val) {
    super();
    this.prototype.val = val;
  }
}

new new Foo(":D")().val; // -> ':D'

💡 해설

이 코드에서 유심히 살펴볼 내용은 Function을 상속한다는 점입니다.

// 이 코드를 하나씩 해석해봅시다.
(new (new Foo(':D')))().val;

// foo는 Foo 인스턴스입니다.
// Foo 인스턴스는 Function 클래스를 상속합니다.
// 따라서, 함수의 속성을 갖고 있습니다.
const foo = new Foo(':D');

// 함수는 `new` 키워드와 함께 호출되면 생성자 함수로 동작합니다.
// 즉, 또 다른 인스턴스가 생성됩니다.
// foo2는 foo 인스턴스라고 볼 수 있습니다.
const foo2 = new foo();

// Foo는 인스턴스화 될 때, 해당 인스턴스의 prototype 값에 val을 할당합니다.
// 따라서, foo.prototype.val = ':D'가 됩니다.
// 모든 함수 객체는 prototype을 갖고 있으며
// 이 함수 객체가 생성자 함수로 새로운 인스턴스를 만들게 되면
// 해당 인스턴스는 프로토타입 체이닝에 의해
// 함수 객체의 prototype과 연결됩니다.
// 즉, foo2는 foo.prototype과 연결되어 있으며
// 따라서 foo2.val은 foo.prototype.val 이 됩니다.
foo2.val; // -> ':D'

Why you should use semicolons

class SomeClass {
  ["array"] = ([]["string"] = "str");
}

new SomeClass().array; // -> 'str'

💡 해설

위 코드는 computed property를 이용한 예제입니다.

클래스가 해석하기 힘들다면, 아래와 같이 객체를 이용하여 테스트하면 좀 더 수월하게 이해하실 수 있습니다.

({
  ['array']: ([]['string'] = 'str')
}).array // -> 'str'

([]['string'] = 'str')에서 string은 사실 아무런 의미도 없습니다. 단지

  1. 배열은 객체입니다.
  2. 따라서, 배열도 프로퍼티를 가질 수 있습니다.
  3. []['string'] = 'str'은 배열 []의 프로퍼티 stringstr 문자열을 할당한 것입니다.
  4. []['string'] = 'str'에서 =assignment operator이므로, 결국 expression입니다.
  5. 따라서 결과는 'str'이고, 이 값이 computed property에 의해 array 프로퍼티로 할당됩니다.

Split a string by a space

"".split(""); // -> []
// but…
"".split(" "); // -> [""]

💡 해설

명세의 노트에 의하면

If the this value is (or converts to) the empty String, the result depends on whether separator can match the empty String. If it can, the result array contains no elements. Otherwise, the result array contains one element, which is the empty String.

즉, thisempty String일 때 seperatorempty string이면 아무 값도 포함되지 않은 배열이 리턴되지만, 그 이외의 경우 empty String이 포함된 하나의 배열이 리턴된다고 설명되어있습니다.

A stringified string

JSON.stringify("production") === "production"; // -> false

💡 해설

문자열을 JSON 형식으로 변경하면, 해당 문자열은 문자열임을 알려주는 따옴표가 포함된 문자열이 리턴됩니다.

JSON.stringify("production") === '"production"'; // -> true

Non-strict comparison of a number to true

1 == true; // -> true
// but…
Boolean(1.1); // -> true
1.1 == true; // -> false

💡 해설

위의 예제는 지금까지 계속 다뤄왔던 내용이므로 설명을 생략하겠습니다.

마치며

8편을 마지막으로, WTFJS를 모두 정리해보았습니다.

어떤 섹션들은 모르면 낭패를 볼 수도 있는 내용을 담고 있지만, 대부분의 내용은 일반적인 경우 몰라도 되는 내용들이 많이 있었습니다.

특히 개인적으로, ==의 경우 괴랄한 결과들을 보여주기 때문에 이왕이면 ===를 사용해야겠다는 마음이 더 굳건해졌던 것 같습니다. ==의 결과를 예측할 수 있는 경우에는 분명히 도움이 되겠지만, 되도록 ===를 사용하는 것이 좋을 것입니다. 특히 요즘은 타입스크립트가 광범위하게 사용되고 있는 추세기 때문에, 아무리 동적 타입 언어라 할지라도 변수의 데이터형에 신경을 쓰는 것이 버그를 줄이는 길일 것입니다.

이 포스팅을 마지막으로 WTFJS의 정리를 마치도록 하겠습니다. 감사합니다!🤣

profile
undefined cat

0개의 댓글