wtfjs README

The project's README from github.com/denysdovhan/wtfjs. As one markdown file: README.md.

SWUbanner

What the f*ck JavaScript?

[![WTFPL 2.0][license-image]][license-url] [![NPM version][npm-image]][npm-url] [![Patreon][patreon-image]][patreon-url] [![Buy Me A Coffee][bmc-image]][bmc-url]

A list of funny and tricky JavaScript examples

JavaScript is a great language. It has a simple syntax, large ecosystem and, what is most important, a great community.

At the same time, we all know that JavaScript is quite a funny language with tricky parts. Some of them can quickly turn our everyday job into hell, and some of them can make us laugh out loud.

The original idea for WTFJS belongs to Brian Leroux. This list is highly inspired by his talk “WTFJS” at dotJS 2012:

dotJS 2012 - Brian Leroux - WTFJS

Node Packaged Manuscript

You can install this handbook using npm. Just run:

$ npm install -g wtfjs

You should be able to run wtfjs at the command line now. This will open the manual in your selected $PAGER. Otherwise, you may continue reading on here.

The source is available here: https://github.com/denysdovhan/wtfjs

Translations

Currently, there are these translations of wtfjs:

Help translating to your language

Note: Translations are maintained by their translators. They may not contain every example, and existing examples may be outdated.

Table of Contents

💪🏻 Motivation

Just for fun

— “Just for Fun: The Story of an Accidental Revolutionary”, Linus Torvalds

The primary goal of this list is to collect some crazy examples and explain how they work, if possible. Just because it's fun to learn something that we didn't know before.

If you are a beginner, you can use these notes to get a deeper dive into JavaScript. I hope these notes will motivate you to spend more time reading the specification.

If you are a professional developer, you can consider these examples as a great reference for all of the quirks and unexpected edges of our beloved JavaScript.

In any case, just read this. You're probably going to find something new.

⚠️ Note: If you enjoy reading this document, please, consider supporting the author of this collection.

✍🏻 Notation

// -> is used to show the result of an expression. For example:

1 + 1; // -> 2

// > means the result of console.log or another output. For example:

console.log("hello, world!"); // > hello, world!

// is just a comment used for explanations. Example:

// Assigning a function to foo constant
const foo = function() {};

👀 Examples

[] is equal ![]

Array is equal not array:

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

💡 Explanation:

The abstract equality operator converts both sides to numbers to compare them, and both sides become the number 0 for different reasons. Arrays are truthy, so on the right, the opposite of a truthy value is false, which is then coerced to 0. On the left, however, an empty array is coerced to a number without becoming a boolean first, and empty arrays are coerced to 0, despite being truthy.

Here is how this expression simplifies:

+[] == +![];
0 == +false;
0 == 0;
true;

See also [] is truthy, but not true.

true is not equal ![], but not equal [] too

Array is not equal true, but not Array is not equal true too; Array is equal false, not Array is equal false too:

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

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

💡 Explanation:

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

// According to the specification

true == []; // -> false

toNumber(true); // -> 1
toNumber([]); // -> 0

1 == 0; // -> false

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

![]; // -> false

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

// According to the specification

false == []; // -> true

toNumber(false); // -> 0
toNumber([]); // -> 0

0 == 0; // -> true

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

![]; // -> false

false == false; // -> true

true is false

!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true

💡 Explanation:

Consider this step-by-step:

// true is 'truthy' and represented by value 1 (number), 'true' in string form is NaN.
true == "true"; // -> false
false == "false"; // -> false

// 'false' is not the empty string, so it's a truthy value
!!"false"; // -> true
!!"true"; // -> true

baNaNa

"b" + "a" + +"a" + "a"; // -> 'baNaNa'

This is an old-school joke in JavaScript, but remastered. Here's the original one:

"foo" + +"bar"; // -> 'fooNaN'

💡 Explanation:

The expression is evaluated as 'foo' + (+'bar'), which converts 'bar' to not a number.

NaN is not a NaN

NaN === NaN; // -> false

💡 Explanation:

