company_banner

Что за черт, Javascript

https://github.com/denysdovhan/wtfjs/blob/master/README.md
  • Перевод


Этот пост — список забавных и хитрых примеров на JavaScript. Это отличный язык. У него простой синтаксис, большая экосистема и, что гораздо важнее, огромное сообщество.


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


Содержание



Мотивация


Ради удовольствия
“Just for Fun: The Story of an Accidental Revolutionary”, Линус Торвальдс

Главная цель появления этого списка — собрать несколько безумных примеров и по возможности объяснить, как они работают. Просто потому, что это приятно, узнавать что-то, о чём мы раньше не знали.


Если вы новичок, то можете использовать эти заметки, чтобы глубже изучить JavaScript. Надеюсь, эта статья мотивирует вас потратить больше времени на чтение спецификаций. Если вы профессиональный разработчик, то можете рассматривать эти примеры как хороший референс для всех выкрутасов и неожиданностей нашего любимого JavaScript. В любом случае, просто почитайте. Вероятно, вы найдёте для себя что-то новое.


Нотация


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


1 + 1 // -> 2

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


console.log('hello, world!') // -> hello, world!

// всего лишь комментарий для объяснений. Например:


// Присвоение функции константе foo
const foo = function () {}

Примеры


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


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


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

Объяснение:



true это false


!!'false' ==  !!'true'  // -> true
!!'false' === !!'true' // -> true

Объяснение:


Рассмотрим пошагово:


true == 'true'    // -> true
false == 'false'  // -> false

// 'false' не является пустой строкой, так что это «истинноватое» (truthy) значение


!!'false' // -> true
!!'true'  // -> true


baNaNa


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

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


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

Объяснение:


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



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


NaN === NaN // -> false

Объяснение:


Спецификация строго определяет логику такого поведения:


  1. Если Type(x) отличается от Type(y), то возвращается false.
  2. Если Type(x) является числом, тогда
    1. если x является NaN, то возвращается false.
    2. если y является NaN, то возвращается false.
    3. … … …


Согласно определению NaN в IEEE:


Возможно четыре взаимоисключающих отношения: меньше чем, эквивалентно, больше чем, неупорядоченно (unordered). Последний вариант бывает, когда как минимум один операнд является NaN. Каждый NaN будет неупорядоченно сравниваться со всеми, включая самого себя.
“What is the rationale for all comparisons returning false for IEEE754 NaN values?”

Это 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

Объяснение:


Вот ссылки на соответствующие разделы спецификации ECMA-262:



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


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


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

В то же время другие «ложноватые» значения, вроде 0 или '', эквивалентны false.


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

Объяснение:


Объяснение то же, что и в предыдущем пример:



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


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


Number.MIN_VALUE > 0 // -> true

Объяснение:


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


А вообще наименьшее возможное значение — Number.NEGATIVE_INFINITY, не являющееся числовым в строгом смысле слова.



Функция не является функцией


Этот баг присутствует в движке V8 v5.5 и ниже (Node.js <=7). Все вы знаете о раздражающей ошибке «undefined is not a function», а что насчёт этого?


// Объявляем класс, расширяющий null
class Foo extends null {}
// -> [Function: Foo]

new Foo instanceof null
// -> TypeError: функция не является функцией
// ->     at … … …

Объяснение:


Это не является частью спецификации. Это просто баг, который уже исправили, так что в будущем проблем быть не должно.


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


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


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

Висящие запятые в массивах


Вы создали массив из четырёх пустых элементов. Но из-за висящих запятых получили массив из трёх элементов:


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

Объяснение:


Висящие запятые (иногда их называют «замыкающие запятые») могут быть полезны при добавлении в JavaScript-код новых элементов, параметров или свойств. Если вы хотите добавить новое свойство, то просто добавляете новую строку без изменения предыдущей последней строки, если в этой строке уже есть висящая запятая. Это упрощает управление версиями, а редактирование кода может быть менее проблематичным.


Висящие запятые


Эквивалентность массивов — это чудовище


Эквивалентность массивов в JS является настоящим чудовищем, судите сами:


[] == ''   // -> 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

Объяснение:


Будьте очень осторожны! Это сложные примеры, описанные в разделе спецификации 7.2.13 Сравнение на основании абстрактной эквивалентности.


undefined и Number


Если не передать аргументы в конструктор Number, то получим 0. Когда реальных аргументов нет, формальным аргументам присваивается значение undefined, так что Number без аргументов получает undefined в качестве значений параметров. Но если передать undefined, получим NaN.


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

Объяснение:


Согласно спецификации:


  1. Если при вызове функции не были переданы параметры, то пусть n будет +0.
  2. Также пусть n будет ? ToNumber (value).
  3. В случае с undefined, ToNumber(undefined) должно возвращать NaN.

Соответствующие разделы:



Плохой parseInt


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


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

Объяснение:


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


Разбор Infinity на числа:


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

Также будьте осторожны с разбором null:


parseInt(null, 24) // -> 23

Объяснение:


null преобразуется в строку "null", а потом пытается его конвертировать. Но для оснований от 0 до 23 не существует чисел, в которые машина может конвертировать это слово, поэтому возвращается NaN. На позиции 24 находится "n", 14-я буква латинского алфавита, она складывается с числом. На позиции 31 находится "u", 21-я буква, она тоже складывается, после чего можно декодировать всю строку. На позиции 37 уже не получится сгенерировать валидное число, поэтому возвращается NaN.


“parseInt(null, 24) === 23… погодите, что?”


Не забывайте и о восьмеричные основания (octal):


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

Объяснение:


Если входная строка начинается с "0", то основание равно 8 (восьмеричное) или 10 (десятичное). Конкретный выбор зависит от реализации. ECMAScript 5 определяет использование 10, но оно поддерживается ещё не всеми браузерами. Поэтому всегда указывайте основание при использовании parseInt.


parseInt всегда преобразуйте входные данные в строковое значение:


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

Вычисления с помощью 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

Когда выполняется сложение или умножение, вызывается метод ToNumber. Согласно спецификации, этот метод возвращает:


Если argument равен true, то возвращается 1. Если argument равен false, то возвращается +0.

Поэтому мы можем складывать булевы значения как обычные числа и получать корректные результаты.


Соответствующие разделы:



HTML-комментарии валидны в JavaScript


Вы будете удивлены, но HTML-комментарий <!-- является валидным комментарием и в JavaScript.


// валидный комментарий
<!-- тоже валидный комментарий

Объяснение:


Впечатлены? Комментарии а-ля HTML должны позволять мягко деградировать (degrade gracefully) тем браузерам, которые не понимают тега <script>. Такие браузеры, вроде Netscape 1.x, уже редко используются. Так что больше нет особого смысла помещать HTML-комментарии внутрь тегов скриптов.


Поскольку Node.js сделан на движке V8, HTML-комментарии поддерживаются и runtime-средой Node.js. Более того, они являются частью спецификации:



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


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


typeof NaN            // -> 'number'

Объяснение:


Объяснение работы операторов typeof и instanceof:



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


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

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

Объяснение:


Поведение оператора typeof определено здесь:



