Previous Entry Share
Небольшой Javascript чек-лист
rawgift
Этот «чек-лист» — второе приятное впечатление от Инновы полуторамесячной давности, когда я написал 100 строк кода, а Паша Моторин отревьюил их по-быстрому. До этого я писал джаваскрипт в одиночку в течение 3 лет, чужим кодом не интересовался (потому и заскучал в итоге) и вообще находился в информационном вакууме.

1. Single var pattern
Все объявления переменных надо размещать в самом начале функции, потому что в джаваскрипте переменная видна во всей функции, где бы она ни была объявлена. Пренебрежение этим правилом чревато возвратом значения «undefined» для переменной, которая по логике программиста ещё не существует (т.е. объявлена позже):

(function() {
    var def = 10;
    alert(def * undef); // 10 * undefined = NaN
    var undef = 20;
})();

Если убрать объявление undef, то интерпретатор сгенерирует ошибку и обидный косяк будет найден. 

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

var $button = $('button.my'); 
$button.each(function() { ... });

Вроде бы ничего ужасного, но плохо становится, когда таких кусков в функции большинство. А именно так и хочется писать, когда заранее не продумываешь функциональность. На самом деле, single var pattern заставляет лучше продумывать архитектуру программы и делать это заранее. Я сторонник немного избыточного подхода, когда даже очень простой модуль из 50 строк пишется грамотно, чтобы сейчас было легко его понять, а потом — безболезненно расширить.


2. Проверка undefined-переменных
Часто стоит проверять не тип переменной и её истинность, а её неистинность. Т.е. вместо привычной мне проверки if (typeof variable !== ‘string’ && variable) стоит разворачивать условие наизнанку и писать if (!variable) { return; }  Проверять потенциально необъявленные переменные можно только первым способом, иначе есть вероятность нарваться на тот самый ReferenceError.

Такой подход полезен для проверки аргументов функций без присвоенных значений, например: это объявленные, но не определённые переменные. Плюса два: сокращение объёма кода и возможность уменьшить вложенность кода. Так намного удобней: не надо вкладывать фактически весь код в условие, надо просто вывернуть условие наизнанку.


3. Правильные сравнения
Сишный стиль сравнения переменной с константой if ('value' == mystring) { ... } стоит забыть как страшный сон как минимум по 2 причинам.
Во-первых, оператор присвоения «=» и оператор строгого равенства «===», рекомендуемый Крокфордом, уже сложно перепутать. 
Во-вторых, typeof variable === 'string' читается намного лучше, чем 'string' === typeof variable.


4. Кэширование
Я часто использовал объекты-неймспейсы, чтобы не засорять глобальный объект своим барахлом:

var MySite = {};
MySite.pages = 10;


Удобно кэшировать обращения к таким объектам внутри функции: var MySite = window.MySite. И вообще имеет смысл кэшировать все объекты, находящиеся во внешнем scope — window, document и т.п., — если к ним нужно обратиться больше одного раза. Тем более в цикле!

Для примера создадим массив из 100 тысяч случайных чисел, а потом вычислим квадрат каждого внутри анонимной функции. В первом случае интерпретатору приходится каждый раз вылезать в scope-объект верхнего уровня, во втором — нет. Третий случай эталонный, т.к. scope остаётся глобальным.
Скопируйте и выполните этот код в браузере.

window.x = [];
for (i = 0; i < 100000; i += 1) {
    window.x[i] = Math.random() * 100;
}
(function() {
    var i;
    console.time('uncached');
    for (i = 0; i < 100000; i += 1) {
        window.x[i] *= window.x[i];
    }
    console.timeEnd('uncached');
}());

for (i = 0; i < 100000; i += 1) {
    window.x[i] = Math.random() * 100;
}
(function() {
    var i,
    wx = window.x;
    console.time('cached');
    for (i = 0; i < 100000; i += 1) {
        wx[i] *= wx[i];
    }
    console.timeEnd('cached');
}());
for (i = 0; i < 100000; i += 1) {
    window.x[i] = Math.random() * 100;
}
console.time('global');
for (i = 0; i < 100000; i += 1) {
    window.x[i] *= window.x[i];
}
console.timeEnd('global');

