Skip to content

Latest commit

 

History

History
1331 lines (907 loc) · 40.9 KB

README.md

File metadata and controls

1331 lines (907 loc) · 40.9 KB

What the f*ck JavaScript?

WTFPL 2.0 NPM version

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, 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

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.

✍🏻 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:

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 empty string, so it's truthy value
!!'false' // -> true
!!'true'  // -> true

baNaNa

'b' + 'a' + + 'a' + 'a'

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

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']

[] 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

⚠️ It's a part of Browser API and wouldn'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 it's 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 particolar 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 progress with new APIs (such document.getElementById) this API call became obsolete and the standard commitee had to decide what to do with it. Because of it's 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 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 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.

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. Despite all, you'll get an arrary with three elements, because of trailing commas:

let a = [,,,]
a.length     // -> 3
a.toString() // -> ',,'

💡 Explanation:

Trailing commas (sometimes called "final commas") can be useful when adding new elements, parameters, or properties to JavaScript code. If you want to add a new property, you can simply add a new line without modifying the previously last line if that line already uses a trailing comma. This makes version-control diffs cleaner and editing code might be less troublesome.

Trailing commas at MDN

Array equality is a monster

Array equality is a monster in JS, think below:

[] == ''   // -> true
[] == 0    // -> true
[''] == '' // -> true
[0] == 0   // -> true
[0] == ''  // -> false
[''] == 0  // -> true

[null] == ''      // true
[null] == 0       // true
[undefined] == '' // true
[undefined] == 0  // true

[[]] == 0  // true
[[]] == '' // true