Согласно спецификации, оператор typeof возвращает строковое значение в соответствии с Таблица 35: Результаты оператора typeof. Для null, обычных, стандартных экзотических и нестандартных экзотических объектов, которые не реализуют [[Call]], возвращается строковое значение "object".


Однако с помощью метода toString вы можете проверить тип объекта.


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

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

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

Магическое увеличение чисел


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

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

Объяснение:


Причина в стандарте IEEE 754-2008 для двоичных вычислений с плавающей запятой. При таких величинах выполняется округление до ближайшего чётного числа:



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


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


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

Объяснение:


Причина описана в вопросе на StackOverflow ”Сломано вычисление с плавающей запятой?”:


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

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


Патчим числа


Вы можете добавить свои методы к объектам-обёрткам вроде Number или String.


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

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

Объяснение:


Очевидно, что вы можете расширять объект Number, как и любой другой объект в JavaScript. Но делать это не рекомендуется, если поведение определяемого метода не является частью спецификации. Вот список свойств Number:



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


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. Подробнее о вычислениях:



Сложение регулярных выражений


Вы знали, что можно складывать такие числа?
// Патч для метода toString
RegExp.prototype.toString = function() {
  return this.source
}

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

Объяснение:



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


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

Объяснение:


Конструктор String возвращает строковое значение:


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

Попробуем с new:


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

Объект? Что это?


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

Подробнее о конструкторе String:



Вызов функций с «обратными кавычками» (backticks)


Давайте объявим функцию, которая журналирует все параметры в консоли:


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

Несомненно, вы можете вызвать эту функцию вот так:


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

Но вы знали, что можете вызвать вообще любую функцию с «обратными кавычками»?


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

Объяснение:


Это вовсе не магия, если вы знакомы с маркированными шаблонными литералами (Tagged template literals). В приведённом примере функция f является тегом для шаблонного литерала. Теги перед шаблонными литералами позволяют разбирать эти литералы с помощью функции. Первый аргумент функции-тега содержит массив строковых значений. Остальные аргументы связаны с выражениями. Пример:


function template(strings, ...keys) {
  // что-то делает со строками и ключами…
}

Это магия, относящаяся к известной библиотеке styled-components, популярной в React-сообществе.



Call call call


Найдено @cramforce


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

Объяснение:


Внимание, это может сломать вам мозг! Попробуйте воспроизвести этот код в голове: мы применяем метод call с помощью метода apply. Подробнее:



Свойство constructor


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

Объяснение:


Разберём пример пошагово:


// Объявляем новую константу, которая является строковым значением 'constructor'
const c = 'constructor'

// c — это строковое значение
c // -> 'constructor'

// получаем конструктор строки
c[c] // -> [Function: String]

// Получаем конструктор конструктора
c[c][c] // -> [Function: Function]

// Вызываем конструктор Function и передаём тело новой функции в качестве аргумента
c[c][c]('console.log("WTF?")') // -> [Function: anonymous]

// А затем вызываем эту анонимную функцию
// Получаем в консоли строку 'WTF?'
c[c][c]('console.log("WTF?")')() // -> WTF?

Object.prototype.constructor возвращает ссылку на функцию-конструктор Object, которая создала объект-экземпляр. В случае со строковыми это String, в случае с числами это Number, и так далее.



Объект как ключ свойства объекта


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

Объяснение:


Почему это работает таким образом? Здесь мы используем вычисленное имя свойства (Computed property name). При передаче объекта между этими скобками, объект неявно преобразуется в строковое значение, так что мы получаем ключ свойства '[object Object]' и значение {}.


Можно сделать «скобочный ад»:


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

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

Подробнее об объектных литералах:



Доступ к прототипам с помощью proto


Как мы знаем, примитивы не имеют прототипов. Однако если мы попытаемся получить для них примитивов значение __proto__, то получим:


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

Объяснение:


Когда что-то не имеет прототипа, оно обёртывается в объект-обёртку с помощью метода ToObject. Пошагово:


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

Подробнее о __proto__:



${{Object}}


Какой будет результат этого выражения?


`${{Object}}`

Ответ:


// -> '[object Object]'

Объяснение:


Мы определили объект со свойством Object с помощью нотации сокращённых свойств (Shorthand property notation):


{ Object: Object }

Затем мы передали этот объект в шаблонный литерал, так что его вызывает метод toString. Поэтому мы получаем строку '[object Object]'.



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


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


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.


Точки и разбиение (spreading)


Можно привести интересный пример с распределением массивов.


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

Объяснение:


Почему 3? Когда мы используем оператор разбиения TODO, то вызывается метод @@iterator, а для получения значений, которые нужно итерировать, используется возвращённый итератор. Итератор для строк по умолчанию разбивает строку на символы, которые затем упаковываются в массив. Затем мы разбиваем этот массив и снова упаковываем в массив.


Строка '...' состоит из трёх символов ., так что длина получившегося массива равна 3.


Пошагово:


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

Очевидно, что мы можем разбить и обернуть элементы массива сколько угодно раз:


[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]
// и так далее…

Метки


Не так много программистов знают о метках в JavaScript. Они весьма интересны:


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

// -> first
// -> undefined

Объяснение:


Маркированные выражения используются с выражениями break или continue. С помощью меток вы можете пометить цикл, а затем использовать выражения break или continue чтобы показать, должна ли программа прервать цикл или продолжить его выполнение.


В приведённом примере мы помечаем как foo. Затем выполняем console.log('first'); и происходит прерывание выполнения.


Подробнее о метках в JavaScript:



Вложенные метки


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

Объяснение:


Как в предыдущем примере, читайте:



Коварный try..catch


Что возвращает это выражение? 2 или 3?


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

Ответ 3. Удивлены?


Объяснение:



Это множественное наследование?


Взгляните на этот пример:


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

Это множественное наследование? Нет.


Объяснение:


Самое интересное связано со значением выражения extends ((String, Array)). Оператор группирования всегда возвращает последний аргумент, так что (String, Array) по сути просто Array. То есть мы создали класс, который расширяет Array.



Генератор, получающий данные из самого себя


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


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

Как видите, возвращаемое значение является объектом, у которого value эквивалентно f. В этом случае мы можем сделать так:


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

// и опять
(function* f() { yield f })().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }

// и опять
(function* f() { yield f })().next().value().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }

// и так далее
// …

Объяснение:


Чтобы понять, почему это работает таким образом, читайте спецификацию:



Класс класса


Рассмотрим этот сбивающий с толку синтаксис:


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

Похоже, мы объявляем класс внутри класса. Должна быть ошибка, однако мы получаем строку 'object'.


Объяснение:


Со времён ECMAScript 5 ключевые слова могут использоваться в качестве наименований свойств. Так что это сродни простому примеру с объектом:


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

А ES6 стандартизировал сокращённые определения методов. К тому же классы могут быть анонимными. Так что если отбросить часть: function, то получим:


class {
  class() {}
}

Результатом класса по умолчанию всегда будет простой объект. И его typeof должен возвращать 'object'.


Почитать подробнее:



Объекты, для которых не действует неявное приведение типа (Non-coercible objects)


С помощью Well-Known Symbols можно избавиться от неявного приведения типа:


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
}

Это можно рассматривать так:


// объекты
const foo = nonCoercible({foo: 'foo'})

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