The specification strictly defines the logic behind this behavior:

  1. If Type(x) is different from Type(y), return false.
  2. If Type(x) is Number, then
    1. If x is NaN, return false.
    2. If y is NaN, return false.
    3. … … …

— 7.2.14 Strict Equality Comparison

Following the definition of NaN from the IEEE:

Four mutually exclusive relations are possible: less than, equal, greater than, and unordered. The last case arises when at least one operand is NaN. Every NaN shall compare unordered with everything, including itself.

— “What is the rationale for all comparisons returning false for IEEE754 NaN values?” at StackOverflow

Object.is() and === weird cases

Object.is() determines if two values have the same value or not. It works similar to the === operator but there are a few weird cases:

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

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

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

💡 Explanation:

In JavaScript lingo, NaN and NaN are the same value but they're not strictly equal. NaN === NaN being false is apparently due to historical reasons so it would probably be better to accept it as it is.

Similarly, -0 and 0 are strictly equal, but they're not the same value.

For more details about NaN === NaN, see the above case.

It's a fail

You would not believe, but …

(![] + [])[+[]] +
  (![] + [])[+!+[]] +
  ([![]] + [][[]])[+!+[] + [+[]]] +
  (![] + [])[!+[] + !+[]];
// -> 'fail'

💡 Explanation:

By breaking that mass of symbols into pieces, we notice that the following pattern occurs often:

![] + []; // -> 'false'
![]; // -> false

So we try adding [] to false. But due to a number of internal function calls (binary + Operator -> ToPrimitive -> [[DefaultValue]]) we end up converting the right operand to a string:

![] + [].toString(); // 'false'

Thinking of a string as an array we can access its first character via [0]:

"false"[0]; // -> 'f'

The rest is obvious, but the i is tricky. The i in fail is grabbed by generating the string 'falseundefined' and grabbing the element on index ['10'].

More examples:

+![]          // -> 0
+!![]         // -> 1
!![]          // -> true
![]           // -> false
[][[]]        // -> undefined
+!![] / +![]  // -> Infinity
[] + {}       // -> "[object Object]"
+{}           // -> NaN

[] is truthy, but not true

An array is a truthy value, however, it's not equal to true.

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

💡 Explanation:

Here are links to the corresponding sections in the ECMA-262 specification:

null is falsy, but not false

Despite the fact that null is a falsy value, it's not equal to false.

!!null; // -> false
null == false; // -> false

At the same time, other falsy values, like 0 or '' are equal to false.

0 == false; // -> true
"" == false; // -> true

💡 Explanation:

The explanation is the same as for previous example. Here's the corresponding link:

document.all is an object, but it is undefined

⚠️ This is part of the Browser API and won't work in a Node.js environment ⚠️

Despite the fact that document.all is an array-like object and it gives access to the DOM nodes in the page, it responds to the typeof function as undefined.

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

At the same time, document.all is not equal to undefined.

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

But at the same time:

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

💡 Explanation:

document.all used to be a way to access DOM elements, in particular with old versions of IE. While it has never been a standard it was broadly used in the old age JS code. When the standard progressed with new APIs (such as document.getElementById) this API call became obsolete and the standard committee had to decide what to do with it. Because of its broad use they decided to keep the API but introduce a willful violation of the JavaScript specification. The reason why it responds to false when using the Strict Equality Comparison with undefined while true when using the Abstract Equality Comparison is due to the willful violation of the specification that explicitly allows that.

— “Obsolete features - document.all” at WhatWG - HTML spec — “Chapter 4 - ToBoolean - Falsy values” at YDKJS - Types & Grammar

Minimal value is greater than zero

Number.MIN_VALUE is the smallest number, which is greater than zero:

Number.MIN_VALUE > 0; // -> true

💡 Explanation:

Number.MIN_VALUE is 5e-324, i.e. the smallest positive number that can be represented within float precision, i.e. that's as close as you can get to zero. It defines the best resolution that floats can give you.

Now the overall smallest value is Number.NEGATIVE_INFINITY although it's not really numeric in a strict sense.