[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0  // true

[[[[[[ null ]]]]]] == 0  // true
[[[[[[ null ]]]]]] == '' // true

[[[[[[ undefined ]]]]]] == 0  // true
[[[[[[ undefined ]]]]]] == '' // true

💡 Explanation:

You should be very careful for above! This is a complex examples, but it's described in 7.2.13 Abstract Equality Comparison section of the specification.

undefined and Number

If we don't pass any arguments into the Number constructor, we'll get 0. The value undefined is assigned to formal arguments when there are no actual arguments, so you might expect that Number without arguments takes undefined as a value of its parameter. However, when we pass undefined, we will get NaN.

Number()          // -> 0
Number(undefined) // -> NaN

💡 Explanation:

According to the specification:

  1. If no arguments were passed to this function's invocation, let n be +0.
  2. Else, let n be ? ToNumber(value).
  3. In case of undefined, ToNumber(undefined) should return NaN.

Here's the corresponding section:

parseInt is a bad guy

parseInt is famous by its quirks:

parseInt('f*ck');     // -> NaN
parseInt('f*ck', 16); // -> 15

💡 Explanation: This happens because parseInt will continue parsing character-by-character until it hits a character it doesn't know. The f in 'f*ck' is the hexadecimal digit 15.

Parsing Infinity to integer is something…

//
parseInt('Infinity', 10) // -> NaN
// ...
parseInt('Infinity', 18) // -> NaN...
parseInt('Infinity', 19) // -> 18
// ...
parseInt('Infinity', 23) // -> 18...
parseInt('Infinity', 24) // -> 151176378
// ...
parseInt('Infinity', 29) // -> 385849803
parseInt('Infinity', 30) // -> 13693557269
// ...
parseInt('Infinity', 34) // -> 28872273981
parseInt('Infinity', 35) // -> 1201203301724
parseInt('Infinity', 36) // -> 1461559270678...
parseInt('Infinity', 37) // -> NaN

Be careful with parsing null too:

parseInt(null, 24) // -> 23

💡 Explanation:

It's converting null to the string "null" and trying to convert it. For radixes 0 through 23, there are no numerals it can convert, so it returns NaN. At 24, "n", the 14th letter, is added to the numeral system. At 31, "u", the 21st letter, is added and the entire string can be decoded. At 37 on there is no longer any valid numeral set that can be generated and NaN is returned.

“parseInt(null, 24) === 23… wait, what?” at StackOverflow

Don't forget about octals:

parseInt('06'); // 6
parseInt('08'); // 8 if support ECMAScript 5
parseInt('08'); // 0 if not support ECMAScript 5

💡 Explanation: If the input string begins with "0", radix is eight (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 specifies that 10 (decimal) is used, but not all browsers support this yet. For this reason always specify a radix when using parseInt.

parseInt always convert input to string:

parseInt({ toString: () => 2, valueOf: () => 1 }) // -> 2
Number({ toString: () => 2, valueOf: () => 1 })   // -> 1

Math with true and false

Let's do some math:

true + true // -> 2
(true + true) * (true + true) - true // -> 3

Hmmm… 🤔

💡 Explanation:

We can coerce values to numbers with the Number constructor. It's quite obvious that true will be coerced to 1:

Number(true) // -> 1

The unary plus operator attempts to convert its value into a number. It can convert string representations of integers and floats, as well as the non-string values true, false, and null. If it cannot parse a particular value, it will evaluate to NaN. That means we can coerce true to 1 easier:

+true // -> 1

When you're performing addition or multiplication, the ToNumber method is invoked. According to the specification, this method returns:

If argument is true, return 1. If argument is false, return +0.

That's why we can add boolean values as regular numbers and get correct results.

Corresponding sections:

HTML comments are valid in JavaScript

You will be impressed, but <!-- (which is known as HTML comment) is a valid comment in JavaScript.

// valid comment
<!-- valid comment too

💡 Explanation:

Impressed? HTML-like comments were intended to allow browsers that didn't understand the <script> tag to degrade gracefully. These browsers, e.g. Netscape 1.x are no longer popular. So there is really no point in putting HTML comments in your script tags anymore.

Since Node.js is based on the V8 engine, HTML-like comments are supported by the Node.js runtime too. Moreover, they're a part of the specification:

NaN is not a number

Type of NaN is a 'number':

typeof NaN            // -> 'number'

💡 Explanation:

Explanations of how typeof and instanceof operators work:

[] and null are objects

typeof []   // -> 'object'
typeof null // -> 'object'

// however
null instanceof Object // false

💡 Explanation:

The behavior of typeof operator is defined in this section of the specification:

According to the specification, the typeof operator returns a string according to Table 35: typeof Operator Results. For null, ordinary, standard exotic and non-standard exotic objects, which do not implement [[Call]], it returns the string "object".

However, you can check the type of an object by using the toString method.

Object.prototype.toString.call([])
// -> '[object Array]'

Object.prototype.toString.call(new Date)
// -> '[object Date]'

Object.prototype.toString.call(null)
// -> '[object Null]'

Magically increasing numbers

999999999999999  // -> 999999999999999
9999999999999999 // -> 10000000000000000

10000000000000000       // -> 10000000000000000
10000000000000000 + 1   // -> 10000000000000000
10000000000000000 + 1.1 // -> 10000000000000002

💡 Explanation:

This is caused by IEEE 754-2008 standard for Binary Floating-Point Arithmetic. At this scale, it rounds to the nearest even number. Read more:

Precision of 0.1 + 0.2

A well-known joke. An addition of 0.1 and 0.2 is deadly precise:

0.1 + 0.2 // -> 0.30000000000000004
(0.1 + 0.2) === 0.3 // -> false

💡 Explanation:

The answer for the ”Is floating point math broken?” question on StackOverflow:

The constants 0.2 and 0.3 in your program will also be approximations to their true values. It happens that the closest double to 0.2 is larger than the rational number 0.2 but that the closest double to 0.3 is smaller than the rational number 0.3. The sum of 0.1 and 0.2 winds up being larger than the rational number 0.3 and hence disagreeing with the constant in your code.

This problem is so known that there is even a website called 0.30000000000000004.com. It occurs in every language that uses floating-point math, not just JavaScript.

Patching numbers

You can add your own methods to wrapper objects like Number or String.

Number.prototype.isOne = function () {
  return Number(this) === 1
}

1.0.isOne() // -> true
1..isOne()  // -> true
2.0.isOne() // -> false
(7).isOne() // -> false

💡 Explanation:

Obviously, you can extend Number object like any other object in JavaScript. However, it's not recommended if the behavior of defined method is not a part of the specification. Here is the list of Number's properties:

Comparison of three numbers

1 < 2 < 3 // -> true
3 > 2 > 1 // -> false

💡 Explanation:

Why does this work that way? Well, the problem is in the first part of an expression. Here's how it works:

1 < 2 < 3 // 1 < 2 -> true
true  < 3 // true -> 1
1     < 3 // -> true

3 > 2 > 1 // 3 > 2 -> true
true  > 1 // true -> 1
1     > 1 // -> false

We can fix this with Greater than or equal operator (>=):

3 > 2 >= 1 // true

Read more about Relational operators in the specification:

Funny math

Often the results of arithmetic operations in JavaScript might be quite unexpected. Consider these examples:

 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

💡 Explanation:

What's happening in the first four examples? Here's a small table to understand addition in JavaScript:

Number  + Number  -> addition
Boolean + Number  -> addition
Boolean + Boolean -> addition
Number  + String  -> concatenation
String  + Boolean -> concatenation
String  + String  -> concatenation

What about other examples? A ToPrimitive and ToString methods are being implicitly called for [] and {} before addition. Read more about evaluation process in the specification:

Addition of RegExps

Did you know you can add numbers like this?

// Patch a toString method
RegExp.prototype.toString = function() {
  return this.source
}

/7/ - /5/ // -> 2

💡 Explanation:

Strings aren't instances of String

'str' // -> 'str'
typeof 'str' // -> 'string'
'str' instanceof String // -> false

💡 Explanation:

The String constructor returns a string:

typeof String('str')   // -> 'string'
String('str')          // -> 'str'
String('str') == 'str' // -> true

Let's try with a new:

new String('str') == 'str' // -> true
typeof new String('str')   // -> 'object'

Object? What's that?

new String('str') // -> [String: 'str']

More information about the String constructor in the specification:

Calling functions with backticks

Let's declare a function which logs all params into the console:

function f(...args) {
  return args
}

No doubt, you know you can call this function like this:

f(1, 2, 3) // -> [ 1, 2, 3 ]

But did you know you can call any function with backticks?

f`true is ${true}, false is ${false}, array is ${[1,2,3]}`
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// ->   true,
// ->   false,
// ->   [ 1, 2, 3 ] ]

💡 Explanation:

Well, this is not magic at all if you're familiar with Tagged template literals. In the example above, f function is a tag for template literal. Tags before template literal allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions. Example:

function template(strings, ...keys) {
  // do something with strings and keys…
}

This is the magic behind famous library called 💅 styled-components, which is popular in the React community.

Link to the specification:

Call call call

Found by @cramforce

console.log.call.call.call.call.call.apply(a => a, [1, 2])

💡 Explanation:

Attention, it could break your mind! Try to reproduce this code in your head: we're applying the call method using the apply method. Read more:

A constructor property

const c = 'constructor'
c[c][c]('console.log("WTF?")')() // > WTF?

💡 Explanation:

Let's consider this example step-by-step:

// Declare a new constant which is a string 'constructor'
const c = 'constructor'

// c is a string
c // -> 'constructor'

// Getting a constructor of string
c[c] // -> [Function: String]

// Getting a constructor of constructor
c[c][c] // -> [Function: Function]

// Call the Function constructor and pass
// the body of new function as an argument
c[c][c]('console.log("WTF?")') // -> [Function: anonymous]

// And then call this anonymous function
// The result is console-logging a string 'WTF?'
c[c][c]('console.log("WTF?")')() // > WTF?

An Object.prototype.constructor returns a reference to the Object constructor function that created the instance object. In case with strings it is String, in case with numbers it is Number and so on.

Object as a key of object's property

{ [{}]: {} } // -> { '[object Object]': {} }

💡 Explanation:

Why does this work so? Here we're using a Computed property name. When you pass an object between those brackets, it coerces object to a string, so we get the property key '[object Object]' and the value {}.

We can make "brackets hell" like this:

({[{}]:{[{}]:{}}})[{}][{}] // -> {}

// structure:
// {
//   '[object Object]': {
//     '[object Object]': {}
//   }
// }

Read more about object literals here:

Accessing prototypes with __proto__

As we know, primitives don't have prototypes. However, if we try to get a value of __proto__ for primitives, we would get this:

(1).__proto__.__proto__.__proto__ // -> null

💡 Explanation:

This happens because when something doesn't have a prototype, it will be wrapped into a wrapper object using the ToObject method. So, step-by-step:

(1).__proto__ // -> [Number: 0]
(1).__proto__.__proto__ // -> {}
(1).__proto__.__proto__.__proto__ // -> null

Here is more information about __proto__:

`${{Object}}`

What is the result of the expression below?

`${{Object}}`

The answer is:

// -> '[object Object]'

💡 Explanation:

We defined an object with a property Object using Shorthand property notation:

{ Object: Object }

Then we've passed this object to the template literal, so the toString method calls for that object. That's why we get the string '[object Object]'.

Destructuring with default values

Consider this example:

let x, { x: y = 1 } = { x }; y;

The example above is a great task for an interview. What the value of y? The answer is:

// -> 1

💡 Explanation:

let x, { x: y = 1 } = { x }; y;
//  ↑       ↑           ↑    ↑
//  1       3           2    4

With the example above:

  1. We declare x with no value, so it's undefined.
  2. Then we pack the value of x into the object property x.
  3. Then we extract the value of x using destructuring and want to assign it to y. If the value is not defined, then we're going to use 1 as the default value.
  4. Return the value of y.

Dots and spreading

Interesting examples could be composed with spreading of arrays. Consider this:

[...[...'...']].length // -> 3

💡 Explanation:

Why 3? When we use the spread operator, the @@iterator method is called, and the returned iterator is used to obtain the values to be iterated. The default iterator for string spreads a string into characters. After spreading, we pack these characters into an array. Then we spread this array again and pack it back to an array.

A '...' string consists with three . characters, so the length of resulting array is 3.

Now, step-by-step:

[...'...']             // -> [ '.', '.', '.' ]
[...[...'...']]        // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3

Obviously, we can spread and wrap the elements of an array as many times as we want:

[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]
// and so on …

Labels

Not so many programmers know about labels in JavaScript. They are kind of interesting:

foo: {
  console.log('first');
  break foo;
  console.log('second');
}

// > first
// -> undefined

💡 Explanation:

The labeled statement is used with break or continue statements. You can use a label to identify a loop, and then use the break or continue statements to indicate whether a program should interrupt the loop or continue its execution.

In the example above, we identify a label foo. After that console.log('first'); executes and then we interrupt the execution.

Read more about labels in JavaScript:

Nested labels

a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5

💡 Explanation:

Similar to previous examples, follow these links:

Insidious try..catch

What will this expression return? 2 or 3?

(() => {
  try {
    return 2;
  } finally {
    return 3;
  }
})()

The answer is 3. Surprised?

💡 Explanation:

Is this multiple inheritance?

Take a look at the example below:

new (class F extends (String, Array) { }) // -> F []

Is this a multiple inheritance? Nope.

💡 Explanation:

The interesting part is the value of the extends clause ((String, Array)). The grouping operator always returns its last argument, so (String, Array) is actually just Array. That means we've just created a class which extends Array.

A generator which yields itself

Consider this example of a generator which yields itself:

(function* f() { yield f })().next()
// -> { value: [GeneratorFunction: f], done: false }

As you can see, the returned value is an object with its value equal to f. In that case, we can do something like this:

(function* f() { yield f })().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }

// and again
(function* f() { yield f })().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }

