JavaScript

TIP

На разных проектах правила и настройки линтера могут отличаться. Здесь собрана золотая середина.

Отступы

Для отступов используем пробелы.

Ширина отступа — 4 пробела.

Переменные

var

var не используем там, где есть поддержка ES6+.

const

const используем для именования переменных, которым далее в коде не будет присвоено другое значение:

const elementId = '#productGallery';

const прекрасно подходит для DOM-элементов. Даже если на элемент навесится событие, добавится класс, изменится свойство и пр. В переменную сохраняется ссылка на объект и эта ссылка будет постоянна. Это же касается массивов и объектов:

// Это все ок
const $btn = $('#myButton');
$btn.on('click', ...);
const myObj = {};
myObj.myProp = 'hello';

Константы в глобальном окружении модуля (файла) именуем КАПСЛОКОМ:

const ENV = 'development';
const PI = 3.14;

let

let используем для переменных, значение которых в коде будет меняться:

let name; // Объявили переменную, значение будет присвоено позже
// ...
name = getName();

Объявление

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

// Плохо, пачка переменных с одним ключевым словом
let menu = $('#menu'),
    height = 100,
    width = 500;

// Хорошо, каждая переменная со своим ключевым словом
const menu = $('#menu');
let height = 100;
let width = 500;

Когда переменных много, бывает очень сложно разобраться, где заканчивается блок объявлений. Такой подход был популярен во времена var, но даже тогда у него было достаточнокритики:

// Плохо, одно ключевое слово на много переменных
const firstNumber = randomize(firstFrom, firstTo),
    step = randomize(stepFrom, stepTo),
    progression = generateProgression(firstNumber, step, numbersQty),
    emptyIndex = randomize(0, numbersQty),
    rightAnswer = progression[emptyIndex],
    rightAnswerIndex = randomize(0, answersQty),
    answers = generateAnswers(answersQty, rightAnswer);

// Хорошо, каждая переменная с ключевым словом
const firstNumber = randomize(firstFrom, firstTo);
const step = randomize(stepFrom, stepTo);
const progression = generateProgression(firstNumber, step, numbersQty);
const emptyIndex = randomize(0, numbersQty);
const rightAnswer = progression[emptyIndex];
const rightAnswerIndex = randomize(0, answersQty);
const answers = generateAnswers(answersQty, rightAnswer);

Почему так лучше:

  • Код быстрее доходит. Сразу видно ключевое слово, не нужно глазами лезть вверх, чтобы узнать, let там или const.
  • Не нужно следить, что в конце ; или ,.
  • Проще добавлять и менять местами переменные.
  • Не создается новых изменений в гите при замене , на ; и наоборот. Об этом вообще не надо думать.

Инкремент/декремент

Помимо увеличения переменной на единицу, операторы ++ и -- возвращают разное значение в зависимости от расположения оператора:

let a = 0;
let b = 0;
console.log(++a, b++); // 1, 0

Чтобы код оставался простым и понятным, операторы ++ и -- должны использоваться только для увеличения переменной на единицу и возвращаемое значение никак не должно влиять на код.

Код не должен зависеть от значения, которое возвращает инкремент/декремент:

// Плохо: код зависит от возвращаемого значения инкремента
let a = ++n;
a[i++] = n;

Переменную нужно изменять явно, вне выражения:

// Плохо: декремент используется в выражении
let i = arr.length;
while (--i >= 0) {
    console.log(arr[i]);
}

// Хорошо: декремент изменяет индекс вне выражения
let index = arr.length - 1;
while(index >= 0) {
    console.log(arr[index]);
    index--;
}

В цикле for увеличение счетчика должно выполняться там, где это предусмотрено синтаксисом:

// Плохо: i меняется в теле цикла, а не в позиции для счетчика;
// код зависит от возвращаемого значения декремента
for (var i = cls.length; i > 0;) {
    if (cls[--i] != className) { 
        // ...
    }
}

// Хорошо: i меняется в позиции счетчика цикла, как предусмотрено синтаксисом;
// код не зависит от возвращаемого значения декремента
for (var i = cls.length; i > 0; --i) {
    if (cls[i] != className) {
        // ...
    }
}

Кавычки

Используем одинарные кавычки. Двойные можно применять, если не хватает одинарных (по возможности избегаем экранирования):

// Плохо:
"use strict";
$("a[href^=\"http://\"");

// Хорошо:
'use strict';
$('a[href^="http://"');

Переводы строк

