# JS


# Основы

  1. Читаемость первостепенна!

    Любое решение в первую очередь должно быть понятным и разбитым на логичные части, чтобы другие члены команды могли разобраться не только в назначении кода, но и в применяемых решениях без дополнительных оговорок.

  2. Все, что может быть написано на чистых html+css в рамках тех свойств, что дают заявленные браузеры - должно быть написано без применения JS.

    Это правило не должно противоречить предыдущему пункту. Это очень важное правило, так как:

    • Часто излишнее использование скриптов вводит других разработчиков в заблуждение, особенно, когда посреди работы скрипт внезапно меняет какие-либо свойства элемента;
    • Это уменьшает производительность веб-приложения;
    • Как правило код, написанный на html+css, гораздо проще изменять.

    Если реализация функционала на html+css возможна только с применением разных хаков, то тут уже стоит переходить на JS, чтобы поддерживаемость проекта не падала.

  3. Избегать неявных критических зависимостей

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

    Вторым примером будет, когда вы в коде опираетесь на что-то из базы данных. Если в базе таких значений не будет и ваша система упала - 99% что это ваша вина, даже если заполнение конкретно этого типа данных было священной обязанностью админов, которые прохалтурили.

# Библиотеки

  1. Ни в коем случае никакой файл библиотеки не редактировать. Если есть небольшая полезная либа, у которой не хватает небольшой правки, то нужно сделать fork и отдельно в зависимостях указать путь до своей версии

  2. Изучайте best-practices для крупных библиотек, которые вы используете в проекте:

    Например:

  3. Модули должны использоваться из node_modules

    Если используются дополнительные модули, то в package.json должны быть прописаны пути до локальных модулей, которые вы установили.

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

    • есть ли у нее отдельный публичный репозиторий и где он находится;
    • есть ли у нее документация и описывает ли она использование библиотеки для нашей поставленной проблемы;
    • если доков нет или нет нормального описания, надо внимательно изучить код библиотеки и понять API, которое позволит решить проблему;
    • осмотреть открытые issue (хотя бы пробежать глазами по первым двум страницам) и изучить те, что могут повлиять на работу библиотеки в контексте нашего проекта;
    • принять решение о использовании библиотеки только совместно с другими равными по статусу или старшими фронтенд-разработчиками из команды;
    • Обратить внимание, сопровождается ли библиотека, или является заброшенной. Есть ли pull request'ы, и есть ли в них обсуждения. Принимаются ли pull request'ы;
    • Обратить внимание, протестирована ли библиотека. Если библиотека протестирована, то стоит уделить внимание тестам, которые были написаны для неё: какие use-cases покрываются в тестах, и решают ли эти use-cases проблемы, ради которых вы хотите её использовать. Так же стоит уделить внимание к качеству самих тестов, не являются ли они хрупкими к изменениям и т.д. Если к рассматриваемой библиотеке тестов нет, возможно, вам стоит поискать другую библиотеку.

# Хорошие практики

  1. Знать и использовать стайлгайд от AirBnB

    Сам он расположен здесь (opens new window). Для него есть переводы, в том числе на русском, но читать надо оригинал, так как там описаны ES6 фичи. Все, что перечислено в гайде принять к исполнению, если это не перекрыто нашими правилами ниже:

    • Использовать eslint или аналоги.

# Соглашение об именованиях