// and again
(function* f() { yield f })().next().value().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }

// and so on
// …

💡 Explanation:

To understand why this works that way, read these sections of the specification:

A class of class

Consider this obfuscated syntax playing:

(typeof (new (class { class () {} }))) // -> 'object'

It seems like we're declaring a class inside of class. Should be and error, however, we get the string 'object'.

💡 Explanation:

Since ECMAScript 5 era, keywords are allowed as property names. So think about it as about this simple object example:

const foo = {
  class: function() {}
};

And ES6 standardized shorthand method definitions. Also, classes can be anonymous. So if we drop : function part, we're going to get:

class {
  class() {}
}

The result of a default class is always a simple object. And its typeof should return 'object'.

Read more here:

Non-coercible objects

With well-known symbols, there's a way to get rid of type coercion. Take a look:

function nonCoercible(val) {
  if (val == null) {
    throw TypeError('nonCoercible should not be called with null or undefined')
  }

  const res = Object(val)

  res[Symbol.toPrimitive] = () => {
    throw TypeError('Trying to coerce non-coercible object')
  }

  return res
}

Now we can use this like this:

// objects
const foo = nonCoercible({foo: 'foo'})

foo * 10      // -> TypeError: Trying to coerce non-coercible object
foo + 'evil'  // -> TypeError: Trying to coerce non-coercible object