(function() {
    var x = [],
    i;
    for (i = 0; i < 100000; i += 1) {
        x[i] = Math.random() * 100;
    }
    console.time('local');
    for (i = 0; i < 100000; i += 1) {
      x[i] *= x[i];
    }
   console.timeEnd('local');
}());

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

5. Правильное именование событий
До этой штуки я без Паши точно не додумался бы — вот так бывает необходим взгляд со стороны. 
Я начал пользоваться кастомными событиями достаточно давно и часто писал что-то вроде: $(document).trigger('popup-start-handling'), т.е. дословно указывал на необходимость начать определённую процедуру — обработку открытия попапа.

Однако это в корне неверно, т.к. в названии события должна отражаться суть произошедшего: $(document).trigger('popup-appear'), например. И уже множество возможных слушателей сами решат, что им делать: то ли start-handling, то ли stop-destroying.


6. Неймспейсы для событий 
Как оказалось, удачная штука. Это джиквери-штучка, в стандарте DOM-событий такого нет.
Например, мы можем навесить несколько обработчиков одного события, а потом захотеть их удалить.

$(document).on('click', function() { alert('a'); });
$(document).on('click', function() { alert('b'); });
$(document).on('click', function() { alert('c'); });
$(document).on('click', function() { alert('d'); });
$(document').off('click'); // ??? Warning! All click handlers are removed.


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

$(document).on('click.mine', function() { alert('a'); });
$(document).on('click.mine', function() { alert('b'); });
$(document).on('click.mine', function() { alert('c'); });
$(document).on('click.mine', function() { alert('d'); });
$(document).off('.mine'); // OK!

Этот код, как и ожидается, удалит только наши обработчики событий, не тронув другие. Удобно. Однако есть два дополнения.

Удаление не всех обработчиков в соответствии с DOM Events
Код, приведённый в примере выше, однозначно плох: не стоит плодить сущности и создавать несколько функций с кодом обработчиков. Ведь в отличие от кода, написанного кем-то до нас или встроенного в браузер, свой код мы контролировать можем: надо переписать код так, чтобы получилась одна функция. Дальше делаем функцию именнованной, задаём её в качестве обработчика и — бинго! — мы можем удалить именно этот обработчик, когда потребуется:

var handler = function() {
    alert('a');
    alert('b');
    alert('c');
    alert('d');
}
document.addEventListener('click', handler);
document.removeEventListener('click', handler); 

Работает такой код не хуже, а организован, на мой взгляд, правильней. Что использовать — выбирать вам.
Наверняка джикверные неймспейсы работают на основе этой документированной возможности — за 5 минут разобраться не удалось.

Отказ от инициализации вместо её повторения 
Я часто использовал приём с удалением обработчика, чтобы застраховаться от нескольких выполнений одного и того же кода. Когда-то, видимо, натнулся на то, что галерея может проинициализироваться дважды и слайды будут листаться по 2. Понял, что это не дело, ну и придумал писать что-то подобное: 1) удаляем результаты предыдущей инициализации, 2) вешаем новый ТОТ ЖЕ САМЫЙ код.

Андрейка смотрел-смотрел на то, что я делаю, а потом открыл мне глаза на жизнь: намного логичней не проводить инициализацию заново, оставляя всё тот же обработчик события, а просто определять, была ли инициализация и ненужный код не выполнять. Бинго! 

  • 1

Не понял 5 пункт

Олег!

Интересная статья, жаль, что со мной рядом нет Паши и Андрейки.

Я не понял 5 пункт, поясни, пожалуйста. Что изменится для возможных слушателей, если я просто изменю имя события?

  • 1
?

Log in

No account? Create an account