Как унизить джаваскриптера

Владислав Козуля
3 min readMar 20, 2017

--

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

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

Начнем с простого: какому стандарту подчиняется JavaScript? ECMAScript? Да, но можно точнее: ECMA-262. Если вы еще не успели с ним ознакомиться, советую это сделать — узнаете много нового.

Например, числа. После определенного порога числа теряют в точности. Порог легко запомнить: 2⁵³ – 1. Не 64 бита, но близко. Так где же потеряли еще 11 степеней? В экспоненте!
Вот тебе, бабушка, и double-precision floating point.
К слову, уровень точности можно проверить при помощи несложного трюка:

0.1 + 0.2 // 0.30000000000000004

Такое поведение характерно не только для JavaScript. Существует даже одноименный сайт, объясняющий суть проблемы.

Вставить столько же знаков слева не выйдет — точность в том числе ограничена по количеству (17, ноль и точка не считаются).

Но это еще не все. У побитовых операций предел еще ниже: 2³¹ – 1. Да, в джаваскрипте есть побитовые операторы. И они работают только с Int32.

9007199254740991 >> 1 // -19007199254740991 << 1 // -22147483647 >> 1 // 1073741823

Кроме одного, который переваривает Uint32. Даже тут исключение!

4294967295 >>> 1 // 2147483647

Идем дальше. Наверное, вы уже сталкивались с таким поведением:

1 + 2 // 31 + "2" // "12"1 — "2" // —11 + 2 + "3" // "33""1" + 2 — "3" // 91 — 2 + "3" // "-13"1 — 2 — "3" // -4

Объяснение очень простое:

  1. Операторы выполняются слева направо.
  2. Сложение со строкой всегда возвращает строку.
  3. Вычитание всегда возвращает число.

Бонус: сложение противоположных Infinity возвращает NaN. Что?

Следующий пример еще проще, если помнить о приоритетах.

1 * 2 + "3" // "23""1" + "2" * 3 // "16""1" + "—2" * 3 // "1—6""1" + 2 / 3 // "10.6666666666666666"1 + "2" / 3 // 1.6666666666666665"1" / "2" + "3" // "0.53"

А теперь самое интересное. Что происходит, когда вместо примитива оператор получает объект?

Number({}) // NaN[2] > [1] // truenew Date() - new Date() // 0

Джаваскрипт конвертирует объект в примитив при помощи встроенной функции ToPrimitive, которая последовательно вызывает valueOf и toString (или наоборот, если в результате ожидает строку), пока не получит искомое. Соответственно, в реальности происходит что-то такое:

var a = {};
a = a.valueOf(); // тот же объект
a = a.toString(); // "[object Object]"
Number(a) // NaN

Во втором случае логика чуть более сложная, потому что полученные строки не конвертируются дальше в числа, а сравниваются по лексикографическому порядку:

var arr1 = [2];
var arr2 = [1];
arr1 = arr1.valueOf();
arr2 = arr2.valueOf(); // те же объекты
arr1 = arr1.toString(); // "2"
arr2 = arr2.toString(); // "1"
arr1 > arr2 // true

Бонус: спецификация говорит, что сравнение с NaN должно всегда возвращать undefined, однако на деле выводит false. Почему так? Ответ находится в другой секции: undefined приводится к false.

С датами же совсем просто: valueOf даты возвращает unix timestamp. Правда, не в секундах, а в миллисекундах, поэтому будьте аккуратны, если сервер отдает время в правильном формате.

Впрочем, есть еще один способ приведения типов: унарные операторы (не путать с обычными операторами, которые мы разбирали выше).

Другой популярный вопрос — оптимизация загрузки страницы. Да, мы можем перетасовать расположение стилей и скриптов, а тяжелые части загрузить позже, но есть один способ, позволяющий показать страничку сразу после соединения: сжать ее до 14 килобайт. Google рекомендует использовать этот прием для загрузки с мобильных устройств.

В итоге, если взять какое-нибудь популярное видео, в котором докладчик жалуется, как противоречив и непонятен язык, то в 99.9% случаев поведение можно легко отследить до конкретной функциональности. Даже известную табличку с приведением типов.

Будьте здоровы и прекратите доставать других разработчиков!

--

--

Владислав Козуля

Интернет-деятель, программист, публицист, лектор, консультант-мемолог.