// строковые значения
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

// числа
const baz = nonCoercible(1)

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

Объяснение:



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


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


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

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


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

Объяснение:


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


Хитрое возвращение


Пример:


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

Объяснение:


return и возвращаемое выражение должны находиться в одной строке:


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

Обращение к свойствам объектов с помощью массивов


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

obj[array] // -> 1

Что насчёт псевдомногомерных массивов?


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

Объяснение:


Оператор [] преобразует переданное выражение toString. Преобразование одноэлементного массива в строку — это преобразование элемента в строку:


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

Другие источники


  • wtfjs.com — коллекция особенных нарушений, несоответствий и просто крайне неочевидных моментов.
  • Wat — прекрасное выступление Гэри Бернхардта на CodeMash 2012.
  • What the… JavaScript? — попытка Кайла Симпсона «убрать безумие» из JavaScript. Он хочет помочь вам писать более чистый, элегантный, читабельный код, а также вдохновить людей вносить свой вклад в open source-движение.
Метки:
Mail.Ru Group 826,14
Строим Интернет
Поделиться публикацией
Комментарии 153
  • +47
    let x, { x: y = 1 } = { x }; y;
    Это прекрасная задача для собеседований. Каким будет значение y

    Я к вам работать не пойду с такими собеседованиями.

    • +13
      Поздравляем, вы приняты!
    • +7
      return и возвращаемое выражение должны находится в одной строке

      Разве это объяснение? Это скорее то, как избежать такого поведения.
      А объяснение в автоматической установке точки с запятой
      • +20

        Sometimes when I'm writing Javascript I want to throw up my hands and say "this is bullshit!" but I can never remember what "this" refers to © Ben Halpern

        • +5
          Я не программирую на JS, но многие пункты, особенно NaN, минимальное значение больше нуля, сравнение трёх чисел — это очевидные и логичные пункты и более того справедливые для многих языков, а не только JS.

          А относительно остальных пунктов хочется заметить, что если язык спроектирован так, чтобы давать свободу выражения, и не писать «простыню» однозначно-очевидного кода для простых вещей, то на граничных случаях будут такие как будто «смешные» случаи.

          Пост в целом хороший, потому что подборка граничных случаев большая, но это совсем не «что за чёрт, Javascript» из заголовка. Такое впечатление может быть только на человека, далёкого от программирования и высокоуровневых языков.
          • 0

            Это перевод wtfjs, отсюда и заголовок

            • –2
              А в каких «многих языках» MinValue — это не «Минимально возможное значение», а «положительное, наиболее близкое к нулю но не нуль»?
              И как в этих «многих языках», получить наименьшее возможное число? Именно число, не Infinity, которое как-бы не число.
              • +1

                Си/C++: FLT_MIN, DBL_MIN
                C++: std::numeric_limits::min(), std::numeric_limits::min()
                Java: Float.MIN_VALUE, Double.MIN_VALUE

                Именно наименьшее возможное число во всех этих случаях можно получить как противоположное к наибольшему.

                • 0
                  C++:
                  std::numeric_limits<double>::min(): 2.22507e-308
                  std::numeric_limits<int>::min(): -2147483648


                  НО
                  std::numeric_limits<double>::lowest(): -1.79769e+308
                  std::numeric_limits<int>::lowest(): -2147483648
                  

                  ну и выше про C, Java написал mayorovp
                  • 0

                    C#: Double.MinValue

                    • 0

                      PHP: PHP_FLOAT_MIN

                  • +19

                    Мораль сей басни: по возможности не используйте конструкции с неочевидным поведением в своем коде.

                    • 0
                      Вот 3 функции:
                      sum(a,d) {
                          return a + b;
                      }
                      sumNum(a,d) {
                          return +a + +b;
                      }
                      concat(a,d) {
                         return '' + a + b;
                      }
                      

                      Поведение каких очевидно, а каких нет и почему?
                    • +2
                      Это полный список факапов, которые можно сотворить с js или есть еще? Но даже этот впечатляет, некоторые вещи не знал, спасибо.
                      • –5
                        Ууууу, думаете это полный? Далеко не полный. Например
                        var x =5;
                        (function(){
                          console.log(x);
                          var x = 10;
                          console.log(x);
                        })();

                        Область видимости переменных (var) — функция, и есть внутренний объект [[Scope]], который хранит всё объявленное, если есть объявление внутри функции, то он сразу создает под неё элемент и тем самым сразу перетирает, что видно в глобальной области видимости.
                        • 0

                          Перетирает? Вы что-то путаете.

                          • –1
                            Наверно он не правильно выразился, но есть такая коварная штука как Hoisting и вместо кода
                            var x =5;
                            (function(){
                              console.log(x);
                              var x = 10;
                              console.log(x);
                            })();
                            

                            на самом деле исполняется
                            var x =5;
                            (function(){
                              var x;
                              console.log(x);
                              x = 10;
                              console.log(x);
                            })();
                            

                            поэтому первый console.log напечатает undefined, а не 5, как это было бы в большинстве других языков. Но об этой проблеме могут не догадываться те, кто никогда не переопределяет внешние переменные в локальном скопе.
                            • +1

                              Ну не с JavaScript такое началось. Скажем, в BASIC'ах "неисполняемые" операторы выполняются до первого исполняемого.
                              И, честно говоря, не вижу в этом большой проблемы. Хотя, конечно, как в Lua было бы удобнее.

                            • 0
                              Хорошо, вместо перетирает — читайте перекрывает.
                              • –1
                                Нет, там надо читать так, что функция «всплывает», поэтому 5 к 'x' присваивается уже после того как отработает функция.
                        • +1
                          JS… я сегодня в продакшене вот такое увидел на VB.NET и это инженерный софт…
                          Dim textString As String
                          If(If((-If(((Me.ViewMode = LocalDimension.ViewModality.LengthOnly) > False), 1S, 0S)), 1S, 0S) Or 5S) <> 0S Then
                             ... some code...
                          Else
                             textString = ""
                          End If
                          • 0

                            О! Прямо детство вспомнил. Я, правда, тогда на VB6.0 развлекался. Оптимизация с помощью побитовых операций — наше всё )))

                            • 0
                              Напоминает
                              var x = 1 > 2;
                              if (x.toString().length == 5){
                              ...
                              }
                              • 0
                                В том же MSDN дофига примеров типа if (boolVar == true)
                                • –1

                                  Мелкомягкие в вопросах JavaScript — не авторитет.
                                  А лет 7 назад, я бы сказал, что у них нет JavaScript.

                              • 0
                                VB отличный язык (и не надо кидать помидорами — помимо VB пишу и на других, включая JS).
                                Если криворукий программист написал такой код на VB, он такой же кривой код напишет и на других языках.
                                Впрочем, затрагивая тему JS: приведённый код хоть и запутан — но выполняет то, что написано. В JS же код зачастую выполняет не то, что написано.
                              • +17
                                Ещё не надоело это мусолить?
                                • 0

                                  Спасибо тебе, Господи, что я питонист!

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

                                    Должен сопротивляться.... Черт, все, теперь я ломаю голову как это сделать в своей функции. Спасибо вам.


                                    Решение довольно простое
                                    function foo(num) {
                                       if (arguments.length === 0) return 0;
                                       if (num === undefined) return NaN;
                                       return num;
                                    }

                                    Вообще тут обнаружил еще крутую штуку — так сказать, кривой, как раз в стиле JS, метод переопределения операторов. Symbol.toPrimitive Особенно мне в нем нравится аргумент hint.

                                    • +1

                                      В JS нет переопределения операторов. Symbol.toPrimitive — придуман совсем для другого. А кривизна нецелевого использования… как бы самоочевидна.

                                      • 0
                                        не совсем

                                        после проверки arguments.length нужно возвращать +num, иначе поведение другое ;)


                                        Вообще тут обнаружил еще крутую штуку — так сказать, кривой, как раз в стиле JS, метод переопределения операторов.

                                        Но ведь это не перегрузка операторов, это неявное приведение типов. Что здесь "кривого"?


                                        Особенно мне в нем нравится аргумент hint.

                                        Если это ирония, то я её не понял. Вы ведь знаете что такое динамическая типизация?

                                        • 0

                                          Вы бы, коль уж берёте смелость на себя критиковать язык, хоть не признавались в незнании в нём базовых вещей :)


                                          Это не переопределение операторов, это обычное приведение типов. Попробуйте создать объект с функциями toString и valueOf (возвращающимися соответственно строку и число) и скастить его в строку или число.

                                        • 0

                                          Наверное, не совсем WTF, но меня такое недавно спрашивали


                                          Что произойдёт, если выполнить (к примеру, сделать console.log) этот код?
                                          [0, 1, 2][0, 1]

                                          • +1

                                            По крайней мере после прочтения этой статьи понятно, что во вторых [] оператор ,. Но лично я бы спросил "зачем так писать?".

                                            • +1
                                              Этот вопрос возникает у любого нормального человека. К сожалению, этого вопроса не возникает у хулителей языка и многих «ведущих разработчиков JS». Так и живем.
                                            • 0
                                              В js есть пара очень малоизвестных фич.
                                              Таких, как, например, оператор "," — позволяет писать несколько команд в строку, возвращает последний.
                                              var x = (1, 2, 3); // x = 3
                                              


                                              А ещё можно изображать питон:
                                              if (something)
                                                  firstFunc(),
                                                  secondFunc();
                                              


                                              К другим не очень известным фичам относятся, например, блоки или метки циклов (чтобы выходить чрез break во внутреннем цикле из внешнего цикла), кому интересно.
                                              • 0
                                                Не вижу смысла в использовании оператора «запятая» в JS, она там просто не нужна.
                                                Реальная польза от её наличия есть только в двух случаях: макросы/условная компиляция (например, макрос assert в C) и в variadic templates в C++ (пример).
                                                • 0

                                                  Да, не нужна. Потому почти никто про неё и не знает.

                                                  • 0
                                                    Ну почему только это, делал свой сахар в библиотеке для работы с бд.
                                                    auto result = db < "SELECT f1, f2 FROM ... WHERE f3=:value:", bind::to<std::tupple<int, string>>(), bind::from("value", 5);

                                                    Что-то похожее на это. Запятая пригодилась.
                                                    • 0
                                                      Ненавижу подобную перегрузку операторов в C++. Я бы написал так:
                                                      auto result = db.Query("SELECT f1, f2 FROM ... WHERE f3=:value:", bind::to<std::tupple<int, string>>(), bind::from("value", 5));

                                                      Заодно и IDE проще с кодом работать — оператор она вам не подскажет, а вот вызов фунции с аргументами — всегда пожалуйста.
                                                      • 0

                                                        А я бы предпочел даже вот так:


                                                        auto result = db.Query<int, string>("SELECT f1, f2 FROM ... WHERE f3={0}", 5);
                                                    • 0
                                                      for(var len = arr.length, i=0; i<len; ++i);
                                                      • 0
                                                        Не усложняйте:
                                                        for (var i = 0; i < arr.length; ++i);

                                                        Выигрыша от сохранения размера массива вы все равно не получите.
                                                        • 0

                                                          В большинстве случаев всё равно удобнее:


                                                          for(const el of arr);

                                                          Редко когда нужны индексы.

                                                          • 0
                                                            Согласен, но в современных браузерах конструкция for… of почему-то в разы медленнее простого for.
                                                            • 0

                                                              forEach() и map() тоже медленнее, но все ими пользуются же. Не занимайтесь преждевременными микрооптимизациями. Гораздо хуже то, что for-of плохо транспилируется, возникает ошибка в IE, что не может найти свойство Symbol.Iterator у DOM-элементов.

                                                              • 0

                                                                В V8 6.1, кстати, foreach ускорили.

                                                        • +1

                                                          В данном случае это обычное объявление двух переменных, которое к оператору "запятая" отношения не имеет.

                                                          • –1

                                                            Однако, это всё же он. В выражении, состоящем из двух операций присвоения.

                                                  • 0
                                                    Собачий язык! (простите, вырвалось). Впрочем, на практике это всё не мешает программировать, потому что никто не заставлять играть с причудами слабой типизации.

                                                    Единственный напрягающий момент — автодополнение строк точками с запятой, из-за которого приходится придерживаться не очень приятного мне стиля расстановки скобок.
                                                    • +1
                                                      Единственный напрягающий момент — автодополнение строк точками с запятой, из-за которого приходится придерживаться не очень приятного мне стиля расстановки скобок.

                                                      Как вариант, можно вручную ставить точки с запятой, тогда не будет проблем со скобками :)
                                                      • +1

                                                        Подозреваю, проблема в том, что JS ставит их там где не надо.

                                                        • –1

                                                          Как раз наоборот, больше неожиданностей из-за того, что они не ставятся там, где это могут ожидать. return, пожалуй, единственное исключение.


                                                          Например, выражение в скобках может быть воспринято как вызов функции из результата предыдущей скобки. Я сталкивался с таким багом, когда склеивались файлы с паттерном «модуль» (function(){/* код модуля */})() в один файл без точки с запятой. Поэтому иногда даже ставят точки с запятой в начало строки.


                                                          Аналогично может быть со строкой вида [1, 2, 3].forEach(/* код цикла */) — квадратные скобки имеют ещё функцию обращения к свойству. (См. про оператор запятую выше.)

                                                          • +1
                                                            И именно поэтому в нормальных языках опускание точки с запятой является синтаксической ошибкой.
                                                            • 0

                                                              И всё-таки это фича, несмотря даже на то, что новички могут не разобраться. Иначе нельзя было бы писать в стиле:


                                                              fetch(resource)
                                                                  .then(response => {/* подготовка данных */})
                                                                  .then(data => {/* работа с данными */})
                                                                  .catch(error => {/* обработка ошибок */});
                                                              • +2
                                                                Почему фича? Что мешало сделать как в том же C да и остальных языках, где перевод строки является пробельным символом и ничем больше?
                                                                • –2

                                                                  Потому что есть statements, а есть expressions. Для функций, например, это принципиально разные вещи. В первом случае будет декларация с хоистингом, во втором это просто объект (функции — объект первого класса), который, например, может передан как параметр другой функции. Даже фигурные скобки имеют разный смысл в обоих случаях.
                                                                  Некоторые вещи не могут быть выражениями (expressions), например, циклы for/while или инструкции return, throw-catch. Собственно, перед ними будет автоматическая вставка точки с запятой.

                                                    • 0
                                                      Не хватает объяснения вот этому:
                                                       {} + []; // -> 0
                                                      ({} + []); // -> "[object Object]"
                                                      
                                                      • +3

                                                        Объяснение простое, где-то я его тоже видел. Дело в том, что эти строки имеют разный смысл.


                                                        Во второй строке это просто выражение «объект + массив» внутри скобок, которые в процессе сложения приводятся к строкам "[object Object]" и "".


                                                        В первом же случае фигурные скобки — это не объект, как можно было бы подумать, а пустой блок, в котором нет операторов; он пропускается, и значение строки сводится к +[].

                                                      • +2
                                                        Это отличный язык. У него простой синтаксис, большая экосистема

                                                        Экосистема — это не язык. А синтаксис по большей части спизж… позаимствован.
                                                        А вот семантика, то, что там своё, — ужасная.
                                                        А язык отличный. Как так получается?
                                                        • –1

                                                          Вот что бывает когда лиспера просят запилить яву (тоже не подарок). Весь ад из лиспа, плюс куча ада который лиспер не понимал потому что он лиспер. Я не говорю что ява лучший язык и блабла, просто отвечаю на вопрос как сам понимаю. На самом деле разработка языка — это очень кропотливая работа, нужно проработать тысячи вариантов взаимодействия разных языковых конструкций, проверить как сделано в других языках, взвесить все плюсы и минусы каждой реализации и тп. За десять дней ничего путного не сделать.

                                                          • 0
                                                            >> нужно проработать тысячи вариантов взаимодействия разных языковых конструкций, проверить как сделано в других языках, взвесить все плюсы и минусы каждой реализации

                                                            Сколько языков вы разработали?
                                                            • +1
                                                              Это типа пока сам не разработал — не знаешь? Знаю. Пилил кое-что в качестве хобби.
                                                              • 0
                                                                Если интересно — можете почитать Страуструпа — как С++ разрабатывался, довольно подробно. Не тяп-ляп.
                                                            • +1

                                                              Я вот видел два ада: один ад — это код на JavaScript, написанный Явером, второй — код на Java, написанный ЯваСкриптером.
                                                              Отсюда мораль: либо читай спеку, либо не жалуйся на язык.

                                                              • +2

                                                                Неоднозначная спека языка, в котором легко накосячить — проблема языка, а не "косячника".

                                                                • 0
                                                                  Поддерживаю! Почему-то многие комментаторы отвечают в духе «90% этого вы просто не будете использовать, и всё будет хорошо» — ключевая ошибка здесь в слове «вы». Во-первых, напоминаю про популярное требование вакансий: «Умение разбираться в чужом коде». Во-вторых, вы в GITе всегда в одиночку всё пишете, ни за кем не дописываете, ни за вами никто не не дописывает?
                                                                  • +1

                                                                    Я работаю в компании, где все знают язык на должном уровне и придерживаются установленного стандарта кода.
                                                                    Никаких трудностей не возникает даже с выражениями типа ~a.indexOf(b) или a&1||2.
                                                                    Спека — однозначная. Просто читать надо уметь.

                                                                    • 0
                                                                      Выражения вырваны из контекста. Первое — вполне понятно, когда речь идёт о двоичном поиске, и при ненайденном элементе возвращается двоичное дополнение индекса, на которое элемент бы встал, если бы его нужно было вставить. Это именно API метода — спека. А вот второе — бессмысленная хрень, за которую нужно бить по пальцам.
                                                                      • –1

                                                                        Второе — возвращает 2, если число чётное, и 1 — если нечётное (0 — не ожидался). Вы хотите сказать, что это не очевидно? У нас в конторе очевидно всем. Уровень, господа!

                                                                        • 0
                                                                          Моя логика была такая: сначала выполняется (a & 1) — действительно, возвращается младший бит, если a — целом число. Затем выполняется логический оператор ||, который во всех нормальных языках конвертирует операнды к bool и возвращает bool, то есть результатом будет true во всех случаях.

                                                                          Но я забыл, что речь идёт о JavaScript, в котором всё перевёрнуто с ног на голову. Ну а так как мой суммарный опыт программирования на JS — не больше недели, мне простительно, что я не знал, что || возвращает не true/false, а один из операндов.

                                                                          Собственно, поэтому я и против таких конструкций. Конструкция вида (a % 2 == 1? 1: 2), либо упрощённая до (a & 2? 1: 2) будет понятна любом программисту, знающему C/C++/C#/Java/PHP. И поэтому, когда такому программисту понадобится прочитать код на JS (мне, бывает, нужно), у него не возникнет проблем.
                                                                          • 0
                                                                            когда такому программисту понадобится прочитать код на JS

                                                                            Он напишет в скайп своему коллеге, пишущему на JS. Или даже переведёт задачу на него, чтобы поправил по своей части.


                                                                            Я за то, чтобы знать язык, а не жаловаться, что в языке всё не так как хочется.
                                                                            Над лиспом тоже ныть будете, что всё не так? А PL SQL Вы изучали?

                                                                            • 0
                                                                              > Он напишет в скайп своему коллеге, пишущему на JS. Или даже переведёт задачу на него, чтобы поправил по своей части.

                                                                              А если надо поправить вот прям щас? Исправление мелочи — минута, а вот коллега потратит 15-20 минут (объяснение проблемы + время на возвращение коллеги обратно в поток).

                                                                              Ну или если человек уже не работает, а код поправить надо?

                                                                              > Я за то, чтобы знать язык, а не жаловаться, что в языке всё не так как хочется.

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

                                                                              > Над лиспом тоже ныть будете, что всё не так? А PL SQL Вы изучали?

                                                                              А причём тут это? Речь о том, что одинаковые конструкции разных языков программирования имеют разную семантику, и это плохо.
                                                                              • 0
                                                                                А если надо поправить вот прям щас? Исправление мелочи — минута, а вот коллега потратит 15-20 минут (объяснение проблемы + время на возвращение коллеги обратно в поток).

                                                                                После такого "прям щас" и появляется говнокод в проекте.


                                                                                Ну или если человек уже не работает, а код поправить надо?

                                                                                Прямо совсем ява-скприптеров в компании не осталось? Это что же за компания такая?


                                                                                И таким страдают, в основном, начинающие программисты, только изучившие язык и хотящие показать мастерство его владения.

                                                                                Ну и чёрт с ними. Если человек идиот — это надолго.


                                                                                А причём тут это?

                                                                                При том, что языки — разные.


                                                                                одинаковые конструкции разных языков программирования имеют разную семантику, и это плохо.

                                                                                Что в этом плохого? Спеку приходится читать? Ой какая трудность!

                                                                                • +1
                                                                                  > После такого «прям щас» и появляется говнокод в проекте.

                                                                                  Видимо, у нас разные понятия о говнокоде.

                                                                                  > Прямо совсем ява-скприптеров в компании не осталось? Это что же за компания такая?

                                                                                  Штатных вообще ноль. А проектом занимаются два человека + пара-тройка эпизодических. Если текущих знаний достаточно для написания работающего кода и удовлетворения заказчика, в чём проблема?

                                                                                  > При том, что языки — разные.

                                                                                  А я про языки с одинаковым синтаксисом, но разной семантикой, в проекте, где одновременно используется несколько языков.

                                                                                  > Что в этом плохого? Спеку приходится читать? Ой какая трудность!

                                                                                  Не трудность, но время занимает. И именно написание обфуцированного кода затрудняет его поддержку, потому что даже для знающих язык приходится напрягать извилины. Короче, код вы не только для себя пишите.
                                                                                  • –1
                                                                                    Штатных вообще ноль.

                                                                                    Согласитесь, это точно не проблема языка.


                                                                                    в проекте, где одновременно используется несколько языков

                                                                                    Работают несколько программистов. Логично?
                                                                                    Был у нас один двухстаночник — Java/JavaScript. Не знаю, какой он был явер, но его JS потом полгода рефакторили к стандарту. Потому что не ходи со своим уставом в чужой монастырь.


                                                                                    обфуцированного

                                                                                    Что что? В каком месте он обфуцированный?


                                                                                    Короче, код вы не только для себя пишите.

                                                                                    Ну да. Я код пишу для себя и своих коллег, которые знают язык не хуже чем я. Об этом я и писал с самого начала.

                                                                                    • –1

                                                                                      Напомню:


                                                                                      Я работаю в компании, где все знают язык на должном уровне и придерживаются установленного стандарта кода.

                                                                                      Работал в общей сложности с десятком ява-скриптеров, и среди них не было тех, кто не читает спеку. Любой из них понял бы вышеприведённые выражения.


                                                                                      Особенность работы оператора || — стандартно используется для присвоения дефолтных значений параметрам. Мне дико слышать, что человек, для которого конструкция вида
                                                                                      a = a || 0;
                                                                                      непривычна, считает, что может писать на JavaScript настоящий проект, а не поделку для личного пользования.

                                                                      • –2
                                                                        Во-первых однозначных спек нет, всегда есть UB. Чем их меньше — тем лучше, но тем не менее.
                                                                        Во-вторых если в JS сложение 3 и 5 внезапно давало бы -1/12, а во всех остальных случаях работало как обычно, но в спеке это было бы описано, то вы бы назвали это нормальным поведением? Если в спеке сказано, что при 3 фазе луны при заходе в юпитер сложение строк внезапно дает NaN, то это никак не умаляет общего идиотизма.

                                                                        Хороший язык делает интуитивно понятные вещи. А если работа с языком идет по принципу «написал-получил-хрень-почитал спеку-»ааааа, ну теперь-то понятно", то этот язык — хрень.

                                                                        Что касается того, что у вас команда бородочей, которые отлично разбираются в JS его как язык никак не исправляет. Уверен, есть специалисты должного уровня даже на брейнфаке. А что, спека всё описывает (причем тот редкий случай, когда даже нет UB!), твори да и только.
                                                                        • –1

                                                                          Вы до сих пор верите, что бывают интуитивно понятные вещи?


                                                                          По вашему кто не эни-кейщик — тот сразу бородач?

                                                                          • 0
                                                                            Я не верю — я ими пользуюсь. Помните метрики WTF/min? К языку точно так же относится.
                                                                            • 0

                                                                              "Интуитивно понятно" то, что привычно. Ни больше и ни меньше.
                                                                              Мне лично привычно, что оператор || возвращает первый из операндов, приводящийся к true, или последний операнд. В других языках часто не хватает оператора с таким поведением, а без него — как без рук.

                                                                              • 0
                                                                                Речь про язык в целом, а не про какой-то отдельно взятый кейс. Например пример с «или» приводящийся к булеву еще понять можно, ну просто такая вот перегрузка для типов, не являющихся булевыми, это нормально и возможно даже хорошо.

                                                                                Не интуитивным является например нетранзитивность сравнения. Если a == b и b == c, то a == c. Только для объектов типа NaN такое сравнение может не выполнятся, но об этом все знают, потому что у всех языков поведение тут одинаковое, и обоснованное на уровне IEEE.
                                                                                • 0
                                                                                  Если a == b и b == c, то a == c

                                                                                  Оператор == означает "равно или приводится друг к другу".
                                                                                  (Очень полезный оператор, если вместо числа может придти строка, содержащая число.)


                                                                                  Если a, b и c — имеют разные типы, то в парах [a,b], [b,c] и [a,c] — они приведутся к друг другу разным образом.
                                                                                  Если они одного типа, то с транзитивностью проблем не возникнет.


                                                                                  Всё стройно и логично.

                                                                                  • +2
                                                                                    Наличие в языке двух разных «равно» это уже провал, имхо.

                                                                                    В общем основная претензия в том, что операторы в JS явно занимаются не тем, чем должны. Оператор «проверить на равенство и выслать емейл если они не равны» не должен существовать по очевидным причинам. Оператор «приведи хоть как-нибудь и сравни» это из той же оперы.

                                                                                    Ну и да, в обычных языках, если a != b, то !!a != !!b

                                                                                    То что результат этого равенства может быть различным в зависимости от типа (информации о котором, конечно же, в языке нет) просто недопустимо. Говорить «это есть в спеке» все равно, что вспоминать, как у нас человек на 3 курсе на лабе по программировании написал такую костылину, которая перемножает матрицы, что работало только с нечетными размерами матриц — 3х3, 5х5… Так он просто в лабу написал «спеку»: «не поддерживается перемножение матриц с четным рангом». Это прокатило, но называть это нормальным решением лично у меня язык не поворачивается.

                                                                                    Язык может быть не похож на другие, хотя чем больше бекграунда с других языков примкенимо — тем в целом лучше. Но правила должны быть логичными и непротиворечивыми. Каждая сноска «а вот если тут такие специальные значения то оператор работает совсем не так» это минус языку. Это как с эсперанто — 12 правил, ни одного исключения. Интуитивно понятен и хорош. Естественные языки — сборник костылей, можно почитать известную пасту про «сапог на столе лежит, на полу стоит, а на ноге сидит» в комментах к другому посту. Это — плохо.
                                                                                    • 0
                                                                                      Наличие в языке двух разных «равно» это уже провал, имхо.

                                                                                      ИМХО, два разных "равно" — необходимы в языках с нестрогой типизацией.


                                                                                      в обычных языках

                                                                                      С нестрогой типизацией?


                                                                                      Каждая сноска «а вот если тут такие специальные значения то оператор работает совсем не так» это минус языку.

                                                                                      Назовите хоть один такой случай в JS. Пока что Вы не назвали ни одного оператора, который бы работал с какими-то специальными значениями иначе, чем во всех остальных случаях.

                                                                                      • 0
                                                                                        Назовите хоть один такой случай в JS. Пока что Вы не назвали ни одного оператора, который бы работал с какими-то специальными значениями иначе, чем во всех остальных случаях.

                                                                                        const areEqualWithInverse = x => x == !x;
                                                                                        undefined
                                                                                        areEqualWithInverse(true)
                                                                                        false
                                                                                        areEqualWithInverse([])
                                                                                        true

                                                                                        По сути нарушается закон исключенного третьего. Единственное значение, для которого подобно допустимо — это NaN.
                                                                                        • +1

                                                                                          Да, Вы правы. Это явный косяк языка. Я на него до сих пор не натыкался.


                                                                                          Кстати, почему для NaN допустимо быть равным собственному отрицанию?

                                                                                        • 0

                                                                                          Кстати, реально не понятно, какого дьявола пустой массив приводится к false, а не к true, как положено любому порядочному объекту.

                                                                                            • 0

                                                                                              Хм…
                                                                                              Если A=[], B = false;
                                                                                              A == B
                                                                                              ToPrimitive(A) == ToNumber(B)
                                                                                              "" == 0
                                                                                              ToNumber("") === 0
                                                                                              0 === 0


                                                                                              Вроде сходится, но возникает следующий вопрос:
                                                                                              С какой целью пустая строка приводится к 0, а не к NaN?

                                                                                              • 0
                                                                                                С какой целью пустая строка приводится к 0, а не к NaN?


                                                                                                Так прописано в стандарте. А вот почему так в стандарте прописано мне неизвестно :-)
                                                                                                • 0

                                                                                                  Кэп, Вы снова с нами? В стандарте я и сам это увидел. А вот в чём практический смысл такого поведения — от меня ускользает.

                                                                                      • 0
                                                                                        Ну и да, в обычных языках, если a != b, то !!a != !!b

                                                                                        Языки Си и С++: 1 != 2, но !!1 == !!2
                                                                                        Питон: 1 != 2, но (not (not 1)) == (not (not 2))


                                                                                        Какие еще обычные языки вам нужны?


                                                                                        И да, выдуманное вами правило "если a != b, то !!a != !!b" не имеет ничего общего с законом исключенного третьего.

                                                                                  • 0
                                                                                    > В других языках часто не хватает оператора с таким поведением, а без него — как без рук.

                                                                                    Стоит отметить, что такая конструкция возможна только в языках со слабой динамической типизацией, поэтому неудивительно, что её больше нигде нет.
                                                                          • 0
                                                                            Тем не менее, одно из требований — не только уметь читать чужой код, но и уметь писать понятный другим код, чтобы читающий как можно меньше времени тратил на его понимание.
                                                                    • +3
                                                                      Да никак не получается. Это просто отмазка такая. Ну как там у классиков: «Мы искренне уважаем благородных утконосов и ни в коем случае не хотели их оскорбить или унизить!..»

                                                                      Вот и здесь так: если убрать эту ритуальную фразу, то набигут тысячи js-еров и с криками затопчут, закопают и надпись «он не любил яваскрипт» написают.

                                                                      А так автор сначала, как те порасята из сказки, благоразумно строит каменный домик с большой красивой надписью «JavaScript is a great language!» и уже оттуда, хохоча сатанинским смехом, набирает полные лопаты wtfjs-ов и кидает ими через забор в проходящую мимо публику. На потребу своей чОрной душе.

                                                                      И всё, вместо тонн хейта всё что он получит — это беспомощное: «ну да, ведь на дне запертого шкафа с бумагами, который стоял в неработающем туалете, на двери которого висела табличка „Осторожно, леопард!“ — так вот там именно это поведение и описано, а что тут такого».

                                                                      P.S. Всегда так делаю.
                                                                    • 0
                                                                      Уточка выразила мои мысли.
                                                                      Статься хорошая, учитывая повсеместное использование этого языка в вебе.
                                                                      • –3
                                                                        Подобные автоматические преобразования отваживают меня от жаваскрипта. Тут или пользоваться обёртками-трансляторами вроде typescript, или не писать вовсе.
                                                                        Предпочитаю писать программы, а не разбираться в тонкостях синтаксиса, ибо слишком много потенциальных и действительных факапов, и не хотел бы быть нинздей языка, если это — исключительно суровая необходимость, чтобы не писать бешено медленную/постоянно ломающуюся фигню, чисто по незнанию россыпи мелких тонкостей.
                                                                        Лично мне кажется, что жаваскрипту пора завязывать с обратной совместимостью, ибо пора бы уже перелопачивать. В сторону того же typescript, хотя бы.

                                                                        В качестве решения проблемы обратной совместимости, на первое время, можно указывать браузеру версию js, вроде этого: #javascriptX.X.
                                                                        Ну, а если и правда окажется здорово — то и фреймворки будут переписаны под последнюю версию. Конечно, возможна проблема как с python2/3, но только на первое время. В зависимости от форса новых стандартов/их фич.
                                                                        Хм, мечты.

                                                                        Мимо-луашник.
                                                                        • +1

                                                                          В js лучше использовать === а не ==

                                                                          • 0
                                                                            Лучше выучить всё-таки язык, а не прибегать к культу карго.
                                                                            • +2
                                                                              строгая типизация защищает от ошибок, культ карго тут не причём.
                                                                              сам Дуглас Крокфорд говорит, что просто не юзайте ==
                                                                              • +1

                                                                                Сколько ошибок в одном комментарии!


                                                                                1. «===» — это ещё не строгая типизация, это лишь строгое сравнение. Как только у вас появился другой оператор, например «>=», всё от приведения типов не убежите.
                                                                                2. За свою десятилетнюю практику ещё ни разу не встречал ошибок из-за нестрогого сравнения. А вот из-за такого насаждения строгого встречал. (Например, коллега поменял «==» на «===», не проверив, и в результате появился баг, что перестала работать проверка вида 2 === '2', где значение приходило от бэкенда.)
                                                                                  Более того! Ещё никто, кого я спрашивал, не смог привести примеры когда встречались баги из-за нестрогого сравнения, в том числе на Хабре.

                                                                                Крокфорд, кроме всего прочего, обучает новичков, в том числе перешедших с других языков. Я вполне допускаю, что для них может быть сложно сразу изучить все тонкости языка, и они могут написать глупости вроде «[] == false». Но ни один сколь-либо опытный JS'ник так не напишет, так как с булевым сравнивают в 0,01% кода, а когда сравнивают, то всегда строго. И для этого даже не надо изучать правила приведения булевых значений, хотя достаточно прочитать их один раз чтобы запомнить.


                                                                                Так что джунам эта рекомендация позволительна, но для мидла и выше просто стыдно не знать простейшие правила приведения, которые составляют основы языка.

                                                                              • 0
                                                                                Именно потому, что я знаю прелести сравнения в js я за ===
                                                                          • +5
                                                                            image
                                                                            • +7
                                                                              забыли вот еще
                                                                              image
                                                                              • +1
                                                                                Меня по предыдущей картинке спросили результат floor(lava). Я завис.
                                                                            • –2
                                                                              // -> означает результат console.log или другие выходные данные

                                                                              Что за чертовщина тут написана?

                                                                              • +1
                                                                                NaN === NaN // -> false

                                                                                Мой опыт подсказывает, что почти во всех ЯП NaN («не число») не равно самому себе и для такой ситуации предусмотрены отдельные методы:
                                                                                Object.is(NaN, NaN); // -> true

                                                                                Аналогичная ситуация в ЯП обстоит с +0 и -0:
                                                                                +0 === -0 // -> true

                                                                                но:
                                                                                Object.is(+0, -0); // -> false

                                                                                • 0
                                                                                  Хочу добавить, что специально для проверки на NaN есть более короткая функция isNaN().
                                                                                  • 0

                                                                                    Поправка: Number.isNaN. Просто isNaN сначала к числу приводит, а потом уже проверяет...

                                                                              • +2

                                                                                Кто заметил странную подсветку синтаксиса в публикации?


                                                                                В коде статьи указан для первого блока кода — basic,
                                                                                для второго — scilab,
                                                                                третьего — javascript (наконец-то),
                                                                                четвертого — inform7 :(,
                                                                                пятого — erlang-repl
                                                                                и так далее.


                                                                                Автор, как так вышло?

                                                                                • 0
                                                                                  null преобразуется в строку «null», а потом пытается его конвертировать. Но для оснований от 0 до 23 не существует чисел, в которые машина может конвертировать это слово, поэтому возвращается NaN. На позиции 24 находится «n», 14-я буква латинского алфавита, она складывается с числом. На позиции 31 находится «u», 21-я буква, она тоже складывается, после чего можно декодировать всю строку. На позиции 37 уже не получится сгенерировать валидное число, поэтому возвращается NaN.


                                                                                  Можно подробнее про «складывается с числом», «после чего можно декодировать всю строку» и «позицию 37»? Не совсем понятно, что с чем складывается, почему после преобразования буквы «u» можно декодировать всю строку и откуда взялось 37.
                                                                                  .
                                                                                  Если не ошибаюсь, тот тут происходит преобразование null > «null», затем каждый символ проверяется на наличие в системе с основанием 23. Например, символ «n» там есть, равен 23 ((от 1 до 9) + (от «a» до «n»)).
                                                                                  Символ «u» из слова «null», в свою очередь, отсутствует в системе с основанием 24, так как равен 30 ((от 1 до 9) + (от «a» до «u»)), так что прекращаем разбор строки и возвращаем 23
                                                                                  • 0
                                                                                    Можно подробнее про «складывается с числом», «после чего можно декодировать всю строку» и «позицию 37»? Не совсем понятно, что с чем складывается, почему после преобразования буквы «u» можно декодировать всю строку и откуда взялось 37.

                                                                                    Предполагаю, что тут имелось ввиду, что если дальше продолжать увеличивать значение основания системы счисления, то при основании системе счисления 31+ все символы строки «null» будут являться цифрами системы счисления и parseInt(null, 31) вернет 714695. А 37 взсялось оттуда, что максимальное занчение основания системы счисления для parseInt является 36.

                                                                                    «складывается с числом»
                                                                                    ИМХО некоректный перевод
                                                                                    Оригинал:
                                                                                    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.
                                                                                  • 0
                                                                                    Свежо и нетривиально (не то же, что и ''==+'', которое тоже true):
                                                                                    console.log(' ' == +' ')  //true
                                                                                    j
                                                                                    • –1
                                                                                      Все ругают Js. Ненавидят Js. Изменяют Js. Путают с Java. Но все равно кодят на Js.
                                                                                    • +3
                                                                                      В копилку «неожиданностей» JS (leading > это приглашение ноды):
                                                                                      > null == 0
                                                                                      false
                                                                                      > null > 0
                                                                                      false
                                                                                      > null >= 0
                                                                                      true
                                                                                      • +1
                                                                                        Если это все из личной практики, то есть только один вопрос: сколько седых волос появилось у автора на голове после всего этого?
                                                                                        • +1
                                                                                          true == 'true' -> false
                                                                                          • 0

                                                                                            Чем яваскрипт похож на скалу: и там, и там половину языка лучше просто не использовать. Но в скале хотя бы не получится использовать ненужные фичи нечаянно.

                                                                                            • +1
                                                                                              90% тех примеров, что описаны вы врядли напишите в коде. А если напишите — вы сам себе буратино.
                                                                                              • –2

                                                                                                Почему же — почти все примеры про применение самых обыкновенных операций (+![]) к неожиданным типам.

                                                                                                • 0
                                                                                                  Я абсолютно не понимаю зачем требуется использовать такую конструкцию. За столько лет программирования я понял только то, что кто пишет сложно или вот такими видами — просто выкобенивается и его надо гнать ссаными тряпками из команды.
                                                                                                  Припоминая принцип KISS, я могу точно сказать что если вы распишете это в несколько условий/переменных/более длинных конструкций — вы не только не умрете от этого, но и позволите читать этот код другим людям, которые не считают, что конструкция (+![]) самая явная и сразу объясняет что хотел сказать автор этим опусом.
                                                                                                  • –2

                                                                                                    это был просто список проблемных операторов))) Вот пример:


                                                                                                    function f(a, b) {
                                                                                                      return a + b
                                                                                                    }

                                                                                                    Что делает эта функция? Какие типы данных может вернуть эта функция? Я бы сказал, что ответ непрост и не тривиален.

                                                                                                    • +1
                                                                                                      Если вы используете подобные не объяснимые функции, то это говорит о качестве кода. За свой опыт я научился никогда не доверять входящим переменным и проверять их тип или возможные значения (это кстати помогает в тестировании), что и вам советую.
                                                                                                      • 0

                                                                                                        На практике — у большинства функций (если только не пишешь библиотеку) есть конкретная область применения и входные параметры достаточно предсказуемы. Хотя, конечно, написать пару строк для проверки — нетрудно и полезно.

                                                                                                      • 0

                                                                                                        Эта функция принимает и возвращает те типы данных, которые разрешены в документации на нее.

                                                                                                        • 0
                                                                                                          404 Страница не найдена.
                                                                                                          Мужчина, что вы тут кодите?! Не кодите тут! Не видно, что ли, тут порефакторино!
                                                                                              • 0
                                                                                                1 < 2 < 3
                                                                                                true

                                                                                                паубивав бы за такие грязные хаки!


                                                                                                -2 < -1 < 0
                                                                                                false
                                                                                                • 0
                                                                                                  Я бы хотел заметить, что такого рода сравнение в принципе не верно и ведёт к заведомо ложному результату. Это даже не хак, а ошибка в коде.
                                                                                                  1 < 2 < 1.1
                                                                                                  true

                                                                                                  Дело в том, что второе "<" производит сравнение true < 1.1, приводит true к 1. Естественно оно меньше 1.1.
                                                                                                • 0
                                                                                                  Когда я только познакомился с Javascript (во времена Netscape) он был совсем не такой навороченый, а вполне понятный.
                                                                                                  Хотя натуральные языки тоже изменяются, заимствуют конструкции из других языков и перестают быть похожими на себя в классическом виде.
                                                                                                  • –2
                                                                                                    ...JavaScript. Это отличный язык.


                                                                                                    Можно не читать, расходитесь, тут не на что смотреть.
                                                                                                    • –1
                                                                                                      В копилку
                                                                                                      (4.1 * 10 * 60 / 10) == (4.1 * 60) // false
                                                                                                      • +3

                                                                                                        Это так почти в любом ЯП будет.

                                                                                                        • 0
                                                                                                          Согласен, неправильно выразился, позвольте поправиться…
                                                                                                          4.1 * 60 // 245.99999999999997