Все обязательные пункты технического задания выполнены
При открытии диалогов, загрузки данных и работе с сайтом не возникает ошибок, программа не ломается и не зависает
Название переменных, параметров, свойств и методов начинается со строчной буквы и записываются в нотации camelcase
Сокращения в словах запрещены. Сокращённые названия переменных можно использовать только, если такое название широко распространено. Допустимые сокращения:
xhr
, для объектовXMLHttpRequest
evt
для объектовEvent
и его производных (MouseEvent
,KeyboardEvent
и подобные)ctx
для контекста канвасаi
,j
,k
,l
,t
для счётчика в цикле,j
для счётчика во вложенном цикле и так далее по алфавиту- если циклов два и более, то можно не переиспользовать переменную
i
cb
для единственного коллбэка в параметрах функции
Слова разделяются подчёркиваниями (UPPER_SNAKE_CASE
), например:
const MAX_HEIGHT = 400;
const EARTH_RADIUS = 6370;
Названия функций не являющихся конструкторами должны начинаться со строчной буквы
Неправильно:
class wizard {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Run {
constructor() {
console.log(`О, я бегу!`);
}
}
Правильно:
class Wizard {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Runner {
constructor() {
console.log(`О, я бегун!`);
}
}
Перечисления начинаются с прописной (заглавной) буквы. Перечисления названы существительными в единственном числе. Значения перечислений объявлены как константы
Неправильно:
const view = {
artist: Artist,
genre: Genre,
};
const EndGameType = {
lives: `lives`,
quests: `quests`,
};
Правильно:
const View = {
ARTIST: Artist,
GENRE: Genre,
};
const EndGameType = {
LIVES: `lives`,
QUESTS: `quests`,
};
Неправильно:
const age = [12, 40, 22, 7];
const name = [`Иван`, `Петр`, `Мария`, `Алексей`];
const wizard = {
name: `Гендальф`,
friend: [`Саурон`, `Фродо`, `Бильбо`]
};
Правильно:
const ages = [12, 40, 22, 7];
const names = [`Иван`, `Петр`, `Мария`, `Алексей`];
const wizard = {
name: `Гендальф`,
friends: [`Саурон`, `Фродо`, `Бильбо`]
};
Название функции/метода должно быть глаголом и соответствовать действию, которое выполняет функция/метод. Например, можно использовать глагол get
для функций/методов, которые что-то возвращают
Исключения:
- Функции конструкторы (см. критерий
Конструкторы названы английскими существительными
) - Функции обработчики/коллбэки (см. критерий
Из названия обработчика события и функции-коллбэка следует, что это обработчик
)
Неправильно:
const function1 = (names) => {
names.forEach((name) => {
console.log(name);
});
};
const wizard = {
name: `Гендальф`,
action() {
console.log(`Стреляю файрболлом!`);
}
};
const randomNumber = () => {
return Math.random();
};
Правильно:
const printNames = (names) => {
names.forEach((name) => {
console.log(name);
});
};
const wizard = {
name: `Гендальф`,
fire() {
console.log(`Стреляю файрболлом!`);
}
};
const getRandomNumber = () => {
return Math.random();
};
Для того, чтобы избежать конфликтов имён в разных операционных системах, лучше применять наименее конфликтный способ именования файлов — строчными (маленькими) буквами через дефис
При объявлении новых значений, предпочтение стоит отдавать использованию ключевого слова const
. Использовать let
нужно только в том случае, если значение будет перезаписано
Неправильно:
let a = 1;
let b = 2;
let sum = a + b;
Правильно:
const a = 1;
const b = 2;
const sum = a + b;
for (let i = 0; i < 42; i++) {
console.log(i);
}
Неправильно:
let level = getLevel(this.state.level, this.quest);
let answerNames = Object.keys(level.answers);
let answers = answerNames.map((key) => ({key, value: level.answers[key]}));
Правильно:
const level = getLevel(this.state.level, this.quest);
const answerNames = Object.keys(level.answers);
const answers = answerNames.map((key) => ({key, value: level.answers[key]}));
В любых конструкциях, где подразумевается использование блока кода (фигурных скобок), таких как for
, while
, if
, switch
, function
— блок кода используется обязательно, даже если инструкция состоит из одной строчки
Неправильно:
(() => {
if (x % 2 === 1) return;
})();
Правильно:
(() => {
if (x % 2 === 1) {
return;
}
})();
Исключения составляют однострочные стрелочные функции, которые можно использовать без обязательных блоков кода:
const checkedCheckBoxes = checkboxes.filter((checkbox) => checkbox.checked);
Все константы выносятся в начало модуля/файла
- Отступы между операторами и ключевым словами соответствуют стайлгайду.
- Для отступов используются одинаковые символы, вложенность кода обозначается отступами.
- Однообразно расставлены пробелы перед, после и внутри скобок, операторов и ключевых слов
Не возникает ошибок при проверке проекта ESLint: npm i && npm test
Множества однотипных констант собираются в перечисления.
Неправильно:
const EARTH_WEIGHT = 5.972 * Math.pow(10, 24);
const EARTH_GRAVITY = 9.8;
const EARTH_RADIUS = 6370;
Правильно:
const Earth = {
WEIGHT: 5.972 * Math.pow(10, 24),
GRAVITY: 9.8,
RADIUS: 6370
};
Для итерирования по массивам и структурам данных по которому можно итерироваться (Iterable) используется конструкция for .. of
Там где не требуется индекс элемента массива или нужно обойти все элементы итерируемой структуры данных, используется цикл for .. of
вместо цикла for
Неправильно:
for (let i = 0; i < levels.length; i++) {
const level = levels[i];
renderLevel(level);
}
Правильно:
for (const level of levels) {
renderLevel(level);
}
Названия методов, которые есть в классе, но не предназначены для внешнего использования начинаются с нижнего подчёркивания _
. Доступ к таким полям из вне класса запрещён
В итоговом коде проекта находятся только те файлы, которые были на момент создания репозитория, которые были получены в патчах и файлы, созданные по заданию
В коде проекта нет файлов, модулей и частей кода, которые не используются, включая, закомментированные участки кода
Нет файлов скриптов, которые являются «мёртвым кодом», который никогда не выполняется
В списках зависимостей в файле package.json
указаны точные версии используемых пакетов. Версия обязательно должна быть указана. Не допускается использование ^
, *
и ~
Например:
- Невыполнимые условия:
const happen = false;
if (happen) {
console.log(`This will not happen anyway!`);
}
(() => {
return;
console.log(`This will not happen!`);
})();
Константы и перечисления (enum
) используются только для чтения, и никогда не переопределяются на всем промежутке жизни программы
Вместо операторов нестрогого сравнения ==
и !=
, используются операторы строгого сравнения ===
, !==
. Таблицы истинности для JavaScript
Неправильно:
const foo = ``;
const bar = [];
if (foo == bar) {
destroy(world);
}
Правильно:
const foo = ``;
const bar = [];
if (foo === bar) {
destroy(world);
}
В названия переменных и свойств не включаются операторы и ключевые слова зарезервированные для будущих версий языка (например, class
, extends
).
Список всех зарезервированных слов можно найти тут
HTML-страница должна быть корректным W3C документом в любой момент работы программы. Т.о. шаблоны не должны конструировать некорректные HTML-фрагменты
Неправильно:
<div class="player-wrapper" src="${answer.src}"></div>
Правильно:
<div class="player-wrapper" data-src="${answer.src}"></div>
Передаются корректные значения, которые ожидаются по спецификации
Неправильно:
const isPressed = element.getAttribute(`aria-pressed`, false);
Правильно:
const isPressed = element.getAttribute(`aria-pressed`);
Встроенные методы массивов используются по назначению.
Неправильно:
let greet = `Привет `;
wizards.map((it) => {
greet += `, ${it.name}`;
});
console.log(`${greet}!`);
Правильно:
const greet = `Привет `;
const names = wizards.map((it) => it.name);
console.log(`${greet} ${names.join(`, `)}!`);
Например, некорректное сложение двух операндов как строк. Проблема приоритета конкатенации над сложением.
Неправильно:
new Date() + 1000;
Правильно:
+new Date() + 1000;
Некорректные проверки на существование с числами. Пример некорректной проверки на то, что переменная является числом:
const double = (value) => {
if (!value) {
return NaN;
}
return value * 2;
};
double(0);
double();
double(5);
Потенциально некорректная операция взятия целой части числа
Неправильно:
const minutesNumber = ~~(seconds / 60);
Правильно:
const minutesNumber = Math.trunc(seconds / 60);
Все файлы JS представляют собой отдельные модули ES2015
Экспорт и импорт значений производится через при помощи ключевых слов export
и import
. Сохранение в глобальную область видимости значений не допускается
Пример правильного модуля:
import {changeView} from '../util';
import WelcomeView from './welcome-view';
import App from '../main';
export default class Welcome {
constructor() {
this.view = new WelcomeView();
}
init() {
changeView(this.view);
this.view.onStart = () => {
App.showGame();
};
}
}
Модуль не должен экспортировать переменную значение которой может измениться в будущем Неправильно:
export let latestResult;
Правильно:
export const latestResult = loadLatestResult();
Разные логические части кода вынесены в отдельные файлы модулей.
Имя модуля должно соответствовать его содержимому. Например, если в модуле лежит класс GameView
, то и имя модуля должно быть game-view.js
При проверке этого критерия, необходимо удостовериться в правильной работе и отсутствии сообщений об ошибках в выполняемых скриптах в браузерах: Chrome, Firefox, Safari, Microsoft Edge.
Допустимое исключение в кроссбраузерности кода: валидация форм в Safari. Safari плохо поддерживает работу с валидацией, например, не показывает ошибку, если при отправке формы не введены данные в поле с атрибутом required, поэтому небольшие ошибки, связанные с валидацией в Safari можно проигнорировать. Тестирование необходимо проводить именно в последних версиях браузеров, которые предоставляют поставщики, а не те, которые установлены в данный момент на компьютере проверяющего. Важно: для пользователей Windows последняя версия браузера Safari — 5, а у всех остальных — 9, поэтому проводить тестирование на Windows не надо. IE не поддерживается, только Edge.
Приводит к неосознанному коду:
const elem = document.querySelector(`.test`);
const onElemClick = () => {
event.target.innerText = `you really need event`;
};
elem.addEventListener(`click`, oneElemClick);
Неправильно:
apartments.forEach((it, index) => {
if (index < 3) {
render(it);
}
});
Правильно:
for (let i = 0; i < Math.min(apartments.length, 3); i++) {
render(apartments[i]);
}
Конкатенация строк в шаблонных строках является антипаттерном, т.к. ухудшает читаемость шаблонной строки
Неправильно:
const page = `${header + `\n` + main + `\n` + footer}`;
Правильно:
const page = `${header}\n${main}\n${footer}`;
Если задачу можно решить за один проход по циклу, вместо нескольких она должна быть решена за один
Неправильно:
const wizardNames = source.
map((it) => it.wizard).
map((it) => it.name);
Правильно:
const wizardNames = source.map((it) => it.wizard.name);
Например, наполнение скопированного из шаблона элемента данными
Обработчики событий для виджетов добавляются только в момент появления виджета на странице и удаляются в момент их исчезновения.
Защита от memory-leak
Кол-во обработчиков подвешенных на глобальную область видимости не должно возрастать. Например, если подвешивается обработчик, который следит за перемещением курсора по экрану, то он должен подвешиваться и отвешиваться в нужный момент. В случае если обработчик на document
только подвешивается это может свидетельствовать о проблеме бесконечного создания обработчиков и потенциальной утечке памяти.
Защита от неправильного поведения интерфейса
Например, на странице может существовать попап, который скрывается по ESC
. Лучше для него гасить обработчик, если он не показан, потому что он может каким-то образом ломать поведение сайта — останавливать распространение, отменять поведение по умолчанию и т.д. Поэтому поведение должно быть явным — если в этот момент времени обработчики не нужны, их нужно удалить. Явное и предсказуемое поведение.
Запрещено использовать innerHTML
и подобные ему свойства и методы для вставки пользовательских строк (имён, фамилий и т.д.)
Защита от XSS-атак, а также изменения исходных данных, запутывание пользователя и прочее
Все обязательные и необязательные пункты технического задания выполнены
Неправильно:
const keks = {
name: `Кекс`
};
Правильно:
const cat = {
name: `Кекс`
};
Неправильно:
const popup = {
openPopup() {
console.log(`I will open popup`);
}
};
class Wizard {
constructor(name = `Пендальф`) {
this.wizardName = name;
}
}
Правильно
const popup = {
open() {
console.log(`I will open popup`);
}
};
class Wizard {
constructor(name = `Пендальф`) {
this.name = name;
}
}
Для единственного обработчика или функции можно использовать callback
или cb
. Для именования нескольких обработчиков внутри одного модуля используется on
или handler
и описание события. Название обработчика строится следующим образом:
on
+ (на каком элементе) + что случилось:
const onSidebarClick = () => {};
const onContentLoad = () => {};
const onResize = () => {};
- (на каком элементе) + что случилось +
Handler
:
const sidebarClickHandler = () => {};
const contentLoadHandler = () => {};
const resizeHandler = () => {};
При объявлении функций используются только стрелочные функции. Для объявления методов объектов используется специальный синтаксис для методов. Для объявления классов используется ключевое слово class
.
Смешение стилей в рамках проекта не допускается
Функция:
const getTheMeaningOfLive = () => {
return 42;
};
или
const getTheMeaningOfLive = function () {
return 42;
};
Метод:
const GOD = {
createWorld() {
return `Your world is ready!`;
}
};
Конструктор
class Planet {
constructor(weight, mass) {
this.weight = weight;
this.mass = mass;
}
}
Использование объявления функций через function
допускается, но не рекомендуется, т.к. все возможные случаи использования контекстных функций решаются при помощи синтаксиса для методов и классов.
Стиль именования переменных сохраняется во всех модулях, например:
- не следует мешать обработчики содержащие
Handler
иon
- если переменные, которые хранят DOM-элемент и содержат слово
Element
, то это правило работает везде.
Неправильно:
const popupMainElement = document.querySelector(`.popup`);
const sidebarNode = document.querySelector(`.sidebar`);
const similarContainer = popupMainElement.querySelector(`ul.similar`);
Правильно:
const popupMainElement = document.querySelector(`.popup`);
const sidebarElement = document.querySelector(`.sidebar`);
const similarContainerElement = popupMainElement.querySelector(`ul.similar`);
При использовании встроенного API, который поддерживает несколько вариантов использования, используется один способ
Если существуют несколько разных API позволяющих решить одну и ту же задачу, например поиск элемента по id в DOM-дереве, то в проекте используется только один из этих API.
Неправильно:
const popupMainElement = document.querySelector(`#popup`);
const sidebarElement = document.getElementById(`sidebar`);
const popupClassName = popupMainElement.getAttribute(`class`);
const sidebarClassName = sidebarElement.className;
Правильно:
const popupMainElement = document.querySelector(`#popup`);
const sidebarElement = document.querySelector(`#sidebar`);
const popupClassName = popupMainElement.getAttribute(`class`);
const sidebarClassName = sidebarElement.getAttribute(`class`);
или
const popupMainElement = document.getElementById(`popup`);
const sidebarElement = document.getElementById(`sidebar`);
const popupClassName = popupMainElement.className;
const sidebarClassName = sidebarElement.className;
Во всех классах методы упорядочены следующим образом:
- Конструктор
- Геттеры/сеттеры свойств объекта
- Основные методы объекта:
- Методы объекта
- Приватные методы
- Перегруженные методы родительских объектов
- Обработчики событий
- Статические методы
Сортировка основных методов объекта свободная, подразумевается что методы будут расположены оптимально для конкретного класса нет смысла ограничивать порядок, потому что он может меняться в зависимости от особенностей объекта.
В случае, если одинаковый код повторяется в нескольких модулях, повторяющаяся часть вынесена в отдельный модуль
Критерий касается структурных единиц кода — повторяющийся блок кода, либо функции с одним и теми же конструкциями, например, утилитные методы для работы с DOM:
export const createElement = (template) => {
const outer = document.createElement(`div`);
outer.innerHTML = template;
return outer;
};
const main = document.getElementById(`main`);
export const changeView = (view) => {
main.innerHTML = ``;
main.appendChild(view.element);
};
Не стоит выносить в отдельный модуль одну повторяющуюся инструкцию:
export const createElement = (template) => {
const outer = document.createElement(`div`);
outer.innerHTML = template;
return outer;
};
Если метод не использует поля класса в котором он объявлен, то этот метод должен быть объявлен как статический:
Неправильно:
class App {
showWelcome() {
location.hash = ControllerID.WELCOME;
}
showGame() {
location.hash = ControllerID.GAME;
}
showScores() {
location.hash = ControllerID.SCOREBOARD;
}
}
Правильно:
class App {
static showWelcome() {
location.hash = ControllerID.WELCOME;
}
static showGame() {
location.hash = ControllerID.GAME;
}
static showScores() {
location.hash = ControllerID.SCOREBOARD;
}
}
Например, если заранее известно, что функция всегда принимает числовой параметр, то не следует проверять его на существование
Неправильно:
const isPositiveNumber = (myNumber) => {
if (typeof myNumber === `undefined`) {
throw new Error(`Parameter is not defined`);
}
return myNumber > 0;
};
isPositiveNumber(15);
isPositiveNumber(-30);
Правильно:
const isPositiveNumber = (myNumber) => {
return myNumber > 0;
};
isPositiveNumber(15);
isPositiveNumber(-30);
Отсутствует дублирование кода: повторяющиеся части кода переписаны как функции или вынесены из условий
При написании кода следует придерживаться принципа DRY
Неправильно:
if (this.level >= 10) {
this.timer.stopTimer();
this.timer.stopTimeout();
this.setResult();
removeTimer();
} else if (this.lives <= 0) {
this.timer.stopTimer();
this.timer.stopTimeout();
app.showResultFail();
removeTimer();
}
Правильно:
this.timer.stopTimer();
this.timer.stopTimeout();
if (this.level >= 10) {
this.setResult();
} else if (this.lives <= 0) {
app.showResultFail();
}
removeTimer();
Если при использовании условного оператора в любом случае возвращается значение, альтернативная ветка опускается
Неправильно:
((val, anotherVal) => {
if (2 > 1) {
return val;
} else {
return anotherVal;
}
});
Правильно:
((val, anotherVal) => {
if (2 > 1) {
return val;
}
return anotherVal;
});
Если заранее известно что в переменной число, то нет смысла превращать переменную в число parseInt(myNumber)
. Тоже касается и избыточной проверки булевой переменной
Неправильно:
if (booleanValue === true) {
console.log(`It\`s true!`);
}
Правильно:
if (booleanValue) {
console.log(`It\`s true!`);
}
Неправильно:
let sex;
if (male) {
sex = `Мужчина`;
} else {
sex = `Женщина`;
}
Правильно:
const sex = male ? `Мужчина` : `Женщина`;
Если функция возвращает булево значение, не используется if..else
с лишними return
Неправильно:
((firstValue, secondValue) => {
if (firstValue === secondValue) {
return true;
} else {
return false;
}
});
Правильно:
((firstValue, secondValue) => {
return firstValue === secondValue;
});
В коде не используются «магические значения», под каждое из них заведена отдельная переменная, названная как константа
Если состояние переменной можно сохранить в переменную или во внутренне свойство, то лучше использовать внутреннее состояние объекта, вместо сериализации из значения в строку и наоборот
Неправильно:
class Timer {
setTime({minutes, seconds}) {
document.querySelector(`.timer-value-mins`).textContent = minutes;
document.querySelector(`.timer-value-secs`).textContent = seconds;
}
getTime() {
const minutes = parseInt(document.querySelector(`.timer-value-mins`).textContent, 10);
const seconds = parseInt(document.querySelector(`.timer-value-secs`).textContent, 10);
return {minutes, seconds};
}
}
Правильно:
class Timer {
constructor(time) {
this.minutesEl = document.querySelector(`.timer-value-mins`);
this.secondsEl = document.querySelector(`.timer-value-secs`);
this.time = time;
}
update() {
this.minutesEl.textContent = this.time.minutes;
this.secondsEl.textContent = this.time.seconds;
}
get time() {
return this.myTime;
}
set time(time) {
this.myTime = time;
this.update();
}
}
Константы, используемые внутри функций создаются вне функций и используются повторно через замыкания
Поиск элементов по селекторам делается минимальное количество раз, после этого ссылки на элементы сохраняются
Неправильно:
for (let i = 0; i < Math.min(apartments.length, 3); i++) {
const dialog = document.querySelector(`.dialog`);
render(dialog, apartments[i]);
}
Правильно:
const dialog = document.querySelector(`.dialog`);
for (let i = 0; i < Math.min(apartments.length, 3); i++) {
render(dialog, apartments[i]);
}
Массивы и объекты, содержимое которых вычисляется, собираются один раз, а после этого только переиспользуются
Например, при удалении классов с DOM-элемента, не производится попытка удалить все возможные классы, если можно убрать лишь тот, который действительно установлен на DOM-элементе в данный момент
Неправильно:
const imageContainer = document.querySelector(`.image-container`);
const changeFilter = (filterName) => {
imageContainer.classList.remove(`filter-chrome`, `filter-sepia`, `filter-marvin`, `filter-phobos`, `filter-heat`);
imageContainer.classList.add(filterName);
};
Правильно:
const imageContainer = document.querySelector(`.image-container`);
let currentFilter;
const changeFilter = (filterName) => {
if (currentFilter) {
imageContainer.classList.remove(currentFilter);
}
imageContainer.classList.add(filterName);
currentFilter = filterName;
};
Одна функция не является обработчиком нескольких разных событий
Итераторы используются для трансформаций массивов — map
, filter
, sort
и прочие. А также для обхода проблемы потери окружения в циклах — forEach
Например:
elements.forEach((el) => {
el.onclick = () => {
console.log(el);
};
});
Неправильно:
imgGenerate(picArray = JSON.parse(data));
Правильно:
picArray = JSON.parse(data);
imgGenerate(picArray);
Все операции над элементами DOM-дерева происходят только там, где эти элементы были созданы и не используются снаружи. Например, всё что связано с отрисовкой данных должно находиться внутри класса View
и управляться только внутри этого класса, любой доступ к закрытым данным снаружи запрещён
Неправильно:
class PlayerController {
constructor(view) {
this.view = view;
}
init() {
const checkboxes = Array.from(this.view.element.querySelectorAll(`input`));
const answers = [];
this.view.makeDecision = () => {
answers.push(checkboxes.filter((it) => it.checked));
if (answers.length > 0) {
goToNextScreen();
}
};
}
}
Правильно:
class PlayerController {
constructor(view) {
this.view = view;
}
init() {
this.view.onAnswer = (answers) => {
if (answers.length > 0) {
goToNextScreen();
}
};
}
}