В конце строки не должно быть лишних пробелов.

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

Это стандарт в UNIX-системах для файлов. Важно при работе в консоли (например, при объединении файлов) и считается вообще хорошим тоном.

Блоки кода

Отступ первой строки

В блоке кода (между скобками { и }) не отступаем строку сверху:

// Плохо
function showFirstPoint() {

    renderPoint($firstItem);
    $firstItem.addClass('active');

    const itemId = $firstItem.data('point-id');
    renderInfo(itemId);
}

// Хорошо
function showFirstPoint() {
    renderPoint($firstItem);
    $firstItem.addClass('active');

    const itemId = $firstItem.data('point-id');
    renderInfo(itemId);
}

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

Условные конструкции

Используем строгое равенство и неравенство (=== и !==) не полагаясь на приведение типов:

// Плохо: так isZero('') вернет true
const isZero = n => n == 0;

// Хорошо
const isZero = n => n === 0; // все ок

if/else

Блок else нужно начинать на той же строке, на которой закрывается блок if:

// Плохо
if (width < 1025) {
    // do this 
}
else {
    // do that 
}

// Хорошо
if (width < 1025) {
    // do this 
} else {
    // do that 
}

Защитное условие (паттерн guard clause)

Используйте защитное условие для упрощения if/else (оно же граничный оператор или guard clause):

// Плохо, большой блок кода вложен в if
function doSomething() {
    if (isEverythingGood()) {
        // Много кода в случае, когда все хорошо
        // ...
        // ...
        // ...

        return SOME_VALUE;
    } else {
        return ANOTHER_VALUE;
    }
}

// Хорошо, большой блок кода вне ифа
function doSomething() {
    if (!isEverythingGood()) {
        return ANOTHER_VALUE;
    }

    // Много кода в случае, когда все хорошо
    // ...
    // ...
    // ...

    return SOME_VALUE;
}

Если первым делом обработать исключительный случай, то потом можно не беспокоиться и писать основной код не вкладывая его в if. К тому же уйдет и else. Кода станет меньше, читать его будет проще.

Тернарный оператор

Тернарный оператор — сокращенная форма записи if/else. В некоторых случаях код с ним станет проще и короче:

// Плохо, слишком много кода
let stickyHeaderHeight;
if (windowWidth < 1025) {
    stickyHeaderHeight = headerHeight;
} else {
    stickyHeaderHeight = stickyHeight;
}

// Хорошо, кода гораздо меньше
const stickyHeaderHeight = windowWidth < 1025 ? headerHeight : stickyHeight;

Предикаты

Предикат — выражение, которое возвращает true или false. Предикат отвечает на какой-то вопрос: «Список пустой?», «Прямоугольник высокий (ширина меньше высоты)?» и т. д.

В коде предикат должен начинаться с is:

// Плохо
const greaterHeight = (width, height) => width < height;
const listEmpty = listItems => listItems.length === 0;

// Хорошо
const isImageTall = (width, height) => width < height;
const isListEmpty = listItems => listItems.length === 0;

При чтении кода будет сразу ясно, что isListEmpty(menuItems) — предикат и возвращает булево значение.

Операторы сравнения===, <, > и т. д. возвращают либо true, либо false. Поэтому при использовании этих операторов достаточно вернуть результат выражения без использования if/else:

// Плохо: много ненужного кода
function isListEmpty(listItems) {
    if (listItems.length === 0) {
        return true;
    } else {
        return false;
    }
}

// Хорошо: оператор сравнения `===` и так вернет нужное булево значение
const isListEmpty = listItems => listItems.length === 0;

Правило

Такая конструкция:

if (ANYTHING) {
    return true;
} else {
    return false;
}

Равносильна такой:

return ANYTHING;

Если ANYTHING вычисляется в true, то вернуть true; иначе вернуть false — это избыточно. Достаточно вернуть результат вычисления ANYTHING.

Переменные с классами и айдишниками

Классы и айдишники нужно сохранять в переменные без точки и решетки:

const closeInfoBtn = 'shopInfoClose';
const addressItem = 'js-address-item';
const menuItemActive = 'menu__item_active';

Обращаться к элементам можно будет так:

$infoItem.on('click', '#' + closeInfoBtn, this.closeInfoBlock);
$addressContainer.on('click', '.' + addressItem, this.setPoint);

Таким образом мы сможем без труда использовать эти переменные в манипуляциях, где не нужны точки и решетки:

$menuItem.removeClass(menuItevActive);