// strings
const bar = nonCoercible('bar')

bar + '1'                 // -> TypeError: Trying to coerce non-coercible object
bar.toString() + 1        // -> bar1
bar === 'bar'             // -> false
bar.toString() === 'bar'  // -> true
bar == 'bar'              // -> TypeError: Trying to coerce non-coercible object

// numbers
const baz = nonCoercible(1)

baz == 1             // -> TypeError: Trying to coerce non-coercible object
baz === 1            // -> false
baz.valueOf() === 1  // -> true

💡 Explanation:

Tricky arrow functions

Consider the example below:

let f = () => 10
f() // -> 10

Okay, fine, but what about this:

let f = () => {}
f() // -> undefined

💡 Explanation:

You might expect {} instead of undefined. This is because the curly braces are part of the syntax of the arrow functions, so f will return undefined.

arguments and arrow functions

Consider the example below:

let f = function(){
  return arguments;
}
f('a'); // -> { '0': 'a' }

Now, try do to the same with an arrow function:

let f = () =>  arguments;
f('a'); // -> Uncaught ReferenceError: arguments is not defined

💡 Explanation:

Arrow functions are lightweight version of regular functions with a focus on being short and lexical this. At the same time arrow functions do not provide a binding for the arguments object. As a valid alternative use the rest parameters to achieve the same result:

let f = (...args) => args;
f('a');

Tricky return

return statement is also tricky. Consider this:

(function () {
  return
  {
    b : 10
  }
})() // -> undefined

💡 Explanation:

return and the returned expression must be in the same line:

(function () {
  return {
    b : 10
  }
})() // -> { b: 10 }

Accessing object properties with arrays

var obj = { property: 1 }
var array = ['property']

obj[array] // -> 1

What about pseudo-multidimensional arrays?

var map = {}
var x = 1
var y = 2
var z = 3

map[[x, y, z]] = true
map[[x + 10, y, z]] = true

map["1,2,3"]  // -> true
map["11,2,3"] // -> true

💡 Explanation:

The brackets [] operator converts the expression passed toString. Converting an one-element array to string it's like converting the element to the string:

['property'].toString() // -> 'property'

Other resources

  • wtfjs.com — a collection of those very special irregularities, inconsistencies and just plain painfully unintuitive moments for the language of the web.
  • Wat — A lightning talk by Gary Bernhardt from CodeMash 2012
  • What the... JavaScript? — Kyle Simpsons talk for Forward 2 attempts to “pull out the crazy” from JavaScript. He wants to help you produce cleaner, more elegant, more readable code, then inspire people to contribute to the open source community.

🎓 License

CC 4.0

© Denys Dovhan