# Переменные

  • Имя переменной должно полно и точно описывать сущность и ее предназначение

  • Старайтесь не давать переменным слишком длинные имена

    Как правило, чтобы раскрыть смысл переменной, достаточно около трех слов (10-15 символов). Переменные всегда используются в каком-то контексте, исходя из которого некоторые детали могут опускаться. Например, объект класса слайдера может содержать поле range, в котором содержится диапазон слайдера. Нет нужды называть это поле sliderRange, поскольку и так очевидно, что это относится к слайдеру. Длинные имена усложняют чтение кода и их следует использовать только тогда, когда более короткое имя неоднозначно и несет в себе слишком мало информации (учитывая контекст, о котором сказано выше) для понимания ее смысла.

  • При именовании переменных использовать camelCase, так как это принято в JavaScript комьюнити. Если данные, которые приходят "со стороны" (например с бекенда), не в camelCase формате (snake_case или любой другой), имеет смысл нормализовать ключи для консистентности.

  • Именовать переменные по смыслу

    Имеется в виду не использовать в качестве имен date, result, array. Например, для текущей даты лучше использовать переменную today, а не date, для списка студентов лучше подойдет students а не array;

  • Присваивайте булевым переменным имена, подразумевающие значение true или false

    В основном мы юзаем префиксы в начале переменных: is/are, has, with. Порой название boolean переменной можно сформулировать как утверждение: selectedColsWereChanged, что позволит сделать код более читаемым и понятным.

    if (selectedColsWereChanged) { // do something... }
    

    С другой стороны никто вас не ограничивает в использовании других конструкций английского языка: will, should, etc. которые позволят корректно выразить то, что вы хотите выразить в коде. Еще есть такой кейс: некоторые названия не нуждаются в дополнительной инфе, которая покажет что это boolean (либо по соображениям языка, либо так исторически сложилось): pened, hidden, disabled, etc. А вообще учите английский, чтобы грамотно выражать свои мысли и код 😃 Тогда вам не придется запоминать все эти штуки, и будет самим понятно почему так или иначе

  • Использовать утвердительную форму булевых переменных

    Имена, основанные на отрицании (такие как notFound, notdone и notSuccessful), при выполнении над переменной операции отрицания становятся куда менее понятны, например:

    if (not notFound) {..}
    

    Подобные имена следует заменить на found, done и processingComplete, выполняя отрицание переменных в случае надобности. Так что для проверки нужного значения вы использовали бы выражение found, а не not notFound.

  • Используйте объясняющие переменные

    Плохо:

    const address = "One Infinite Loop, Cupertino 95014";
    const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
    saveCityZipCode(
      address.match(cityZipCodeRegex)[1],
      address.match(cityZipCodeRegex)[2]
    );
    

    Хорошо:

    const address = "One Infinite Loop, Cupertino 95014";
    const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
    const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
    saveCityZipCode(city, zipCode);
    
  • Имя константы должно характеризовать абстрактную сущность, представляемую константой, а не конкретное значение

    Например нужна константа, отражающая количество рабочих дней, плохо: FIVE, хорошо: WORK_DAYS.

# Функции

  • Все имена функций, за редкими исключениями (например, следование уже устоявшемуся соглашению в рамках какой-нибудь библиотеки), должны начинаться с глаголов

  • Функции высшего порядка, возвращающие функции, следует именовать по шаблону make + .* + отглагольное существительное, где .* — опциональный, синтаксически корректный набор слов уточняющий предназначение функции

    Пример: makeButtonClickHandler.

  • Обработчики событий** (под событиями понимаются не только DOM-события, но и какие-то абстрактные события, которые обрабатываются с помощью коллбэков, вроде onClose модального окна или onLogin формы логина)

  • Пропсы React-компоненты, относящиеся к обработке событий, должны именоваться по шаблону onEventName, где EventName — имя обрабатываемого события

  • Обработчики событий для БЭМ-элементов, а также для компонент, вложенных в БЭМ-элементы, именуются по шаблону handleElementNameEventName, где ElementName - имя БЭМ-элемента

    Примеры:

    <button
      className="block__cancel-button"
      onClick="{this.handleCancelButtonClick}"
    />
    <div className="block__first-name-input">
      <input onChange="{this.handleFirstNameInputChange}" />
    </div>
    

    Если компонента не вложена в БЭМ-элемент, то ElementName в данном случае стоит выбирать по смыслу, исходя из контекста (причем, поскольку БЭМ-элементы именуются по похожему принципу, то и БЭМ-элемент в данном случае назывался бы, скорее всего, так же):

    <div className="block">
      <button onClick="{this.handleCancelButtonClick}" />
      <input onChange="{this.handleFirstNameInputChange}" />
    </div>
    

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

    Примечание: в случаях, когда тело обработчика используется где-то ещё, то его следует выносить в отдельную функцию: например, обработчик handleButtonClick, выводящий список итемов, может передаваться только в пропс onClick БЭМ-элемента "button"; если же требуется вывести список итемов еще в каких-то других случаях, то следует сделать следующим образом:

    handleButtonClick() {
      showItems();
    }
    
    showItems() {
      ...
    }
    

# Методы

Именование

  • Имя метода должно быть self descriptive(описывать само себя). Из названия должно быть понятно для чего нужен метод и что он делает. Не должно быть причин задумываться о его внутренней реализации.

Параметры:

  • Если в метод необходимо передать много параметров, объединяйте их в один объект. Например, у вас есть метод, который рисует прямоугольник принимая координаты точек, ширину, высоту, цвет, толщину рамок и другие параметры. В таком случае, проще будет передать один объект ‘options’. Это снимет с нас обязанность держать в голове все параметры, а также их порядок, и позволит удобно сделать многие свойства с дефолтными значениями;
  • В случае, когда параметры передаются объектом, получать их значения предпочтительнее с помощью деструктуризации;
  • Если параметр опциональный, необходимо ему задать значение по умолчанию.

Тело:

  • Соблюдать принцип Single Responsibility. Метод должен выполнять только одну задачу (например, метод createReport, который создает отчет и отправляет его на сервер необходимо разделить на два: createReport и sendReport);
  • Метод должен быть компактным. Обычно, длина метода варьируется в пределах 10-40 строк. Это не значит что ваш метод обязательно должен должен быть в таких рамках, но если ваш метод занимает 200 строк, это повод задуматься о том, можно ли его разделить на несколько отдельных методов. Про компактность методов можно почитать у С. Макконела “Совершенный код” и Р. Мартина “Чистый код”. Так же здесь (opens new window) Неплохое рассуждение о размерах метода.

# Делайте время жизни переменных как можно короче

Объявление переменной должно быть рядом с местом ее использования. Это дает более быстрое понимание того, что происходит в коде, плюс снижается вероятность неправильного использования переменной или ее перезапись. Более подробнее про время жизни переменной можно прочитать у С. Макконела в "Совершенном коде".

# Все кнопки сабмита должны быть внутри <form> вместе со всеми соответствующими инпутами. Обработку завершения заполнения вешать на событие submit формы, а не клик по кнопке

Это очень важное правило для UX, которое часто упускается новичками. Еще раз: не вешайте обработку заполнения формы на событие click у кнопки, а вешайте на событие submit у формы. Как минимум, submit формы может быть вызван дополнительными путями - например, нажатие на enter у любого input, и, совершая ошибку, вы серьезно нарушаете UX всего сайта.

# Все проверки содержащие более одного условия должны быть вынесены

Выносить в отдельную архитектурную единицу: переменную или функцию.

Плохо:

 if ((this.allowUpdate) && ((user.isAdmin) || (user.role === item.owner)) {
   this.update(item.data);
 }

Хорошо:

 function isUpdateAllowedForUser(user, item) {
   return (this.allowUpdate) && ((user.isAdmin) || (user.role === item.owner);
 }
 //.. в нужном месте
 if (this.isUpdateAllowedForUser(user, item)) {
   this.update(item.data);
 }

Тоже хорошо (пример ветвистого кода с кучей условий, где все именованно и раскидано, а поэтому понятно)

const isTodayRequested = day === "today";
const isTomorrowRequested = day === "tomorrow";
const isDepartTomorrow =
  departure.day() !== now.day() && unix(departure) > unix(now);
const isDepartToday = departure.day() === now.day();
const isRequestedDayIncorrect =
  (isDepartToday && !isTodayRequested) ||
  (isDepartTomorrow && !isTomorrowRequested);
const result = isRequestedDayIncorrect ? getPreviousDay(day) : day;

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

# Всегда избегать неявного приведения типов в js

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

Плохо:

const lifes = [true, false, false, true, true];
aliveTotal = 0;
for (let i = 0; i < lifes.length; i++) {
  aliveTotal += lifes[i]; // тут мы к числу прибавляем элемент массива, который булин.
}

Самое ужасное, что код этот будет работать, но в нем все слишком уязвимо и его чтобы поддерживать, надо постоянно в голове держать, что там булины с числами складываются.

# Не изменять прототипы стандартных конструкторов

Например:

String.prototype или Function.prototype), до тех пор пока вопрос внимательно не изучен и этот трюк не согласован со страшим разработчиком

# Конструктор класса должен быть максимально легковесным

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

# Выносить обработчики событий в отдельные функции

Не стоит создавать анонимные функции прямо в том же месте, где идет привязка к событию.

Плохо:

elem.addEventListener("click", function() {
  alert("Спасибо!");
});

Хорошо:

 class Component {

   bindEventListeners() {
     stopButton.addEventListener('click', this.handleStopButtonClick);
   }

   handleStopButtonClick() {
     ...
   }
 }

# В обработчиках работать не с контекстом (this), а с аргументом объекта ивента, который был передан свыше

Не полагаться на this при работе с объектами событий, а использовать первый параметр коллбека event. Работать только с тем, что приходит из события. JS позволяет вносить дополнительные данные в event. То есть не использовать "this.value". Вместо этого получать данные через объект события "e.currentTarget.value".

Плохо:

handleButtonClick() {
 const buttonWidth = $(this).width();
 ...
}

Хорошо:

handleButtonClick(event) {
 const buttonWidth = $(event.currentTarget).width();
 ...
}

# Стараться по максимуму избегать циклов и использовать встроенные методы массивов

.map, .filter и тд обычно гораздо проще читаются, плюс функцию обработки одного элемента можно вынести и многократно использовать в разных местах.

# Не использовать литералы из бизнес-логики напрямую - надо создавать объект с константами

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

Можно:

 if (status === ordersModule.ACTIVE_STATUS) {...}

Нельзя:

 if (status === 'active') {...}

# Форматирование размещения импортов

Импорты объединяются в секции, секции разделяются переносом строки. Для фронта выделяются три секции (в таком порядке размещения):

  • Абсолютные импорты из node_modules;
  • Абсолютные импорты из src;
  • Относительные импорты, отсортированные в порядке убывания переходов на более верхний уровень в дереве пути (через ../).

Например:

import * as React from "react";
import block from "bem-cn";
import { connect, Dispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { arrayPush } from "redux-form";

import { Modal } from "shared/view/elements";
import { IPreset } from "shared/types/models";
import { IAppReduxState } from "shared/types/app";
import { actions as notificationActions } from "services/notification";
import { selectors as configSelectors } from "services/config";

import { actions, selectors } from "../../../redux";
import { managePresetsFormEntry } from "../../../redux/reduxFormEntries";
import { Presets } from "../../components/ManagePresets";
import "./ManagePresets.scss";

Также, для любого поддерева элемента пути не должно быть такого поддерева, импорты которого разделяются другим поддеревом такой же глубины.

Например:

  • Импорты поддерева shared (глубина 1) разделяются импортом поддерева services.

    import { IModel } from "shared/types/models";
    import { i18nConnect } from "services/i18n";
    import { Component } from "shared/view/components";
    

    Правильно будет так:

    import { IModel } from "shared/types/models";
    import { Component } from "shared/view/components";
    import { i18nConnect } from "services/i18n";
    

    Или в:

    import { IAppReduxState } from "shared/types/app";
    import { Component } from "shared/view/components";
    import { IModel } from "shared/types/models";
    import { i18nConnect } from "services/i18n";
    
  • импорты поддерева shared/types (глубина 2) разделяются импортом поддерева shared/view. Правильно будет так:

    import { IAppReduxState } from "shared/types/app";
    import { IModel } from "shared/types/models";
    import { Component } from "shared/view/components";
    import { i18nConnect } from "services/i18n";
    

# Экспорты держать в конце файла

Плохо:

export class Foo {
  // ...
}
export class Bar {
  // ...
}
// finita la comedia

Хорошо:

class Foo {
  // ...
}
class Bar {
  // ...
}
export { Foo, Bar };

# Если есть сомнения, поддерживает ли браузер какую-либо фичу

  • Проверять непосредственно определена ли эта функция, а не сравнивать user-agent с названиями и версиями браузеров, которые по документации это поддерживают. А по хорошему вообще использовать Modernizr (opens new window)

# Избегать глобальных переменных

Почти в любом языке глобальные переменные - источник зла. В JavaScript это правило не исключение.

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

Чаще всего пользоваться глобальными переменными можно только потому что, так требуют олдскульные библиотеки, например Google Maps, которая создает глобальный объект google и карты можно кастомизировать только через него, и которая для колбэка при своей инициации просит создать глобальную функцию, которую Google Maps потом сама вызовет.

Можно на первых стадиях совсем простых проектов, где чуть-чуть анимаций на jquery использовать одну (и только одну) глобальную переменную, чтобы сохранять в нее модули и иметь возможность доступа к функциям из других js-файлов (по умолчанию сам js этого не поддерживает).

# К глобальным переменным обращаться только как к свойствам window

Создавать объекты только как свойства window, потому что объявление без var неочевидно - так же происходят переопределения переменных из замыкания и сложно отличить эти моменты.

// тут правило нарушено, создается глобальная переменная без window, и обращение к ней тоже без window:
function test() {
  foo = "hello world"; // обратить внимание, что переменная создана без var, значит она глобальная
}
test();
console.log(foo); // вывод 'hello world', обращение тоже без window
// тут следуют описанная глобальная переменная, как надо:
function test() {
  window.foo = "hello world";
}
test();
console.log(window.foo); // вывод 'hello world', обращение как к >свойству объекта window

# Используйте специальные обертки для логирования, а не сырой console.log

  • У каждого лога должен быть префикс, который говорит из какого модуля вызван лог и в какое время он был вызван (таймстеймп);
  • Разделять логирование как минимум на два уровня - debug и production, чтобы в production сборке не выводились логи для разработчиков;
  • Все логи, которые ваша система производит, должны сохраняться в какую-либо единую переменную, чтобы в случае ошибок можно было сразу все отправить на сервер.

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

Это в первую очередь предназначено для ваших коллег - большие куски лишнего кода сбивают с толку, мешают поиску через ctrl+F, заставляют иногда отвлекаться на изучение кода. Так что лучше его скрыть в истории изменений.

# Все изменения в стилях, которые можно сделать через переключение классов у элемента, надо делать через добавление/удаление классов, а не простановку стилей у DOM-элементов

Например, можно переключать видимость элемента. Однако для непрерывно изменяющихся численных значений так сделать уже не получится, как например при изменении свойства top при скролле страницы, поэтому тут придётся все-таки проставлять напрямую у элемента значение в css-свойстве.**;

Это не только увеличит производительность (потому что за раз вы примените сразу все стили от нового класса), но и поможет потом переписать стили из css нормальным способом, потому что иначе стили будут инлайновые и из css их нельзя будет переопределить, кроме как через !important.

# Очень полезно понимать, как работают циклы перерисовки браузера и как можно проверить и увеличить производительность работы с DOM

Здесь в статье перечислены полезные источники (opens new window)

# Код должен быть без орфографических ошибок

Имена переменных, функций, комментарии должны быть написаны правильно

Обычно, чтобы избежать большинства ошибок, используют расширения для IDE, для vscode например есть хорошее расширение Code Spell Checker (opens new window).

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

В сафари если включен режим инкогнито или если в браузере стоит блокировка cookie, то браузер будет райзить ошибку. Поэтому если ее не отловить, приложение посыпется 😃

# jQuery

Все библиотеки со схожим API, где мы ищем элементы по селекторам и манипулируем ими как с js-переменными, например:

var $newMessage = $('<li class="js-message" />');
var $messageList = $body.find("ul.js-messages");
$messageList.append($newMessage);

# Все классы, которые используем для поиска по DOM-у должны начинаться с префикса js-

Например, есть кнопка, по клику открывающая popup - нам надо продублировать ее класс и добавить туда этот префикс:

<div class="open-popup-button js-open-popup-button"></div>

В итоге в js-файлах мы используем только второй класс:

 $('.js-open-popup-button').click(...);

Это поможет избежать неожиданной поломки скриптов при смене верстки и наоборот.

# Кэшировать все найденные элементы

Плохо:

$(".js-element").show();
$(".js-element")
  .find(".js-children")
  .doSomething();
$(".js-element").attr("data-id", 123);

Хорошо:

var $element = $(".js-element");
$element.show();
$element.find(".js-children").doSomething();
$element.attr("data-id", 123);

# Конкатенировать селекторы, если с элементами будет проводиться одна и та же работа

$("form p").addClass("valid");
$("form li").addClass("valid");
$("form span").addClass("valid");
// - это лучше заменить на это:
$("form p, form li, form span").addClass("valid");

# Все переменные, которые поддерживают jQuery API, должны начинаться со знака $

Например:

var $body = $("body");
var $lists = $body.find("ul");

А тут уже не jQuery API и переменные не должны начинаться с $

var chatBox = document.getElementById("chat-box"); // потому что это нативной метод, который тоже возвращает нативной DOM-элемент
var firstList = $lists[0]; // потому что изъятие по индексу возвращает уже нативной DOM-элемент

# Обработчики событий всегда именовать

// Надо не просто делать
$(window).scroll(...);
// а
$(window).on('scroll.myComponent', function (event) {...});

Это позволит легко отсоединять обработчики, когда нужда в них отпадет.

# Поддерживать модульность

Так как идеология jquery не несет никаких знаний по тому, как организовывать более-менее сложные приложения (когда больше 3-х элементов на странице), то приходится все придумывать самим. Главные принципы, которым лучше следовать, чтобы код был чистый, а архитектура предсказуемой и понятной для других участников команды:

  • делить приложение на смысловые блоки. Все, что смотрится, как отдельный независимый элемент должно быть вынесено в отдельный блок. Весь js-код по нему должен быть в отдельном файле и строго отделен от остального приложения. У этого кода должен быть четкий и понятный интерфейс, который бы позволял внешнему миру общаться с блоком;
  • гарантировать независимость двух одинаковых блоков на одной странице друг от друга. То есть, если на странице два слайдера, то заранее на архитектурном уровне побеспокоиться, чтобы никакие события первого слайдера не влияли на второй;
  • Предыдущего условия (независимости однородных блоков) можно достичь, если js-код каждого блока обрамлять в класс, а затем для каждого встречного html-блока в верстке создавать по инстансу класса. В инстанс класса тогда можно сохранять сам DOM-элемент и потом только через него искать все вложенные элементы и только на них вешать обработчики;
  • Выше было требование, что все обработчики должны быть отименованы. Тут это принимает еще более строгий характер — имена обработчиков для разных инстансов одного и того же класса должны быть уникальными, чтобы $(block).off(...) отключал события только для текущего инстанса блока, не для всех инстансов, а то неожиданно для разработчика сразу будут отключены обработчики события для всех слайдеров на странице;
  • Также классы позволяют сделать структуру всех блоков одинаковой и предсказуемой — зная, как устроен блок slider, уже можно предсказать, как устроены блоки calendar и dropdown. У всех у них будут методы инициализации и поиска вложенных DOM-элементов, у всех будет код, который навешивает события, у всех будет интерфейс по удалению инстанса и тд. Так что лучше сразу позаботиться об одинаковой структуре и все максимально стандартизировать.

# React

Читать и применять только если уже знакомы с React, в противном случае можно пока игнорить

# Все компоненты должны быть по умолчанию функциональными

То есть stateless (что это такое? (opens new window)), и только при необходимости становиться class-based component

# Метод render() должен быть чистым

(Читаем доки (opens new window))

# Избегайте создания методов-рендеров в компонентах-классах

Речь не о методе render, а о методах, которые вы можете написать помимо него, и которые тоже возвращают JSX

Так как эти методы возвращают куски JSX, которые не являются полноценными react-unit объектами, у вас могут возникнуть проблемы с сопровождением этих компонент в дальнейшем.

И потом - с неправильно вынесенным jsx не удается работать как с полноценным react-unit, проблемы с рефами и отслеживаемостью в ДОМ

Если вы хотите вынести JSX — выносите его в stateless компоненту.

Например:

export class OrderStatus extends Component {
  renderStatusView(status) {
    // этот рендер должен быть вынесен в отдельную компоненту
    if (status === "succeed") {
      return <span>Done!</span>;
    }
    return <span>Active...</span>;
  }

  render() {
    return (
      <div>
        Order status: {this.renderStatusView(this.props.status)}// Здесь должна
        использоваться компонента а не метод
      </div>
    );
  }
}

# Метод render() и все методы, возвращающие куски DOM-a, должны располагаться в конце компоненты, сам метод render самым последним из публичных

# Принципы работы с action creators (redux)

  • Следует избегать лишнего getState обращения в action creator, кроме специфических случаев (например, когда вьюха никак не может получить достаточное количество данных). Данные следует отправлять в action creator явно аргументами, чтобы явно указать, с какими данными функция работает и что ей необходимо для корректной работы;
  • Декомпозиция сложного экшна (состоящего из нескольких структурных, алгоритмических экшнов) должна проводиться в присутствии старшего разработчика. то есть если напрашивается множественный dispatch в одном action creator'e, следует обсудить этот момент со старшим разработчиком, т.к. бывают случаи, когда этого можно избежать, а бывают - когда не удается;
  • Следует вызывать action creators только в контейнерах, в ответ на более низкоуровневые действия глупых компонентов: к примеру registerUser action следует вызывать в ответ на OnSubmit событие компонента формы регистрации.

# Архитектура

# Модульность - наше все

  • Обязательно дробить приложение на разные модули, даже если сайт - просто статичный контент с несколькими обработчиками событий, которые вешаются через jquery.
  • Для импорта модулей друг в друга использовать webpack, если проект совсем простой, можно обойтись созданием одного (!) глобального объекта и у него свойствами сохранять разные модули.
  • Для каждого модуля - только один файл. Без использования webpack и других сборщиков сами заботьтесь о том, чтобы не загрязнять глобальную область видимости (оборачивать все содержимое каждого файла в самовызывающуюся функцию).
  • В рамках одного модуля описывать только схожий функционал, который объединен смыслом. Это называется связность (opens new window). Обратная сторона медали - зацепление (opens new window), когда модули друг от друга сильно зависят - этого как раз надо избегать. То есть общий принцип - модули надо разбить так, чтобы внутри каждого модуля была максимальная схожесть области работы, а между модулями было максимальное различие. Например, в статистике сегмантация выполняется по таким же принципам, все группы должны состоять из максимально похожих элементов и при этом максимально отличаться друг от друга 😃.

# В рамках модуля четко делить на слои и не мешать логику работы с DOM и работу с данными (например, ajax-запросы)

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

Желательно для каждого приложения более менее формализовать структуру модуля, описать ее в конкретном виде для всех проектов невозможно, так как что-то пишется на React, что-то на React+Redux, а что-то на простом jquery. Для последнего этот пункт наиболее важен, но при этом наиболее неопределен.

# Избегать мутаций одной переменной сразу в нескольких функциях

Пример плохого кода:

var box = {};

function addBall(box) {
  box.ball = { radius: 2 };
}

function addFood(box) {
  box.food = { carrot: 4 };
}

function addShoes(box) {
  box.shoes = { sneakers: 2 };
}

addBall(box);
addFood(box);
addShoes(box);

Из-за таких действий область использования переменной становится слишком большой и тяжело понять, какая именно функция и когда изменила переменную. Когда произойдет ошибка в 10-ой функции, будет непонятно почему box содержит 5 пар кроссовок и 15 морковок.

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

# В js нет публичных и частных свойств, поэтому частные методы просто делать с префиксами "_" (нижнее подчеркивание)

class Person {
  constructor(name) {
    this.name = name;
  }

  public() {
    console.log("Call me from the outer modules");
  }

  _private() {
    console.log(
      "Call me only from the class Person methods, for example from public"
    );
  }
}

# При написании класса все публичные методы объявляйте первыми

Причем самым первым должен идти конструктор, все приватные методы группируйте по смыслу внизу класса

# Производительность

# В проектах на jquery или чем-либо, где поиск DOM-элементов идет по селекторам - надо кэшировать все найденные элементы

 $foodItem = $('#shopping-list li');

 $foodItem.click(function() {
   ... do something ...
 });

# Все операции с DOM-ом лучше сначала проводить на виртуальных элементах, а потом сразу целиком вставлять в документ страницы

 for (var i=0; i < items.length; i++){
   var item = document.createElement("li");
   item.appendChild(document.createTextNode("Option " + i);
   list.appendChild(item);
 }

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

 var fragment = document.createDocumentFragment();
 for (var i=0; i < items.length; i++){
   var item = document.createElement("li");
   item.appendChild(document.createTextNode("Option " + i);
   fragment.appendChild(item);
 }
 list.appendChild(fragment);

здесь мы в цикле вносим элемент в виртуальный элемент, а затем уже целиком за раз это внедряем в реальный DOM и получаем один цикл перерисовки

# Избегать сложных селекторов

В зависимости от типа и сложности css-селектора зависит производительность рендеринга и анимации сайта. Например использование в transition селектора по атрибуту (например input[type="text"] { transform: translateX(50%) } теоретически может замедлить саму анимацию. Те же правила экстраполируются на всю страницу. Чем сложнее css-селекторы (много вложенности и нетривиальных селекторов с необходимостью часто и много считать приоритеты), тем сложнее браузеру рендерить страницу, т.к. приходится применять стили согласованно между друг другом и анализировать DOM-дерево, что, конечно, не самая простая задача с точки зрения производительности сама по себе.

# Все js-ки подключаем внизу страницы перед закрывающимся тегом <body>

Полезные материалы:

# Безопасность