— “Why is 0 less than Number.MIN_VALUE in JavaScript?” at StackOverflow

function is not a function

⚠️ A bug present in V8 v5.5 or lower (Node.js <=7) ⚠️

All of you know about the annoying undefined is not a function, but what about this?

// Declare a class which extends null
class Foo extends null {}
// -> [Function: Foo]

new Foo() instanceof null;
// > TypeError: function is not a function
// >     at … … …

💡 Explanation:

This is not a part of the specification. It's just a bug that has now been fixed, so there shouldn't be a problem with it in the future.

Super constructor null of Foo is not a constructor

It's continuation of story with previous bug in modern environment (tested with Chrome 71 and Node.js v11.8.0).

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

💡 Explanation:

This is not a bug because:

Object.getPrototypeOf(Foo.prototype); // -> null

If the class has no constructor the call from prototype chain. But in the parent has no constructor. Just in case, I’ll clarify that null is an object:

typeof null === "object";

Therefore, you can inherit from it (although in the world of the OOP for such terms would have beaten me). So you can't call the null constructor. If you change this code:

class Foo extends null {
  constructor() {
    console.log("something");
  }
}

You see the error:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

And if you add super:

class Foo extends null {
  constructor() {
    console.log(111);
    super();
  }
}

JS throws an error:

TypeError: Super constructor null of Foo is not a constructor

Adding arrays

What if you try to add two arrays?

[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'

💡 Explanation:

The concatenation happens. Step-by-step, it looks like this:

[1, 2, 3] +
  [4, 5, 6][
    // call toString()
    (1, 2, 3)
  ].toString() +
  [4, 5, 6].toString();
// concatenation
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");

Trailing commas in array

You've created an array with 4 empty elements

wtfjs (denysdovhan/wtfjs)

Searching Database…

37.7K ·GitHub
这是什么?这是一本精选手册(WTFJS),收录了一批有趣又容易让人困惑的 JavaScript 示例。每个代码片段先展示出人意料的行为,然后逐步解释原因,并附上 ECMAScript 规范的链接。
这是教程,还是一份 JS 奇怪行为的参考清单?这是一个参考式的合集:每一条都先给出一段令人困惑的 JS 代码,然后详细说明它为什么会这样运行,并引用相关的规范章节。
我该怎么阅读它?你可以直接在 GitHub 上阅读 README,也可以通过 npm 将它安装为命令行手册,然后运行 `wtfjs` 命令,在终端的分页器中打开。
除了 Node 和 npm,还需要其他东西才能使用吗?如果只想使用 `wtfjs` 命令行阅读器,就需要 Node.js/npm;否则无需安装任何东西,因为它只是一份可以在 GitHub 上阅读的 Markdown 文档。
所有示例在任何 JS 环境中都有效吗?并非如此——有几个示例依赖 'document.all' 等仅限浏览器的 API,在 Node.js 中无法运行;至少有一个示例是 V8 引擎的一个漏洞(Node.js <=7),该漏洞后来已被修复。
这对初学者有用,还是只对有经验的 JS 开发者有用?两者都有用:作者表示,初学者可以借助它更深入地理解 JS 基础知识,专业开发者则可以把它当作语言怪癖和边界情况的参考。
这个合集的灵感来自哪里?该项目的灵感来自 Brian Leroux 在 2012 年 dotJS 大会上的 'WTFJS' 演讲,这场演讲首次让人们开始收集 JavaScript 的各种奇怪特性。
baNaNa 示例讲的是什么?'foo' + + 'bar' 的计算方式是 'foo' + (+'bar');由于 'bar' 无法转换为数字,结果变成 NaN,最终得到字符串 'fooNaN'。
列出的这些“bug”现在还能复现吗?并非全部——其中一个示例('function is not a function')只针对旧版 V8(Node.js <=7),后来已被修复,因此它更像是历史趣闻,而不是当下会踩的坑。
Size
1.5 MB
Stars
37.7K
Age
9y
Commits · 90 Days