Нотация

// -> используется для отображения результата выражения. Например:

1 + 1 // -> 2

// -> означает результат console.log или другие выходные данные.

[] эквивалентно ![]

Массив эквивалентен не массиву:

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

baNaNa

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

Это олдскульная шутка на JavaScript, но переработанная. Вот исходник:

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

Выражение вычисляется как 'foo' + (+'bar'), то есть 'bar' преобразуется в нечисловое значение.

NaN не является NaN

NaN === NaN // -> false

Это fail

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

Если разбить эту кучу символов на части, то можно заметить часто повторяющийся паттерн:

(![]+[]) // -> 'false'
![]      // -> false

Попробуем добавить [] к false. Но из-за нескольких внутренних вызовов фукнций (binary + Operator -> ToPrimitive -> [[DefaultValue]]) мы в результате преобразуем правый операнд в строку:

(![]+[].toString()) // -> 'false'

Если считать строку массивом, то можно обратиться к первому символу с помощью [0]:

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

Остальное очевидно, но с i не всё так просто. Символ i в значении fail получается с помощью генерирования строки 'falseundefined' и вытаскивания элемента с индексом ['10'].

[] является «истинноватым», но не true

Массив является «истинноватым» (truthy) значением, которое, однако, не эквивалентно true:

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

null является «ложноватым», но не false

Несмотря на то, что null является «ложноватым» (falsy) значением, оно не эквивалентно false:

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

Минимальное значение больше нуля

Number.MIN_VALUE является самым маленьким числом больше нуля:

Number.MIN_VALUE > 0 // -> true

Number.MIN_VALUE — это 5e-324, то есть самое маленькое положительное число, которое можно выразить с точностью плавающей запятой (float precision), то есть находящееся ближе всех к нулю. Это наилучшая точность, обеспечиваемая плавающей запятой.

Сложение массивов

Что если попробовать сложить два массива?

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

Выполняется конкатенация. Разберём пошагово:

[1, 2, 3] + [4, 5, 6]
// вызывается toString()
[1, 2, 3].toString() + [4, 5, 6].toString()
// конкатенация
'1,2,3' + '4,5,6'
// ->
'1,2,34,5,6'

Плохой parseInt

parseInt славится своими причудами:

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

Так происходит потому, что parseInt продолжает разбирать символ за символом, пока не наткнётся на неизвестный символ. f в 'f*ck' является шестнадцатеричным выражением числа 15.

Вычисления с помощью true и false

Давайте повычисляем:

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

С помощью конструктора Number мы можем неявно преобразовать значения в числа. Очевидно, что true превратится в 1:

Number(true) // -> 1

Унарный оператор сложения тоже пытается превратить значение в число. Он может преобразовывать строковые представления целых чисел и чисел с плавающей запятой, а также нестроковые значения true, false и null. Если он не может разобрать конкретное значение, то вычисляет его как NaN. Так что мы можем ещё проще преобразовать true в 1:

+true // -> 1

NaN не является числом

Типом NaN является 'number':

typeof NaN            // -> 'number'

[] и null являются объектами

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

// однако
null instanceof Object // false

Точность вычисления 0.1 + 0.2

Широко известная шутка. Результат сложения 0.1 и 0.2 получается невероятно точным:

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

Константы 0.2 и 0.3 будут аппроксимированы до своих истинных значений. Получается, что ближайшее к 0.2 значение double оказывается больше рационального числа 0.2, но ближайшее к 0.3 значение double меньше рационального числа 0.3. Сумма 0.1 и 0.2 оказывается больше рационального числа 0.3, и следовательно не соответствует константам в коде.

Проблема так хорошо известна, что даже есть сайт 0.30000000000000004.com. Такое происходит в любом языке, использующем вычисления с плавающей запятой, а не только в JavaScript.

Сравнение трёх чисел

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

Почему это работает таким образом? Проблема кроется в первой части выражения. Работает оно так:

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

Мы можем исправить это с помощью оператора «больше или равно»:

3 > 2 >= 1 // true

Забавная математика

Зачастую результаты математических операций в JavaScript могут быть весьма неожиданными. Примеры:

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

Что происходит в первых четырёх примерах? Вот маленькая таблица, которая поможет понять операцию сложения в JavaScript:

Number + Number -> сложение
Boolean + Number -> сложение
Boolean + Boolean -> сложение
Number + String -> конкатенация
String + Boolean -> конкатенация
String + String -> конкатенация

А что насчёт остальных примеров? Применительно к [] и {}, до операции сложения неявно вызываются методы ToPrimitive и ToString.

Строки не являются экземплярами String

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

Деструктурирование со значениями по умолчанию

Рассмотрим пример:

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

Каким будет значение y? Ответ: 1.

let x, { x: y = 1 } = { x }; y;
//  ↑       ↑           ↑    ↑
//  1       3           2    4
  1. Мы объявили x без значения, поэтому оно undefined.
  2. Затем упаковали свойство x свойство объекта x.
  3. Затем с помощью деструктурирования извлекли значение x, и хотим присвоить его y. Если значение не определено, то мы используем 1 в качестве значения по умолчанию.
  4. Возвращаем значение y.

Хитрые стрелочные функции

Рассмотрим пример:

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

Так, отлично, а что насчёт этого:

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

Вы можете ожидать получить {} вместо undefined. Причина в том, что фигурные скобки являются частью синтаксиса стрелочных функций, так что f будет возвращено неопределённым.

Еще.