![](https://habrastorage.org/getpro/habr/post_images/ade/9bb/322/ade9bb322bafb763c7f503a383036a27.jpg)
На Хабре уже разминались и развлекались кажущимися нелогичностями JavaScript. По-моему, такие примеры отличный способ размять мозги после длительных новогодних праздников, поэтому предлагаю вам подумать над 10 задачками.
Ответы и свой вариант объяснения почему такое поведение логично я буду скрывать под спойлером. Сразу оговорюсь, что не претендую на непоколебимую истину своих версий и буду рад их обсудить. В разгадывании вам может помочь отличный русский перевод спецификации ECMAScript 5 за который большое спасибо iliakan!
1. Так что помните, дети, всегда подставляем системы счисления!
Для начала простой, но прикольной пример. Что вернут такие вызовы parseInt?
parseInt('fuck');
parseInt('fuck', 16);
Решение
Как работает parseInt: он посимвольно проверяет переданную строку на соответствие указанной вторым аргументом системе счисления и если находит некорректный символ, то завершает работу. По умолчанию, система счисления считается десятичной. В не самых свежих браузерах если переданная строка начинается с 0, то по умолчанию система считается восьмеричной. В десятичной системе счисления символ ‘f’ недопустим и функция заканчивается свою работу не найдя ни одной цифры. А вот в шестнадцатиричной системе символ ‘f’ допустим и соответствует десятичному числу 15. Символ ‘u’ не допустим и функция завершает свою работу.
parseInt('fuck'); // NaN
parseInt('fuck', 16); // 15
Как работает parseInt: он посимвольно проверяет переданную строку на соответствие указанной вторым аргументом системе счисления и если находит некорректный символ, то завершает работу. По умолчанию, система счисления считается десятичной. В не самых свежих браузерах если переданная строка начинается с 0, то по умолчанию система считается восьмеричной. В десятичной системе счисления символ ‘f’ недопустим и функция заканчивается свою работу не найдя ни одной цифры. А вот в шестнадцатиричной системе символ ‘f’ допустим и соответствует десятичному числу 15. Символ ‘u’ не допустим и функция завершает свою работу.
2. Может кто подскажет?
Чему равно такое выражение?
"Why am I a " + typeof + "";
Решение
Оператор “+” обладает большим приоритетом чем “typeof”, поэтому указанная запись эквивалентна следующей:
Унарный “+” выполняет приведение к числу, поэтому “typeof” от результата его выполнения — это number.
“Why am I a number”
Оператор “+” обладает большим приоритетом чем “typeof”, поэтому указанная запись эквивалентна следующей:
“Why am I a ” + (typeof (+ “”)).
Унарный “+” выполняет приведение к числу, поэтому “typeof” от результата его выполнения — это number.
3. Кто больше?
Ну и кто больше?
[1, 2, 4] < [1, 2, 5]
[1, 2, 'd'] < [1, 2, 5]
[1, 2, ['d', 5]] < [1, 2, [20, 5]]
[1, 2, 5] == [1, 2, 5]
[1, 2, 5] <= [1, 2, 5]
[1, 2, 5] >= [1, 2, 5]
Решение
Согласно спецификации ECMAScript 5 для выполнения операции сравнения аргументы, которые не являются примитивами, должны быть приведены к примитивам (если кому интересно, то пункт 11.8.5 “Алгоритм сравнения абстрактного отношения”). Сначала JavaScript пробует преобразовать аргумент к числу с помощью метода valueOf и только когда это не удается приводит к строковому типу (изначально я хотел написать здесь про естественность сравнения чисел, и что когда мы говорим, что слово А больше слова Б, то имеем в виду длину. Но из беседы с коллегой выяснилось, что это не правда. Когда мы сравнимаем два слова мы инстанцируем связанные с ними образы и сравнимаем их. И только если образов не нашлось, то сравниваем слова не как метки образов, а именно как набор букв). Для массивов метод valueOf не определен и поэтому они сравниваются как строки:
Но все вышесказаное верно только для операций сравнения. В случае оператора “==”” приведение к примтивам осуществляется только если один из аргументов примитив, а во всех остальных случаях оператор “==” возвращает true только если оба аргумента ссылаются на один объект (пункт 11.9.3 “Алгоритм сравнения абстрактного равенства”), что в нашем случае не так.
Следует заметить, что для объектов сравнение работать не будет, т.к. их дефолтный toString возвращает [object Object].
[1, 2, 4] < [1, 2, 5] //true
[1, 2, 'd'] < [1, 2, 5] //false
[1, 2, ['d', 5]] < [1, 2, [20, 5]] //false
[1, 2, 5] == [1, 2, 5] //false
[1, 2, 5] <= [1, 2, 5] //true
[1, 2, 5] >= [1, 2, 5] //true
Согласно спецификации ECMAScript 5 для выполнения операции сравнения аргументы, которые не являются примитивами, должны быть приведены к примитивам (если кому интересно, то пункт 11.8.5 “Алгоритм сравнения абстрактного отношения”). Сначала JavaScript пробует преобразовать аргумент к числу с помощью метода valueOf и только когда это не удается приводит к строковому типу (изначально я хотел написать здесь про естественность сравнения чисел, и что когда мы говорим, что слово А больше слова Б, то имеем в виду длину. Но из беседы с коллегой выяснилось, что это не правда. Когда мы сравнимаем два слова мы инстанцируем связанные с ними образы и сравнимаем их. И только если образов не нашлось, то сравниваем слова не как метки образов, а именно как набор букв). Для массивов метод valueOf не определен и поэтому они сравниваются как строки:
"1,2,4" < "1,2,5" //true
"1,2,d" < "1,2,5" //false
Но все вышесказаное верно только для операций сравнения. В случае оператора “==”” приведение к примтивам осуществляется только если один из аргументов примитив, а во всех остальных случаях оператор “==” возвращает true только если оба аргумента ссылаются на один объект (пункт 11.9.3 “Алгоритм сравнения абстрактного равенства”), что в нашем случае не так.
Следует заметить, что для объектов сравнение работать не будет, т.к. их дефолтный toString возвращает [object Object].
4. Кто больше?-2
По мотивам предыдущей загадки, точнее её решения.
var obj1 = {
test: 'test',
toString: function(){ return 10; },
valueOf: function(){ return 100; }
}
var obj2 = {
test: 'test',
toString: function(){ return 100; },
valueOf: function(){ return 10; }
}
obj1 ? obj2
Решение
Как было сказано в предыдущем решении, при сравнении JavaScript сначала пытается привести непримитивные аргументы к числам, т.е. вызывает метод valueOf. А 100 > 10.
obj1 > obj2
Как было сказано в предыдущем решении, при сравнении JavaScript сначала пытается привести непримитивные аргументы к числам, т.е. вызывает метод valueOf. А 100 > 10.
5. Бесконечности безумия
Что вернет?
parseFloat('Infinity');
Number('Infinity');
parseInt('Infinity');
Решение
Число отображается как бесконечность, когда оно превышает максимальное число с плавающей точкой: 1.7976931348623157E+308. Т.е. по сути Infinity это константа с дробным значением и поэтому она определяется parseFloat и Number, но не определяется parseInt.
parseFloat('Infinity'); //Infinity
Number('Infinity'); //Infinity
parseInt('Infinity'); //NaN
Число отображается как бесконечность, когда оно превышает максимальное число с плавающей точкой: 1.7976931348623157E+308. Т.е. по сути Infinity это константа с дробным значением и поэтому она определяется parseFloat и Number, но не определяется parseInt.
6. Игры с плюсами
Для тех, кто внимательно читал предыдущие решения, не составит труда сказать, что вернет вот такое выражение:
"foo" + + "bar"
Решение
Выделим унарный +:
Как не трудно видеть, он вернет NaN, т.к. строку нельзя привести к числу. Таким образом:
("foo" + + "bar") === "fooNaN" // true
Выделим унарный +:
“foo” + (+ “bar”)
Как не трудно видеть, он вернет NaN, т.к. строку нельзя привести к числу. Таким образом:
"foo" + NaN === "fooNaN".
7. Самое маленькое число
Кто больше?
Number.MIN_VALUE ? 0
Решение
Согласно спецификации MIN_VALUE — это число наиболее приближенное к 0, которое позволяет JavaScript. Приблизительно равно 5e-324. Все числа меньшие по модулю конвертируются в 0. Называть его минимальным, пожалуй, логично, т.к. максимальное отрицательное так и называется “максимально отрицательное”, но все-таки вносит определенную путаницу.
Number.MIN_VALUE > 0
Согласно спецификации MIN_VALUE — это число наиболее приближенное к 0, которое позволяет JavaScript. Приблизительно равно 5e-324. Все числа меньшие по модулю конвертируются в 0. Называть его минимальным, пожалуй, логично, т.к. максимальное отрицательное так и называется “максимально отрицательное”, но все-таки вносит определенную путаницу.
8. Налог на роскошь
Что увидим на экране?
alert(111111111111111111111);
Решение
JavaScript интерпретирует большие числа в экспоненциальной форме:
Но для хранения 1.11111111111111111111 точности не хватает и поэтому
111111111111111111000
JavaScript интерпретирует большие числа в экспоненциальной форме:
111111111111111111111 = 1.11111111111111111111e20.
Но для хранения 1.11111111111111111111 точности не хватает и поэтому
(1.11111111111111111111e20).toString() == 111111111111111110000
9. “Булева арифметика”
(true + 1) === 2;
(true + true) === 2;
true === 2;
true === 1;
Решение
Двухместный оператор “+” либо складывает числа, либо конкатенирует строки. При приведении true к числу будет возвращена 1.
Оператор “===” в отличии от “+” не производит приведение типов и поэтому true строго не равно единице.
(true + 1) === 2; // true
(true + true) === 2; // true
true === 2; // false
true === 1; // false
Двухместный оператор “+” либо складывает числа, либо конкатенирует строки. При приведении true к числу будет возвращена 1.
Оператор “===” в отличии от “+” не производит приведение типов и поэтому true строго не равно единице.
10. Отрицание пустоты
[] == ![]
Решение
Небольшая цитата из правила приведение типов в операторе == из спецификации:
1. Если Type(x) – Number и Type(y) – String, вернуть результат сравнения x == ToNumber(y).
2. Если Type(x) – String и Type(y) – Number, вернуть результат сравнения ToNumber(x) == y.
3. Если Type(x) – Boolean, вернуть результат сравнения ToNumber(x) == y.
4. Если Type(y) – Boolean, вернуть результат сравнения x == ToNumber(y).
5. Если Type(x) – либо String, либо Number, и Type(y) – Object, вернуть результат сравнения x == ToPrimitive(y).
6. Если Type(x) – Object и Type(y) – либо String, либо Number, вернуть результат сравнения ToPrimitive(x) == y.
Сначала вычислим правую часть равенства и получим:
т.к. объект всегда приводится к true.
Теперь по правилу 4:
по правилу 6 и со знаниями из 3 задачки:
Затем по правилу 2:
Вуаля!
[] == ![] //true
Небольшая цитата из правила приведение типов в операторе == из спецификации:
1. Если Type(x) – Number и Type(y) – String, вернуть результат сравнения x == ToNumber(y).
2. Если Type(x) – String и Type(y) – Number, вернуть результат сравнения ToNumber(x) == y.
3. Если Type(x) – Boolean, вернуть результат сравнения ToNumber(x) == y.
4. Если Type(y) – Boolean, вернуть результат сравнения x == ToNumber(y).
5. Если Type(x) – либо String, либо Number, и Type(y) – Object, вернуть результат сравнения x == ToPrimitive(y).
6. Если Type(x) – Object и Type(y) – либо String, либо Number, вернуть результат сравнения ToPrimitive(x) == y.
Сначала вычислим правую часть равенства и получим:
[] == false
т.к. объект всегда приводится к true.
Теперь по правилу 4:
[] == 0
по правилу 6 и со знаниями из 3 задачки:
"" == 0
Затем по правилу 2:
0 == 0
Вуаля!
Заключение
Сколько задач с первого раза вы решили правильно? Надеюсь, вам понравились эти небольшие упражнения. По субъективным ощущениям, разбор таких ситуаций очень полезен. Отличный повод заглянуть в документацию и освежить расположение граблей.
Если вы знаете другие интересные примеры, то прошу добавлять в комментарии скрывая ответы и объяснения под спойлер. Со своей стороны не могу удержаться, чтобы не добавить в качестве бонуса задачку из комментариев к одной из старых статей:
Что больше?
Math.min() ? Math.max()
Решение
Согласно определению метода Math.max в случае отсутствия аргументов возвращается -Infinity. Аналогично min возвращает просто Infinity.
Math.min() > Math.max()
Согласно определению метода Math.max в случае отсутствия аргументов возвращается -Infinity. Аналогично min возвращает просто Infinity.
И да, главное: не используйте это в боевом коде! Пожалейте тех, кто будет его читать.