Помните, пару дней назад я задавался вопросом приведения типов? Я тогда не нашел объяснения, почему {}+[]
дает 0, a {}+{}
дает NaN
. Теперь я могу это объяснить.
Оказывается, левый операнд воспринимается не как пустой объект, а как пустой блок кода. И именно это главная составляющая, которой мне не хватило для объяснения. Получается, что выражение делится на два: {};+[]
. Дальше []
приводится к примитивному типу и становится равным пустой строке. А так как унарный плюс требует числовой операнд, то пустая строка переводится в число и получается 0.
Такая же история с {}+{}
. Это выражение тоже делится на два: {};+{}
. При переводе {}
к примитивному типу мы получаем строку ”[object Object]”
, которая при приведении к числу и дает NaN
. Так что никакой мистики.
Вот такая магия. Правильный ответ был найден на Stack Overflow.
P.S. Пример в прошлой статье я поправил на более верный.
Нашел еще один JavaScript-фреймворк (спасибо Виктору за наводку), который на несколько порядков быстрее всех остальных. Прошу любить и жаловать vanilla.js! Если верить создателям, то получение всех элементов по имени тега отрабатывает быстрее jQuery в 425 раз. Я думаю, одного этого достаточно, чтобы глянуть на фреймворк.
Вы, наверное, слышали об этом JavaScript-фреймворке. Модный тренд, если позволите так выразиться. Superheroic JavaScript MVW1 Framework, как утвержают сами разработчики. Не удержался и посмотрел на него, тем более что выдалась возможность сделать это в рабочее время и за счет заказчика. Мы выбираем основу для будущего проекта, вот и решили глянуть современные js фреймворки, и мне достался AngularJS. Поделюсь своими впечатлениями.
Не знаю как с производительностью, но с функциональностью у этого фреймворка полный порядок. Покажу на небольшом примере. Итак, у нас есть кусочек разметки:
<table class="table table-striped table-hover" ng-controller="TestController">
<thead>
<tr>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="action in actions">
<td>{{action.action}}</td>
</tr>
</tbody>
</table>
Дальше мы подключаем JavaScript:
function TestController($scope) {
$scope.actions = [
{action: 'Action1'},
{action: 'Action2'}
];
}
Ну и всё. У нас готова страница с таблицей, в которой 2 строки: «Action1» и «Action2». Но это еще не все. Если мы потом добавим в поле actions
еще чего-нибудь, то оно тоже автоматически отобразится на странице.
Единственное, мне потребовалось некоторое время, чтобы разобраться с некоторыми более сложными вещами. Например, создание независимых компонентов. В моей задаче требовалось, чтобы родительский компонент передавал данные для дочернего. В результате оказалось, что на момент вызова родительского контроллера дочерний еще не инициализирован и нужно немного подождать. И лучше всего использовать для этого событие $viewContentLoaded
. Чтобы это понять, официального сайта было недостаточно, и пришлось провести пару-тройку часов в обнимку с дебаггером и гуглом.
В общем, впечатление от фреймворка очень положительное, надо будет попробовать написать что-нибудь более-менее серьезное.
Еще примеры можете оценить на главной странице фреймворка, они там гораздо лучше, чем я вам привел.
Model-View-Whatever
JavaScript прекрасен. Но в нем есть некоторое количество так называемых особенностей, которые делают его чуть менее прекрасным, чем хотелось бы. Например, приведение типов.
[]+[]
> ""
[]+{}
> "[object Object]"
{}+[]
> 0
{}+{}
> NaN
Почему так? Это кажется бредом, пока не разберешься с системой типов в JavaScript. Ладно-ладно, это кажется бредом в любом случае, но тем не менее попробуем хоть чуть разобраться.
Самое главное, что нужно знать — операция +
работает только с примитивными типами. Т.е. что бы вы ни написали, выражение будет приведено к этим типам. Даже больше: если хотя бы один операнд — строка, то и результат будет строкой. Поэтому, чтобы понять причину происходящего выше, нужно узнать, как объекты и массивы приводятся к примитивным типам.
У каждого объекта и массива (ведь массив тоже объект) есть 2 метода: valueOf
и toString
. И именно они будут использоваться для преобразования типов. Сначала интерпретатор вызывает valueOf
, и если он вернул не примитивное значение, то вызывается toString
. Теперь нужно выяснить, что же возвращают эти методы у {}
и []
.
console.log({}.valueOf()); // Возвращает себя
console.log([].valueOf()); // [] (тоже себя)
console.log([].toString()); // ""
console.log({}.toString()); // "[object Object]"
Засада. Каким образом {}+[]
дает 0, ведь по логике должно быть "[object Object]"
? Ковыряние в спецификации пока ни к чему не привело. Если кто знает правильный ответ, не томите, поделитесь наблюдениями.
Ну и совсем напоследок видео, из-за которого я начал разбираться в этой теме. Посмотрите, не пожалеете.
UPD: Исправлен верхний листинг.
UPD2: Разгадка.
Блог подвергся тотальной оптимизации. Теперь всё должно работать еще быстрее и еще лучше. Теперь на каждой странице осталось только по одному js и css файлу, конечно, если не считать сторонние сервисы — Disqus и Google Analytics. И вообще везде царствует гармония и фен-шуй.
Весь javascript был переписан с использованием Google Closurе. Попутно в него были внесены мелкие, но приятные дополнения. Например, навигация с помощью комбинаций клавиш Ctrl+← и Ctrl+→ или эмуляция переходов между состояниями (transitions) для старых браузеров, не поддерживающих CSS Transitions. Конечно, для этого пришлось написать часть кода из плагинов Twitter Bootstrap. Но в любом случае я доволен: суммарный размер всех js фалов на рабочем сервере уменьшился в 3 раза: со 180 до 60 килобайт. Да и прокачанный навык в Google Closure многого стоит.
С CSS было проще: я просто выкинул неиспользуемые части из Twitter Bootstrap и добавил свой код в результирующий файл. Это не заняло много времени.
А еще теперь при наведении на стоку в листинге с кодом слева подписывается ее номер. Не особо нужная штука, но может кому пригодиться.
Весь код, как обычно, на GitHub, включая новые большие скрипты для сборки проекта. Смотрим, изучаем, комментируем.