# Руководство по написанию JavaScript-кода от [Airbnb](https://github.com/airbnb/javascript/)() { *Наиболее разумный подход к написанию JavaScript-кода* > **Замечание**: это руководство подразумевает использование [Babel](https://babeljs.io) вместе с [babel-preset-airbnb](https://npmjs.com/babel-preset-airbnb) или аналогом. Оно также предполагает установленный shims/polyfills в вашем приложении, такой как [airbnb-browser-shims](https://npmjs.com/airbnb-browser-shims) или аналог. ## Оглавление 1. [Типы](#types) 1. [Объявление переменных](#references) 1. [Объекты](#objects) 1. [Массивы](#arrays) 1. [Деструктуризация](#destructuring) 1. [Строки](#strings) 1. [Функции](#functions) 1. [Стрелочные функции](#arrow-functions) 1. [Классы и конструкторы](#classes--constructors) 1. [Модули](#modules) 1. [Итераторы и генераторы](#iterators-and-generators) 1. [Свойства](#properties) 1. [Переменные](#variables) 1. [Подъём](#hoisting) 1. [Операторы сравнения и равенства](#comparison-operators--equality) 1. [Блоки](#blocks) 1. [Управляющие операторы](#control-statements) 1. [Комментарии](#comments) 1. [Пробелы](#whitespace) 1. [Запятые](#commas) 1. [Точка с запятой](#semicolons) 1. [Приведение типов](#type-casting--coercion) 1. [Соглашение об именовании](#naming-conventions) 1. [Аксессоры](#accessors) ## <a name="types">Типы</a> <a name="types--primitives"></a><a name="1.1"></a> - [1.1](#types--primitives) **Простые типы**: Когда вы взаимодействуете с простым типом, вы напрямую работаете с его значением. - `string` - `number` - `boolean` - `null` - `undefined` - `symbol` ```javascript const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9 ``` - Символы не могут быть полностью заполифиллены, поэтому они не должны использоваться, если разработка ведётся для браузеров/сред, которые не поддерживают их нативно. <a name="types--complex"></a><a name="1.2"></a> - [1.2](#types--complex) **Сложные типы**: Когда вы взаимодействуете со сложным типом, вы работаете со ссылкой на его значение. - `object` - `array` - `function` ```javascript const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9 ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="references">Объявление переменных</a> <a name="references--prefer-const"></a><a name="2.1"></a> - [2.1](#references--prefer-const) Используйте `const` для объявления переменных; избегайте `var`. eslint: [`prefer-const`](https://eslint.org/docs/rules/prefer-const.html), [`no-const-assign`](https://eslint.org/docs/rules/no-const-assign.html) > Почему? Это гарантирует, что вы не сможете переопределять значения, т.к. это может привести к ошибкам и к усложнению понимания кода. ```javascript // плохо var a = 1; var b = 2; // хорошо const a = 1; const b = 2; ``` <a name="references--disallow-var"></a><a name="2.2"></a> - [2.2](#references--disallow-var) Если вам необходимо переопределять значения, то используйте `let` вместо `var`. eslint: [`no-var`](https://eslint.org/docs/rules/no-var.html) > Почему? Область видимости `let` — блок, у `var` — функция. ```javascript // плохо var count = 1; if (true) { count += 1; } // хорошо, используйте let. let count = 1; if (true) { count += 1; } ``` <a name="references--block-scope"></a><a name="2.3"></a> - [2.3](#references--block-scope) Помните, что у `let` и `const` блочная область видимости. ```javascript // const и let существуют только в том блоке, в котором они определены. { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="objects">Объекты</a> <a name="objects--no-new"></a><a name="3.1"></a> - [3.1](#objects--no-new) Для создания объекта используйте литеральную нотацию. eslint: [`no-new-object`](https://eslint.org/docs/rules/no-new-object.html) ```javascript // плохо const item = new Object(); // хорошо const item = {}; ``` <a name="es6-computed-properties"></a><a name="3.4"></a> - [3.2](#es6-computed-properties) Используйте вычисляемые имена свойств, когда создаёте объекты с динамическими именами свойств. > Почему? Они позволяют вам определить все свойства объекта в одном месте. ```javascript function getKey(k) { return `a key named ${k}`; } // плохо const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // хорошо const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, }; ``` <a name="es6-object-shorthand"></a><a name="3.5"></a> - [3.3](#es6-object-shorthand) Используйте сокращённую запись метода объекта. eslint: [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand.html) ```javascript // плохо const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // хорошо const atom = { value: 1, addValue(value) { return atom.value + value; }, }; ``` <a name="es6-object-concise"></a><a name="3.6"></a> - [3.4](#es6-object-concise) Используйте сокращённую запись свойств объекта. eslint: [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand.html) > Почему? Это короче и понятнее. ```javascript const lukeSkywalker = 'Luke Skywalker'; // плохо const obj = { lukeSkywalker: lukeSkywalker, }; // хорошо const obj = { lukeSkywalker, }; ``` <a name="objects--grouped-shorthand"></a><a name="3.7"></a> - [3.5](#objects--grouped-shorthand) Группируйте ваши сокращённые записи свойств в начале объявления объекта. > Почему? Так легче сказать, какие свойства используют сокращённую запись. ```javascript const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // плохо const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // хорошо const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, }; ``` <a name="objects--quoted-props"></a><a name="3.8"></a> - [3.6](#objects--quoted-props) Только недопустимые идентификаторы помещаются в кавычки. eslint: [`quote-props`](https://eslint.org/docs/rules/quote-props.html) > Почему? На наш взгляд, такой код легче читать. Это улучшает подсветку синтаксиса, а также облегчает оптимизацию для многих JS-движков. ```javascript // плохо const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // хорошо const good = { foo: 3, bar: 4, 'data-blah': 5, }; ``` <a name="objects--prototype-builtins"></a> - [3.7](#objects--prototype-builtins) Не вызывайте напрямую методы `Object.prototype`, такие как `hasOwnProperty`, `propertyIsEnumerable`, и `isPrototypeOf`. eslint: [`no-prototype-builtins`](https://eslint.org/docs/rules/no-prototype-builtins) > Почему? Эти методы могут быть переопределены в свойствах объекта, который мы проверяем `{ hasOwnProperty: false }`, или этот объект может быть `null` (`Object.create(null)`). ```javascript // плохо console.log(object.hasOwnProperty(key)); // хорошо console.log(Object.prototype.hasOwnProperty.call(object, key)); // отлично const has = Object.prototype.hasOwnProperty; // Кэшируем запрос в рамках модуля. console.log(has.call(object, key)); /* или */ import has from 'has'; // https://www.npmjs.com/package/has console.log(has(object, key)); ``` <a name="objects--rest-spread"></a> - [3.8](#objects--rest-spread) Используйте оператор расширения вместо [`Object.assign`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) для поверхностного копирования объектов. Используйте синтаксис оставшихся свойств, чтобы получить новый объект с некоторыми опущенными свойствами. ```javascript // очень плохо const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // эта переменная изменяет `original` ಠ_ಠ delete copy.a; // если сделать так // плохо const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // хорошо const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } const { a, ...noA } = copy; // noA => { b: 2, c: 3 } ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="arrays">Массивы</a> <a name="arrays--literals"></a><a name="4.1"></a> - [4.1](#arrays--literals) Для создания массива используйте литеральную нотацию. eslint: [`no-array-constructor`](https://eslint.org/docs/rules/no-array-constructor.html) ```javascript // плохо const items = new Array(); // хорошо const items = []; ``` <a name="arrays--push"></a><a name="4.2"></a> - [4.2](#arrays--push) Для добавления элемента в массив используйте [Array#push](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/push) вместо прямого присваивания. ```javascript const someStack = []; // плохо someStack[someStack.length] = 'abracadabra'; // хорошо someStack.push('abracadabra'); ``` <a name="es6-array-spreads"></a><a name="4.3"></a> - [4.3](#es6-array-spreads) Для копирования массивов используйте оператор расширения `...`. ```javascript // плохо const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // хорошо const itemsCopy = [...items]; ``` <a name="arrays--from"></a> <a name="arrays--from-iterable"></a><a name="4.4"></a> - [4.4](#arrays--from-iterable) Для преобразования итерируемого объекта в массив используйте оператор расширения `...` вместо [`Array.from`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/from). ```javascript const foo = document.querySelectorAll('.foo'); // хорошо const nodes = Array.from(foo); // отлично const nodes = [...foo]; ``` <a name="arrays--from-array-like"></a> - [4.5](#arrays--from-array-like) Используйте [`Array.from`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/from) для преобразования массивоподобного объекта в массив. ```javascript const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // плохо const arr = Array.prototype.slice.call(arrLike); // хорошо const arr = Array.from(arrLike); ``` <a name="arrays--mapping"></a> - [4.6](#arrays--mapping) Используйте [`Array.from`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/from) вместо оператора расширения `...` для маппинга итерируемых объектов, это позволяет избежать создания промежуточного массива. ```javascript // плохо const baz = [...foo].map(bar); // хорошо const baz = Array.from(foo, bar); ``` <a name="arrays--callback-return"></a><a name="4.5"></a> - [4.7](#arrays--callback-return) Используйте операторы `return` внутри функций обратного вызова в методах массива. Можно опустить `return`, когда тело функции состоит из одной инструкции, возвращающей выражение без побочных эффектов. [8.2](#arrows--implicit-return). eslint: [`array-callback-return`](https://eslint.org/docs/rules/array-callback-return) ```javascript // хорошо [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // хорошо [1, 2, 3].map((x) => x + 1); // плохо - нет возвращаемого значения, следовательно, `acc` становится `undefined` после первой итерации [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); }); // хорошо [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); return flatten; }); // плохо inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // хорошо inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; }); ``` <a name="arrays--bracket-newline"></a> - [4.8](#arrays--bracket-newline) Если массив располагается на нескольких строках, то используйте разрывы строк после открытия и перед закрытием скобок. ```javascript // плохо const arr = [ [0, 1], [2, 3], [4, 5], ]; const objectInArray = [{ id: 1, }, { id: 2, }]; const numberInArray = [ 1, 2, ]; // хорошо const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ]; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="destructuring">Деструктуризация</a> <a name="destructuring--object"></a><a name="5.1"></a> - [5.1](#destructuring--object) При обращении к нескольким свойствам объекта используйте деструктуризацию объекта. eslint: [`prefer-destructuring`](https://eslint.org/docs/rules/prefer-destructuring) > Почему? Деструктуризация избавляет вас от создания временных переменных для этих свойств. ```javascript // плохо function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // хорошо function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // отлично function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; } ``` <a name="destructuring--array"></a><a name="5.2"></a> - [5.2](#destructuring--array) Используйте деструктуризацию массивов. eslint: [`prefer-destructuring`](https://eslint.org/docs/rules/prefer-destructuring) ```javascript const arr = [1, 2, 3, 4]; // плохо const first = arr[0]; const second = arr[1]; // хорошо const [first, second] = arr; ``` <a name="destructuring--object-over-array"></a><a name="5.3"></a> - [5.3](#destructuring--object-over-array) Используйте деструктуризацию объекта для множества возвращаемых значений, но не делайте тоже самое с массивами. > Почему? Вы сможете добавить новые свойства через некоторое время или изменить порядок без последствий. ```javascript // плохо function processInput(input) { // затем происходит чудо return [left, right, top, bottom]; } // при вызове нужно подумать о порядке возвращаемых данных const [left, __, top] = processInput(input); // хорошо function processInput(input) { // затем происходит чудо return { left, right, top, bottom }; } // при вызове выбираем только необходимые данные const { left, top } = processInput(input); ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="strings">Строки</a> <a name="strings--quotes"></a><a name="6.1"></a> - [6.1](#strings--quotes) Используйте одинарные кавычки `''` для строк. eslint: [`quotes`](https://eslint.org/docs/rules/quotes.html) ```javascript // плохо const name = "Capt. Janeway"; // плохо - литерал шаблонной строки должен содержать интерполяцию или переводы строк const name = `Capt. Janeway`; // хорошо const name = 'Capt. Janeway'; ``` <a name="strings--line-length"></a><a name="6.2"></a> - [6.2](#strings--line-length) Строки, у которых в строчке содержится более 100 символов, не пишутся на нескольких строчках с использованием конкатенации. > Почему? Работать с разбитыми строками неудобно и это затрудняет поиск по коду. ```javascript // плохо const errorMessage = 'This is a super long error that was thrown because \ of Batman. When you stop to think about how Batman had anything to do \ with this, you would get nowhere \ fast.'; // плохо const errorMessage = 'This is a super long error that was thrown because ' + 'of Batman. When you stop to think about how Batman had anything to do ' + 'with this, you would get nowhere fast.'; // хорошо const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; ``` <a name="es6-template-literals"></a><a name="6.4"></a> - [6.3](#es6-template-literals) При создании строки программным путём используйте шаблонные строки вместо конкатенации. eslint: [`prefer-template`](https://eslint.org/docs/rules/prefer-template.html) [`template-curly-spacing`](https://eslint.org/docs/rules/template-curly-spacing) > Почему? Шаблонные строки дают вам читабельность, лаконичный синтаксис с правильными символами перевода строк и функции интерполяции строки. ```javascript // плохо function sayHi(name) { return 'How are you, ' + name + '?'; } // плохо function sayHi(name) { return ['How are you, ', name, '?'].join(); } // плохо function sayHi(name) { return `How are you, ${ name }?`; } // хорошо function sayHi(name) { return `How are you, ${name}?`; } ``` <a name="strings--eval"></a><a name="6.5"></a> - [6.4](#strings--eval) Никогда не используйте `eval()`, т.к. это открывает множество уязвимостей. eslint: [`no-eval`](https://eslint.org/docs/rules/no-eval) <a name="strings--escaping"></a> - [6.5](#strings--escaping) Не используйте в строках необязательные экранирующие символы. eslint: [`no-useless-escape`](https://eslint.org/docs/rules/no-useless-escape) > Почему? Обратные слеши ухудшают читабельность, поэтому они должны быть только при необходимости. ```javascript // плохо const foo = '\'this\' \i\s \"quoted\"'; // хорошо const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="functions">Функции</a> <a name="functions--declarations"></a><a name="7.1"></a> - [7.1](#functions--declarations) Используйте функциональные выражения вместо объявлений функций. eslint: [`func-style`](https://eslint.org/docs/rules/func-style) > Почему? У объявлений функций есть подъём. Это означает, что можно использовать функцию до того, как она определена в файле, но это вредит читабельности и поддержке. Если вы обнаружили, что определение функции настолько большое или сложное, что мешает понимать остальную часть файла, то, возможно, пришло время извлечь его в отдельный модуль. Не забудьте явно назвать функциональное выражение, независимо от того, подразумевается ли имя из содержащейся переменной (такое часто бывает в современных браузерах или при использовании компиляторов, таких как Babel). Это помогает точнее определять место ошибки по стеку вызовов. ([Обсуждение](https://github.com/airbnb/javascript/issues/794)) ```javascript // плохо function foo() { // ... } // плохо const foo = function () { // ... }; // хорошо // лексическое имя, отличное от вызываемой(-ых) переменной(-ых) const foo = function uniqueMoreDescriptiveLexicalFoo() { // ... }; ``` <a name="functions--iife"></a><a name="7.2"></a> - [7.2](#functions--iife) Оборачивайте в скобки немедленно вызываемые функции. eslint: [`wrap-iife`](https://eslint.org/docs/rules/wrap-iife.html) > Почему? Немедленно вызываемая функция представляет собой единый блок. Чтобы чётко показать это — оберните функцию и вызывающие скобки в ещё одни скобки. Обратите внимание, что в мире с модулями вам больше не нужны немедленно вызываемые функции. ```javascript // Немедленно вызываемая функция (function () { console.log('Welcome to the Internet. Please follow me.'); }()); ``` <a name="functions--in-blocks"></a><a name="7.3"></a> - [7.3](#functions--in-blocks) Никогда не объявляйте функции в нефункциональном блоке (`if`, `while`, и т.д.). Вместо этого присвойте функцию переменной. Браузеры позволяют выполнить ваш код, но все они интерпретируют его по-разному. eslint: [`no-loop-func`](https://eslint.org/docs/rules/no-loop-func.html) <a name="functions--note-on-blocks"></a><a name="7.4"></a> - [7.4](#functions--note-on-blocks) **Примечание:** ECMA-262 определяет `блок` как список инструкций. Объявление функции не является инструкцией. ```javascript // плохо if (currentUser) { function test() { console.log('Nope.'); } } // хорошо let test; if (currentUser) { test = () => { console.log('Yup.'); }; } ``` <a name="functions--arguments-shadow"></a><a name="7.5"></a> - [7.5](#functions--arguments-shadow) Никогда не называйте параметр `arguments`. Он будет иметь приоритет над объектом `arguments`, который доступен для каждой функции. ```javascript // плохо function foo(name, options, arguments) { // ... } // хорошо function foo(name, options, args) { // ... } ``` <a name="es6-rest"></a><a name="7.6"></a> - [7.6](#es6-rest) Никогда не используйте `arguments`, вместо этого используйте синтаксис оставшихся параметров `...`. eslint: [`prefer-rest-params`](https://eslint.org/docs/rules/prefer-rest-params) > Почему? `...` явно говорит о том, какие именно аргументы вы хотите извлечь. Кроме того, такой синтаксис создаёт настоящий массив, а не массивоподобный объект как `arguments`. ```javascript // плохо function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // хорошо function concatenateAll(...args) { return args.join(''); } ``` <a name="es6-default-parameters"></a><a name="7.7"></a> - [7.7](#es6-default-parameters) Используйте синтаксис записи аргументов по умолчанию, а не изменяйте аргументы функции. ```javascript // очень плохо function handleThings(opts) { // Нет! Мы не должны изменять аргументы функции. // Плохо вдвойне: если переменная opts будет ложной, // то ей присвоится пустой объект, а не то что вы хотели. // Это приведёт к коварным ошибкам. opts = opts || {}; // ... } // всё ещё плохо function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // хорошо function handleThings(opts = {}) { // ... } ``` <a name="functions--default-side-effects"></a><a name="7.8"></a> - [7.8](#functions--default-side-effects) Избегайте побочных эффектов с параметрами по умолчанию. > Почему? И так всё понятно. ```javascript var b = 1; // плохо function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3 ``` <a name="functions--defaults-last"></a><a name="7.9"></a> - [7.9](#functions--defaults-last) Всегда вставляйте последними параметры по умолчанию. ```javascript // плохо function handleThings(opts = {}, name) { // ... } // хорошо function handleThings(name, opts = {}) { // ... } ``` <a name="functions--constructor"></a><a name="7.10"></a> - [7.10](#functions--constructor) Никогда не используйте конструктор функций для создания новых функий. eslint: [`no-new-func`](https://eslint.org/docs/rules/no-new-func) > Почему? Создание функции в таком духе вычисляет строку подобно `eval()`, из-за чего открываются уязвимости. ```javascript // плохо var add = new Function('a', 'b', 'return a + b'); // всё ещё плохо var subtract = Function('a', 'b', 'return a - b'); ``` <a name="functions--signature-spacing"></a><a name="7.11"></a> - [7.11](#functions--signature-spacing) Отступы при определении функции. eslint: [`space-before-function-paren`](https://eslint.org/docs/rules/space-before-function-paren) [`space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks) > Почему? Однородность кода — это хорошо. Вам не надо будет добавлять или удалять пробел при манипуляции с именем. ```javascript // плохо const f = function(){}; const g = function (){}; const h = function() {}; // хорошо const x = function () {}; const y = function a() {}; ``` <a name="functions--mutate-params"></a><a name="7.12"></a> - [7.12](#functions--mutate-params) Никогда не изменяйте параметры. eslint: [`no-param-reassign`](https://eslint.org/docs/rules/no-param-reassign.html) > Почему? Манипуляция объектами, приходящими в качестве параметров, может вызывать нежелательные побочные эффекты в вызывающей функции. ```javascript // плохо function f1(obj) { obj.key = 1; } // хорошо function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; } ``` <a name="functions--reassign-params"></a><a name="7.13"></a> - [7.13](#functions--reassign-params) Никогда не переназначайте параметры. eslint: [`no-param-reassign`](https://eslint.org/docs/rules/no-param-reassign.html) > Почему? Переназначенные параметры могут привести к неожиданному поведению, особенно при обращении к `arguments`. Это также может вызывать проблемы оптимизации, особенно в V8. ```javascript // плохо function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // хорошо function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... } ``` <a name="functions--spread-vs-apply"></a><a name="7.14"></a> - [7.14](#functions--spread-vs-apply) Отдавайте предпочтение использованию оператора расширения `...` при вызове вариативной функции. eslint: [`prefer-spread`](https://eslint.org/docs/rules/prefer-spread) > Почему? Это чище, вам не нужно предоставлять контекст, и не так просто составить `new` с `apply`. ```javascript // плохо const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // хорошо const x = [1, 2, 3, 4, 5]; console.log(...x); // плохо new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // хорошо new Date(...[2016, 8, 5]); ``` <a name="functions--signature-invocation-indentation"></a> - [7.15](#functions--signature-invocation-indentation) Функции с многострочным определением или запуском должны содержать такие же отступы, как и другие многострочные списки в этом руководстве: с каждым элементом на отдельной строке, с запятой в конце элемента. eslint: [`function-paren-newline`](https://eslint.org/docs/rules/function-paren-newline) ```javascript // плохо function foo(bar, baz, quux) { // ... } // хорошо function foo( bar, baz, quux, ) { // ... } // плохо console.log(foo, bar, baz); // хорошо console.log( foo, bar, baz, ); ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="arrow-functions">Стрелочные функции</a> <a name="arrows--use-them"></a><a name="8.1"></a> - [8.1](#arrows--use-them) Когда вам необходимо использовать анонимную функцию (например, при передаче встроенной функции обратного вызова), используйте стрелочную функцию. eslint: [`prefer-arrow-callback`](https://eslint.org/docs/rules/prefer-arrow-callback.html), [`arrow-spacing`](https://eslint.org/docs/rules/arrow-spacing.html) > Почему? Таким образом создаётся функция, которая выполняется в контексте `this`, который мы обычно хотим, а также это более короткий синтаксис. > Почему бы и нет? Если у вас есть довольно сложная функция, вы можете переместить эту логику внутрь её собственного именованного функционального выражения. ```javascript // плохо [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // хорошо [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); ``` <a name="arrows--implicit-return"></a><a name="8.2"></a> - [8.2](#arrows--implicit-return) Если тело функции состоит из одного оператора, возвращающего [выражение](https://developer.mozilla.org/ru/docs/Web/JavaScript/Guide/Expressions_and_Operators#Выражения) без побочных эффектов, то опустите фигурные скобки и используйте неявное возвращение. В противном случае, сохраните фигурные скобки и используйте оператор `return`. eslint: [`arrow-parens`](https://eslint.org/docs/rules/arrow-parens.html), [`arrow-body-style`](https://eslint.org/docs/rules/arrow-body-style.html) > Почему? Синтаксический сахар. Когда несколько функций соединены вместе, то это лучше читается. ```javascript // плохо [1, 2, 3].map((number) => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // хорошо [1, 2, 3].map((number) => `A string containing the ${number + 1}.`); // хорошо [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // хорошо [1, 2, 3].map((number, index) => ({ [index]: number, })); // Неявный возврат с побочными эффектами function foo(callback) { const val = callback(); if (val === true) { // Сделать что-то, если функция обратного вызова вернёт true } } let bool = false; // плохо foo(() => bool = true); // хорошо foo(() => { bool = true; }); ``` <a name="arrows--paren-wrap"></a><a name="8.3"></a> - [8.3](#arrows--paren-wrap) В случае, если выражение располагается на нескольких строках, то необходимо обернуть его в скобки для лучшей читаемости. > Почему? Это чётко показывает, где функция начинается и где заканчивается. ```javascript // плохо ['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // хорошо ['get', 'post', 'put'].map((httpMethod) => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) )); ``` <a name="arrows--one-arg-parens"></a><a name="8.4"></a> - [8.4](#arrows--one-arg-parens) Всегда оборачивайте аргументы круглыми скобками для ясности и согласованности. eslint: [`arrow-parens`](https://eslint.org/docs/rules/arrow-parens.html) > Почему? Минимизирует различия при удалении или добавлении аргументы. ```javascript // плохо [1, 2, 3].map(x => x * x); // хорошо [1, 2, 3].map((x) => x * x); // плохо [1, 2, 3].map(number => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // хорошо [1, 2, 3].map((number) => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // плохо [1, 2, 3].map(x => { const y = x + 1; return x * y; }); // хорошо [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); ``` <a name="arrows--confusing"></a><a name="8.5"></a> - [8.5](#arrows--confusing) Избегайте схожести стрелочной функции (`=>`) с операторами сравнения (`<=`, `>=`). eslint: [`no-confusing-arrow`](https://eslint.org/docs/rules/no-confusing-arrow) ```javascript // плохо const itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize; // плохо const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize; // хорошо const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize); // хорошо const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height <= 256 ? largeSize : smallSize; }; ``` <a name="whitespace--implicit-arrow-linebreak"></a> - [8.6](#whitespace--implicit-arrow-linebreak) Зафиксируйте расположение тела стрелочной функции с неявным возвратом. eslint: [`implicit-arrow-linebreak`](https://eslint.org/docs/rules/implicit-arrow-linebreak) ```javascript // плохо (foo) => bar; (foo) => (bar); // хорошо (foo) => bar; (foo) => (bar); (foo) => ( bar ) ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="classes--constructors">Классы и конструкторы</a> <a name="constructors--use-class"></a><a name="9.1"></a> - [9.1](#constructors--use-class) Всегда используйте `class`. Избегайте прямых манипуляций с `prototype`. > Почему? Синтаксис `class` является кратким и понятным. ```javascript // плохо function Queue(contents = []) { this.queue = [...contents]; } Queue.prototype.pop = function () { const value = this.queue[0]; this.queue.splice(0, 1); return value; }; // хорошо class Queue { constructor(contents = []) { this.queue = [...contents]; } pop() { const value = this.queue[0]; this.queue.splice(0, 1); return value; } } ``` <a name="constructors--extends"></a><a name="9.2"></a> - [9.2](#constructors--extends) Используйте `extends` для наследования. > Почему? Это встроенный способ наследовать функциональность прототипа, не нарушая `instanceof`. ```javascript // плохо const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function () { return this.queue[0]; }; // хорошо class PeekableQueue extends Queue { peek() { return this.queue[0]; } } ``` <a name="constructors--chaining"></a><a name="9.3"></a> - [9.3](#constructors--chaining) Методы могут возвращать `this`, чтобы делать цепочки вызовов. ```javascript // плохо Jedi.prototype.jump = function () { this.jumping = true; return true; }; Jedi.prototype.setHeight = function (height) { this.height = height; }; const luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // хорошо class Jedi { jump() { this.jumping = true; return this; } setHeight(height) { this.height = height; return this; } } const luke = new Jedi(); luke.jump() .setHeight(20); ``` <a name="constructors--tostring"></a><a name="9.4"></a> - [9.4](#constructors--tostring) Вы можете определить свой собственный метод `toString()`, просто убедитесь, что он успешно работает и не создаёт никаких побочных эффектов. ```javascript class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } } ``` <a name="constructors--no-useless"></a><a name="9.5"></a> - [9.5](#constructors--no-useless) У классов есть конструктор по умолчанию, если он не задан явно. Можно опустить пустой конструктор или конструктор, который только делегирует выполнение родительскому классу. eslint: [`no-useless-constructor`](https://eslint.org/docs/rules/no-useless-constructor) ```javascript // плохо class Jedi { constructor() {} getName() { return this.name; } } // плохо class Rey extends Jedi { constructor(...args) { super(...args); } } // хорошо class Rey extends Jedi { constructor(...args) { super(...args); this.name = 'Rey'; } } ``` <a name="classes--no-duplicate-members"></a> - [9.6](#classes--no-duplicate-members) Избегайте дублирующих членов класса. eslint: [`no-dupe-class-members`](https://eslint.org/docs/rules/no-dupe-class-members) > Почему? Если объявление члена класса повторяется, без предупреждения будет использовано последнее. Наличие дубликатов скорее всего приведёт к ошибке. ```javascript // плохо class Foo { bar() { return 1; } bar() { return 2; } } // хорошо class Foo { bar() { return 1; } } // хорошо class Foo { bar() { return 2; } } ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="modules">Модули</a> <a name="modules--use-them"></a><a name="10.1"></a> - [10.1](#modules--use-them) Всегда используйте модули (`import`/`export`) вместо нестандартных модульных систем. Вы всегда сможете транспилировать код в вашу любимую модульную систему. > Почему? Модули — это будущее. Давайте начнём использовать будущее уже сейчас! ```javascript // плохо const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // отлично import { es6 } from './AirbnbStyleGuide'; export default es6; ``` <a name="modules--no-wildcard"></a><a name="10.2"></a> - [10.2](#modules--no-wildcard) Не используйте импорт через `*`. > Почему? Это гарантирует, что у вас есть единственный экспорт по умолчанию. ```javascript // плохо import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // хорошо import AirbnbStyleGuide from './AirbnbStyleGuide'; ``` <a name="modules--no-export-from-import"></a><a name="10.3"></a> - [10.3](#modules--no-export-from-import) Не экспортируйте прямо из импорта. > Почему? Несмотря на то, что запись в одну строку является краткой, разделение на отдельные строки делает вещи последовательными. ```javascript // плохо // файл es6.js export { es6 as default } from './AirbnbStyleGuide'; // хорошо // файл es6.js import { es6 } from './AirbnbStyleGuide'; export default es6; ``` <a name="modules--no-duplicate-imports"></a> - [10.4](#modules--no-duplicate-imports) Импортируйте из пути только один раз. eslint: [`no-duplicate-imports`](https://eslint.org/docs/rules/no-duplicate-imports) > Почему? Наличие нескольких строк, которые импортируют из одного и того же файла, может сделать код неподдерживаемым. ```javascript // плохо import foo from 'foo'; // … какие-то другие импорты … // import { named1, named2 } from 'foo'; // хорошо import foo, { named1, named2 } from 'foo'; // хорошо import foo, { named1, named2, } from 'foo'; ``` <a name="modules--no-mutable-exports"></a> - [10.5](#modules--no-mutable-exports) Не экспортируйте изменяемые переменные. eslint: [`import/no-mutable-exports`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md) > Почему? Вообще, следует избегать мутации, в особенности при экспорте изменяемых переменных. Несмотря на то, что эта техника может быть необходима в редких случаях, в основном только константа должна быть экспортирована. ```javascript // плохо let foo = 3; export { foo }; // хорошо const foo = 3; export { foo }; ``` <a name="modules--prefer-default-export"></a> - [10.6](#modules--prefer-default-export) В модулях с единственным экспортом предпочтительнее использовать экспорт по умолчанию, а не экспорт по имени. eslint: [`import/prefer-default-export`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/prefer-default-export.md) > Почему? Для того чтобы поощрять создание множества файлов, которые бы экспортировали одну сущность, т.к. это лучше для читабельности и поддержки кода. ```javascript // плохо export function foo() {} // хорошо export default function foo() {} ``` <a name="modules--imports-first"></a> - [10.7](#modules--imports-first) Поместите все импорты выше остальных инструкций. eslint: [`import/first`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md) > Почему? Так как `import` обладает подъёмом, то хранение их всех в начале файла предотвращает от неожиданного поведения. ```javascript // плохо import foo from 'foo'; foo.init(); import bar from 'bar'; // хорошо import foo from 'foo'; import bar from 'bar'; foo.init(); ``` <a name="modules--multiline-imports-over-newlines"></a> - [10.8](#modules--multiline-imports-over-newlines) Импорты на нескольких строках должны быть с отступами как у многострочных литералов массива и объекта. > Почему? Фигурные скобки следуют тем же правилам отступа как и любая другая фигурная скобка блока в этом руководстве, тоже самое касается висячих запятых. ```javascript // плохо import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // хорошо import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path'; ``` <a name="modules--no-webpack-loader-syntax"></a> - [10.9](#modules--no-webpack-loader-syntax) Запретите синтаксис загрузчика Webpack в импорте. eslint: [`import/no-webpack-loader-syntax`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md) > Почему? Использование Webpack синтаксиса связывает код с упаковщиком модулей. Предпочтительно использовать синтаксис загрузчика в `webpack.config.js`. ```javascript // плохо import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // хорошо import fooSass from 'foo.scss'; import barCss from 'bar.css'; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="iterators-and-generators">Итераторы и генераторы</a> <a name="iterators--nope"></a><a name="11.1"></a> - [11.1](#iterators--nope) Не используйте итераторы. Применяйте функции высшего порядка вместо таких циклов как `for-in` или `for-of`. eslint: [`no-iterator`](https://eslint.org/docs/rules/no-iterator.html) [`no-restricted-syntax`](https://eslint.org/docs/rules/no-restricted-syntax) > Почему? Это обеспечивает соблюдение нашего правила о неизменности переменных. Работать с чистыми функциями, которые возвращают значение, проще, чем с функциями с побочными эффектами. > Используйте `map()` / `every()` / `filter()` / `find()` / `findIndex()` / `reduce()` / `some()` / ... для итерации по массивам, а `Object.keys()` / `Object.values()` / `Object.entries()` для создания массивов, с помощью которых можно итерироваться по объектам. ```javascript const numbers = [1, 2, 3, 4, 5]; // плохо let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // хорошо let sum = 0; numbers.forEach((num) => { sum += num; }); sum === 15; // отлично (используйте силу функций) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // плохо const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // хорошо const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // отлично (продолжайте в том же духе) const increasedByOne = numbers.map((num) => num + 1); ``` <a name="generators--nope"></a><a name="11.2"></a> - [11.2](#generators--nope) Не используйте пока генераторы. > Почему? Они не очень хорошо транспилируются в ES5. <a name="generators--spacing"></a> - [11.3](#generators--spacing) Если всё-таки необходимо использовать генераторы, или вы не обратили внимания на [наш совет](#generators--nope), убедитесь, что `*` у функции генератора расположена должным образом. eslint: [`generator-star-spacing`](https://eslint.org/docs/rules/generator-star-spacing) > Почему? `function` и `*` являются частью одного и того же ключевого слова. `*` не является модификатором для `function`, `function*` является уникальной конструкцией, отличной от `function`. ```javascript // плохо function * foo() { // ... } const bar = function * () { // ... }; const baz = function *() { // ... }; const quux = function*() { // ... }; function*foo() { // ... } function *foo() { // ... } // очень плохо function * foo() { // ... } const wat = function * () { // ... }; // хорошо function* foo() { // ... } const foo = function* () { // ... }; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="properties">Свойства</a> <a name="properties--dot"></a><a name="12.1"></a> - [12.1](#properties--dot) Используйте точечную нотацию для доступа к свойствам. eslint: [`dot-notation`](https://eslint.org/docs/rules/dot-notation.html) ```javascript const luke = { jedi: true, age: 28, }; // плохо const isJedi = luke['jedi']; // хорошо const isJedi = luke.jedi; ``` <a name="properties--bracket"></a><a name="12.2"></a> - [12.2](#properties--bracket) Используйте скобочную нотацию `[]`, когда название свойства хранится в переменной. ```javascript const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi'); ``` <a name="es2016-properties--exponentiation-operator"></a> - [12.3](#es2016-properties--exponentiation-operator) Используйте оператор `**` для возведения в степень. eslint: [`no-restricted-properties`](https://eslint.org/docs/rules/no-restricted-properties). ```javascript // плохо const binary = Math.pow(2, 10); // хорошо const binary = 2 ** 10; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="variables">Переменные</a> <a name="variables--const"></a><a name="13.1"></a> - [13.1](#variables--const) Всегда используйте `const` или `let` для объявления переменных. Невыполнение этого требования приведёт к появлению глобальных переменных. Необходимо избегать загрязнения глобального пространства имён. eslint: [`no-undef`](https://eslint.org/docs/rules/no-undef) [`prefer-const`](https://eslint.org/docs/rules/prefer-const) ```javascript // плохо superPower = new SuperPower(); // хорошо const superPower = new SuperPower(); ``` <a name="variables--one-const"></a><a name="13.2"></a> - [13.2](#variables--one-const) Используйте объявление `const` или `let` для каждой переменной или присвоения. eslint: [`one-var`](https://eslint.org/docs/rules/one-var.html) > Почему? Таким образом проще добавить новые переменные. Также вы никогда не будете беспокоиться о перемещении `;` и `,` и об отображении изменений в пунктуации. Вы также можете пройтись по каждому объявлению с помощью отладчика, вместо того, чтобы прыгать через все сразу. ```javascript // плохо const items = getItems(), goSportsTeam = true, dragonball = 'z'; // плохо // (сравните с кодом выше и попытайтесь найти ошибку) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // хорошо const items = getItems(); const goSportsTeam = true; const dragonball = 'z'; ``` <a name="variables--const-let-group"></a><a name="13.3"></a> - [13.3](#variables--const-let-group) В первую очередь группируйте `const`, а затем `let`. > Почему? Это полезно, когда в будущем вам понадобится создать переменную, зависимую от предыдущих. ```javascript // плохо let i, len, dragonball, items = getItems(), goSportsTeam = true; // плохо let i; const items = getItems(); let dragonball; const goSportsTeam = true; let len; // хорошо const goSportsTeam = true; const items = getItems(); let dragonball; let i; let length; ``` <a name="variables--define-where-used"></a><a name="13.4"></a> - [13.4](#variables--define-where-used) Создавайте переменные там, где они вам необходимы, но помещайте их в подходящее место. > Почему? `let` и `const` имеют блочную область видимости, а не функциональную. ```javascript // плохо - вызов ненужной функции function checkName(hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; } // хорошо function checkName(hasName) { if (hasName === 'test') { return false; } const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; } ``` <a name="variables--no-chain-assignment"></a><a name="13.5"></a> - [13.5](#variables--no-chain-assignment) Не создавайте цепочки присваивания переменных. eslint: [`no-multi-assign`](https://eslint.org/docs/rules/no-multi-assign) > Почему? Такие цепочки создают неявные глобальные переменные. ```javascript // плохо (function example() { // JavaScript интерпретирует это, как // let a = ( b = ( c = 1 ) ); // Ключевое слово let применится только к переменной a; // переменные b и c станут глобальными. let a = b = c = 1; }()); console.log(a); // throws ReferenceError console.log(b); // 1 console.log(c); // 1 // хорошо (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // throws ReferenceError console.log(b); // throws ReferenceError console.log(c); // throws ReferenceError // тоже самое и для `const` ``` <a name="variables--unary-increment-decrement"></a><a name="13.6"></a> - [13.6](#variables--unary-increment-decrement) Избегайте использования унарных инкрементов и декрементов (`++`, `--`). eslint [`no-plusplus`](https://eslint.org/docs/rules/no-plusplus) > Почему? Согласно документации eslint, унарные инкремент и декремент автоматически вставляют точку с запятой, что может стать причиной трудноуловимых ошибок при инкрементировании и декрементировании значений. Также нагляднее изменять ваши значения таким образом `num += 1` вместо `num++` или `num ++`. Запрет на унарные инкремент и декремент ограждает вас от непреднамеренных преждевременных инкрементаций/декрементаций значений, которые могут привести к непредсказуемому поведению вашей программы. ```javascript // плохо const array = [1, 2, 3]; let num = 1; num++; --num; let sum = 0; let truthyCount = 0; for (let i = 0; i < array.length; i++) { let value = array[i]; sum += value; if (value) { truthyCount++; } } // хорошо const array = [1, 2, 3]; let num = 1; num += 1; num -= 1; const sum = array.reduce((a, b) => a + b, 0); const truthyCount = array.filter(Boolean).length; ``` <a name="variables--linebreak"></a> - [13.7](#variables--linebreak) В присвоении избегайте разрывов строк до и после `=`. Если ваше присвоение нарушает правило [`max-len`](https://eslint.org/docs/rules/max-len.html), оберните значение в круглые скобки. eslint [`operator-linebreak`](https://eslint.org/docs/rules/operator-linebreak.html). > Почему? Разрывы строк до и после `=` могут приводить к путанице в понимании значения. ```javascript // плохо const foo = superLongLongLongLongLongLongLongLongFunctionName(); // плохо const foo = 'superLongLongLongLongLongLongLongLongString'; // хорошо const foo = ( superLongLongLongLongLongLongLongLongFunctionName() ); // хорошо const foo = 'superLongLongLongLongLongLongLongLongString'; ``` <a name="variables--no-unused-vars"></a> - [13.8](#variables--no-unused-vars) Запретить неиспользуемые переменные. eslint: [`no-unused-vars`](https://eslint.org/docs/rules/no-unused-vars) > Почему? Переменные, которые объявлены и не используются в коде, скорее всего, являются ошибкой из-за незавершённого рефакторинга. Такие переменные занимают место в коде и могут привести к путанице при чтении. ```javascript // плохо var some_unused_var = 42; // Переменные, которые используются только для записи, не считаются используемыми. var y = 10; y = 5; // Чтение для собственной модификации. var z = 0; z = z + 1; // Неиспользуемые аргументы функции. function getX(x, y) { return x; } // хорошо function getXPlusY(x, y) { return x + y; } var x = 1; var y = a + 2; alert(getXPlusY(x, y)); // Переменная 'type' игнорируется, даже если она не испольуется, потому что рядом есть rest-свойство. // Эта форма извлечения объекта, которая опускает указанные ключи. var { type, ...coords } = data; // 'coords' теперь 'data' объект без свойства 'type'. ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="hoisting">Подъём</a> <a name="hoisting--about"></a><a name="14.1"></a> - [14.1](#hoisting--about) Объявления `var` поднимаются в начало области видимости ближайшей закрывающей их функции, а их присвоение нет. Объявления `const` и `let` работают по новой концепции называемой [Временные Мёртвые Зоны (Temporal Dead Zone)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/let#Временные_мертвые_зоны_и_ошибки_при_использовании_let). Важно знать, почему использовать [typeof больше не безопасно](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15). ```javascript // мы знаем, что это не будет работать // (если нет глобальной переменной notDefined) function example() { console.log(notDefined); // => выбросит ошибку ReferenceError } // обращение к переменной до её создания // будет работать из-за подъёма. // Примечание: значение true не поднимается. function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // интерпретатор понимает объявление // переменной в начало области видимости. // это означает, что наш пример // можно переписать таким образом: function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // использование const и let function example() { console.log(declaredButNotAssigned); // => выбросит ошибку ReferenceError console.log(typeof declaredButNotAssigned); // => выбросит ошибку ReferenceError const declaredButNotAssigned = true; } ``` <a name="hoisting--anon-expressions"></a><a name="14.2"></a> - [14.2](#hoisting--anon-expressions) Для анонимных функциональных выражений наверх области видимости поднимается название переменной, но не её значение. ```javascript function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous не является функцией var anonymous = function () { console.log('anonymous function expression'); }; } ``` <a name="hoisting--named-expressions"></a><a name="hoisting--named-expressions"></a><a name="14.3"></a> - [14.3](#hoisting--named-expressions) Для именованных функциональных выражений наверх области видимости поднимается название переменной, но не имя или тело функции. ```javascript function example() { console.log(named); // => undefined named(); // => TypeError named не является функцией superPower(); // => ReferenceError superPower не определена var named = function superPower() { console.log('Flying'); }; } // тоже самое справедливо, когда имя функции // совпадает с именем переменной. function example() { console.log(named); // => undefined named(); // => TypeError named не является функцией var named = function named() { console.log('named'); }; } ``` <a name="hoisting--declarations"></a><a name="14.4"></a> - [14.4](#hoisting--declarations) При объявлении функции её имя и тело поднимаются наверх области видимости. ```javascript function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } } ``` - Более подробно можно прочитать в статье [JavaScript Scoping & Hoisting](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting/) от [Ben Cherry](http://www.adequatelygood.com/). **[⬆ к оглавлению](#Оглавление)** ## <a name="comparison-operators--equality">Операторы сравнения и равенства</a> <a name="comparison--eqeqeq"></a><a name="15.1"></a> - [15.1](#comparison--eqeqeq) Используйте `===` и `!==` вместо `==` и `!=`. eslint: [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq.html) <a name="comparison--if"></a><a name="15.2"></a> - [15.2](#comparison--if) Условные операторы, такие как `if`, вычисляются путём приведения к логическому типу `Boolean` через абстрактный метод `ToBoolean` и всегда следуют следующим правилам: - **Object** соответствует **true** - **Undefined** соответствует **false** - **Null** соответствует **false** - **Boolean** соответствует **значению булева типа** - **Number** соответствует **false**, если **+0, -0, or NaN**, в остальных случаях **true** - **String** соответствует **false**, если строка пустая `''`, в остальных случаях **true** ```javascript if ([0] && []) { // true // Массив (даже пустой) является объектом, а объекты возвращают true } ``` <a name="comparison--shortcuts"></a><a name="15.3"></a> - [15.3](#comparison--shortcuts) Используйте сокращения для булевских типов, а для строк и чисел применяйте явное сравнение. ```javascript // плохо if (isValid === true) { // ... } // хорошо if (isValid) { // ... } // плохо if (name) { // ... } // хорошо if (name !== '') { // ... } // плохо if (collection.length) { // ... } // хорошо if (collection.length > 0) { // ... } ``` <a name="comparison--moreinfo"></a><a name="15.4"></a> - [15.4](#comparison--moreinfo) Более подробную информацию можно узнать в статье [Truth Equality and JavaScript](https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108) от Angus Croll. <a name="comparison--switch-blocks"></a><a name="15.5"></a> - [15.5](#comparison--switch-blocks) Используйте фигурные скобки для `case` и `default`, если они содержат лексические декларации (например, `let`, `const`, `function`, и `class`). eslint: [`no-case-declarations`](https://eslint.org/docs/rules/no-case-declarations.html). > Почему? Лексические декларации видны во всем `switch` блоке, но инициализируются только при присваивании, которое происходит при входе в блок `case`. Возникают проблемы, когда множество `case` пытаются определить одно и то же. ```javascript // плохо switch (foo) { case 1: let x = 1; break; case 2: const y = 2; break; case 3: function f() { // ... } break; default: class C {} } // хорошо switch (foo) { case 1: { let x = 1; break; } case 2: { const y = 2; break; } case 3: { function f() { // ... } break; } case 4: bar(); break; default: { class C {} } } ``` <a name="comparison--nested-ternaries"></a><a name="15.6"></a> - [15.6](#comparison--nested-ternaries) Тернарные операторы не должны быть вложены и в большинстве случаев должны быть расположены на одной строке. eslint: [`no-nested-ternary`](https://eslint.org/docs/rules/no-nested-ternary.html). ```javascript // плохо const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // разбит на два отдельных тернарных выражения const maybeNull = value1 > value2 ? 'baz' : null; const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // отлично const foo = maybe1 > maybe2 ? 'bar' : maybeNull; ``` <a name="comparison--unneeded-ternary"></a><a name="15.7"></a> - [15.7](#comparison--unneeded-ternary) Избегайте ненужных тернарных операторов. eslint: [`no-unneeded-ternary`](https://eslint.org/docs/rules/no-unneeded-ternary.html). ```javascript // плохо const foo = a ? a : b; const bar = c ? true : false; const baz = c ? false : true; // хорошо const foo = a || b; const bar = !!c; const baz = !c; ``` <a name="comparison--no-mixed-operators"></a> - [15.8](#comparison--no-mixed-operators) При смешивании операторов, помещайте их в круглые скобки. Единственным исключением являются стандартные арифметические операторы: `+`, `-` и `**`, так как их приоритет широко известен. Мы рекомендуем заключить `/` и `*` в круглые скобки, поскольку их приоритет может быть неоднозначным, когда они смешиваются. eslint: [`no-mixed-operators`](https://eslint.org/docs/rules/no-mixed-operators.html) > Почему? Это улучшает читаемость и уточняет намерения разработчика. ```javascript // плохо const foo = a && b < 0 || c > 0 || d + 1 === 0; // плохо const bar = a ** b - 5 % d; // плохо // можно ошибиться, думая что это (a || b) && c if (a || b && c) { return d; } // плохо const bar = a + b / c * d; // хорошо const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // хорошо const bar = a ** b - (5 % d); // хорошо if (a || (b && c)) { return d; } // хорошо const bar = a + (b / c) * d; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="blocks">Блоки</a> <a name="blocks--braces"></a><a name="16.1"></a> - [16.1](#blocks--braces) Используйте фигурные скобки, когда блок кода занимает несколько строк. eslint: [`nonblock-statement-body-position`](https://eslint.org/docs/rules/nonblock-statement-body-position) ```javascript // плохо if (test) return false; // хорошо if (test) return false; // хорошо if (test) { return false; } // плохо function foo() { return false; } // хорошо function bar() { return false; } ``` <a name="blocks--cuddled-elses"></a><a name="16.2"></a> - [16.2](#blocks--cuddled-elses) Если блоки кода в условии `if` и `else` занимают несколько строк, расположите оператор `else` на той же строчке, где находится закрывающая фигурная скобка блока `if`. eslint: [`brace-style`](https://eslint.org/docs/rules/brace-style.html) ```javascript // плохо if (test) { thing1(); thing2(); } else { thing3(); } // хорошо if (test) { thing1(); thing2(); } else { thing3(); } ``` <a name="blocks--no-else-return"></a><a name="16.3"></a> - [16.3](#blocks--no-else-return) Если в блоке `if` всегда выполняется оператор `return`, последующий блок `else` не нужен. `return` внутри блока `else if`, следующем за блоком `if`, который содержит `return`, может быть разделён на несколько блоков `if`. eslint: [`no-else-return`](https://eslint.org/docs/rules/no-else-return) ```javascript // плохо function foo() { if (x) { return x; } else { return y; } } // плохо function cats() { if (x) { return x; } else if (y) { return y; } } // плохо function dogs() { if (x) { return x; } else { if (y) { return y; } } } // хорошо function foo() { if (x) { return x; } return y; } // хорошо function cats() { if (x) { return x; } if (y) { return y; } } // хорошо function dogs(x) { if (x) { if (z) { return y; } } else { return z; } } ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="control-statements">Управляющие операторы</a> <a name="control-statements"></a> - [17.1](#control-statements) Если ваш управляющий оператор (`if`, `while` и т.д.) слишком длинный или превышает максимальную длину строки, то каждое (сгруппированное) условие можно поместить на новую строку. Логический оператор должен располагаться в начале строки. > Почему? Наличие операторов в начале строки приводит к выравниванию операторов и напоминает цепочку методов. Это также улучшает читаемость, упрощая визуальное отслеживание сложной логики. ```javascript // плохо if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // плохо if (foo === 123 && bar === 'abc') { thing1(); } // плохо if (foo === 123 && bar === 'abc') { thing1(); } // плохо if ( foo === 123 && bar === 'abc' ) { thing1(); } // хорошо if ( foo === 123 && bar === 'abc' ) { thing1(); } // хорошо if ( (foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // хорошо if (foo === 123 && bar === 'abc') { thing1(); } ``` <a name="control-statement--value-selection"></a><a name="control-statements--value-selection"></a> - [17.2](#control-statements--value-selection) Не используйте операторы выбора вместо управляющих операторов. ```javascript // плохо !isRunning && startRunning(); // хорошо if (!isRunning) { startRunning(); } ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="comments">Комментарии</a> <a name="comments--multiline"></a><a name="17.1"></a> - [18.1](#comments--multiline) Используйте конструкцию `/** ... */` для многострочных комментариев. ```javascript // плохо // make() возвращает новый элемент // соответствующий переданному названию тега // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // хорошо /** * make() возвращает новый элемент * соответствующий переданному названию тега */ function make(tag) { // ... return element; } ``` <a name="comments--singleline"></a><a name="17.2"></a> - [18.2](#comments--singleline) Используйте двойной слеш `//` для однострочных комментариев. Располагайте такие комментарии отдельной строкой над кодом, который хотите пояснить. Если комментарий не является первой строкой блока, добавьте сверху пустую строку. ```javascript // плохо const active = true; // это текущая вкладка // хорошо // это текущая вкладка const active = true; // плохо function getType() { console.log('fetching type...'); // установить по умолчанию тип 'no type' const type = this.type || 'no type'; return type; } // хорошо function getType() { console.log('fetching type...'); // установить по умолчанию тип 'no type' const type = this.type || 'no type'; return type; } // тоже хорошо function getType() { // установить по умолчанию тип 'no type' const type = this.type || 'no type'; return type; } ``` <a name="comments--spaces"></a> - [18.3](#comments--spaces) Начинайте все комментарии с пробела, так их проще читать. eslint: [`spaced-comment`](https://eslint.org/docs/rules/spaced-comment) ```javascript // плохо //это текущая вкладка const active = true; // хорошо // это текущая вкладка const active = true; // плохо /** *make() возвращает новый элемент *соответствующий переданному названию тега */ function make(tag) { // ... return element; } // хорошо /** * make() возвращает новый элемент * соответствующий переданному названию тега */ function make(tag) { // ... return element; } ``` <a name="comments--actionitems"></a><a name="17.3"></a> - [18.4](#comments--actionitems) Если комментарий начинается со слов `FIXME` или `TODO`, то это помогает другим разработчикам быстро понять, когда вы хотите указать на проблему, которую надо решить, или когда вы предлагаете решение проблемы, которое надо реализовать. Такие комментарии, в отличие от обычных, побуждают к действию: `FIXME: -- нужно разобраться с этим` или `TODO: -- нужно реализовать`. <a name="comments--fixme"></a><a name="17.4"></a> - [18.5](#comments--fixme) Используйте `// FIXME:`, чтобы описать проблему. ```javascript class Calculator extends Abacus { constructor() { super(); // FIXME: здесь не должна использоваться глобальная переменная total = 0; } } ``` <a name="comments--todo"></a><a name="17.5"></a> - [18.6](#comments--todo) Используйте `// TODO:`, чтобы описать решение проблемы. ```javascript class Calculator extends Abacus { constructor() { super(); // TODO: нужна возможность задать total через параметры this.total = 0; } } ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="whitespace">Пробелы</a> <a name="whitespace--spaces"></a><a name="18.1"></a> - [19.1](#whitespace--spaces) Используйте мягкую табуляцию (символ пробела) шириной в 2 пробела. eslint: [`indent`](https://eslint.org/docs/rules/indent.html) ```javascript // плохо function foo() { ∙∙∙∙let name; } // плохо function bar() { ∙let name; } // хорошо function baz() { ∙∙let name; } ``` <a name="whitespace--before-blocks"></a><a name="18.2"></a> - [19.2](#whitespace--before-blocks) Ставьте 1 пробел перед открывающей фигурной скобкой у блока. eslint: [`space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks.html) ```javascript // плохо function test(){ console.log('test'); } // хорошо function test() { console.log('test'); } // плохо dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // хорошо dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', }); ``` <a name="whitespace--around-keywords"></a><a name="18.3"></a> - [19.3](#whitespace--around-keywords) Ставьте 1 пробел перед открывающей круглой скобкой в операторах управления (`if`, `while` и т.п.). Не оставляйте пробелов между списком аргументов и названием в объявлениях и вызовах функций. eslint: [`keyword-spacing`](https://eslint.org/docs/rules/keyword-spacing.html) ```javascript // плохо if(isJedi) { fight (); } // хорошо if (isJedi) { fight(); } // плохо function fight () { console.log ('Swooosh!'); } // хорошо function fight() { console.log('Swooosh!'); } ``` <a name="whitespace--infix-ops"></a><a name="18.4"></a> - [19.4](#whitespace--infix-ops) Разделяйте операторы пробелами. eslint: [`space-infix-ops`](https://eslint.org/docs/rules/space-infix-ops.html) ```javascript // плохо const x=y+5; // хорошо const x = y + 5; ``` <a name="whitespace--newline-at-end"></a><a name="18.5"></a> - [19.5](#whitespace--newline-at-end) В конце файла оставляйте одну пустую строку. eslint: [`eol-last`](https://github.com/eslint/eslint/blob/master/docs/rules/eol-last.md) ```javascript // плохо import { es6 } from './AirbnbStyleGuide'; // ... export default es6; ``` ```javascript // плохо import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ↵ ``` ```javascript // хорошо import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ``` <a name="whitespace--chains"></a><a name="18.6"></a> - [19.6](#whitespace--chains) Используйте переносы строк и отступы, когда делаете длинные цепочки методов (больше 2 методов). Ставьте точку в начале строки, чтобы дать понять, что это не новая инструкция, а продолжение цепочки. eslint: [`newline-per-chained-call`](https://eslint.org/docs/rules/newline-per-chained-call) [`no-whitespace-before-property`](https://eslint.org/docs/rules/no-whitespace-before-property) ```javascript // плохо $('#items').find('.selected').highlight().end().find('.open').updateCount(); // плохо $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // хорошо $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // плохо const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // хорошо const leds = stage.selectAll('.led') .data(data) .enter().append('svg:svg') .classed('led', true) .attr('width', (radius + margin) * 2) .append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // хорошо const leds = stage.selectAll('.led').data(data); ``` <a name="whitespace--after-blocks"></a><a name="18.7"></a> - [19.7](#whitespace--after-blocks) Оставляйте пустую строку между блоком кода и следующей инструкцией. ```javascript // плохо if (foo) { return bar; } return baz; // хорошо if (foo) { return bar; } return baz; // плохо const obj = { foo() { }, bar() { }, }; return obj; // хорошо const obj = { foo() { }, bar() { }, }; return obj; // плохо const arr = [ function foo() { }, function bar() { }, ]; return arr; // хорошо const arr = [ function foo() { }, function bar() { }, ]; return arr; ``` <a name="whitespace--padded-blocks"></a><a name="18.8"></a> - [19.8](#whitespace--padded-blocks) Не добавляйте отступы до или после кода внутри блока. eslint: [`padded-blocks`](https://eslint.org/docs/rules/padded-blocks.html) ```javascript // плохо function bar() { console.log(foo); } // тоже плохо if (baz) { console.log(qux); } else { console.log(foo); } // хорошо function bar() { console.log(foo); } // хорошо if (baz) { console.log(qux); } else { console.log(foo); } ``` <a name="whitespace--no-multiple-blanks"></a> - [19.9](#whitespace--no-multiple-blanks) Не используйте множество пустых строк для заполнения кода. eslint: [`no-multiple-empty-lines`](https://eslint.org/docs/rules/no-multiple-empty-lines) <!-- markdownlint-disable MD012 --> ```javascript // плохо class Person { constructor(fullName, email, birthday) { this.fullName = fullName; this.email = email; this.setAge(birthday); } setAge(birthday) { const today = new Date(); const age = this.getAge(today, birthday); this.age = age; } getAge(today, birthday) { // .. } } // good class Person { constructor(fullName, email, birthday) { this.fullName = fullName; this.email = email; this.setAge(birthday); } setAge(birthday) { const today = new Date(); const age = getAge(today, birthday); this.age = age; } getAge(today, birthday) { // .. } } ``` <a name="whitespace--in-parens"></a><a name="18.9"></a> - [19.10](#whitespace--in-parens) Не добавляйте пробелы между круглыми скобками и их содержимым. eslint: [`space-in-parens`](https://eslint.org/docs/rules/space-in-parens.html) ```javascript // плохо function bar( foo ) { return foo; } // хорошо function bar(foo) { return foo; } // плохо if ( foo ) { console.log(foo); } // хорошо if (foo) { console.log(foo); } ``` <a name="whitespace--in-brackets"></a><a name="18.10"></a> - [19.11](#whitespace--in-brackets) Не добавляйте пробелы между квадратными скобками и их содержимым. eslint: [`array-bracket-spacing`](https://eslint.org/docs/rules/array-bracket-spacing.html) ```javascript // плохо const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // хорошо const foo = [1, 2, 3]; console.log(foo[0]); ``` <a name="whitespace--in-braces"></a><a name="18.11"></a> - [19.12](#whitespace--in-braces) Добавляйте пробелы между фигурными скобками и их содержимым. eslint: [`object-curly-spacing`](https://eslint.org/docs/rules/object-curly-spacing.html) ```javascript // плохо const foo = {clark: 'kent'}; // хорошо const foo = { clark: 'kent' }; ``` <a name="whitespace--max-len"></a><a name="18.12"></a> - [19.13](#whitespace--max-len) Старайтесь не допускать, чтобы строки были длиннее 100 символов (включая пробелы). Замечание: согласно [пункту выше](#strings--line-length), длинные строки с текстом освобождаются от этого правила и не должны разбиваться на несколько строк. eslint: [`max-len`](https://eslint.org/docs/rules/max-len.html) > Почему? Это обеспечивает удобство чтения и поддержки кода. ```javascript // плохо const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // плохо $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // хорошо const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // хорошо $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.')); ``` <a name="whitespace--block-spacing"></a> - [19.14](#whitespace--block-spacing) Требуйте согласованного расстояния между открывающим символом блока и следующим символом на одной и той же строке. Тоже самое касается расстояния между закрывающим символом блока и предыдущим символом. eslint: [`block-spacing`](https://eslint.org/docs/rules/block-spacing) ```javascript // плохо function foo() {return true;} if (foo) { bar = 0;} // хорошо function foo() { return true; } if (foo) { bar = 0; } ``` <a name="whitespace--comma-spacing"></a> - [19.15](#whitespace--comma-spacing) Избегайте пробелов перед запятыми и ставьте его после. eslint: [`comma-spacing`](https://eslint.org/docs/rules/comma-spacing) ```javascript // плохо var foo = 1,bar = 2; var arr = [1 , 2]; // хорошо var foo = 1, bar = 2; var arr = [1, 2]; ``` <a name="whitespace--computed-property-spacing"></a> - [19.16](#whitespace--computed-property-spacing) Избегайте пробелов внутри скобок вычисляемого свойства. eslint: [`computed-property-spacing`](https://eslint.org/docs/rules/computed-property-spacing) ```javascript // плохо obj[foo ] obj[ 'foo'] var x = {[ b ]: a} obj[foo[ bar ]] // хорошо obj[foo] obj['foo'] var x = { [b]: a } obj[foo[bar]] ``` <a name="whitespace--func-call-spacing"></a> - [19.17](#whitespace--func-call-spacing) Избегайте пробелов между функциями и их вызовами. eslint: [`func-call-spacing`](https://eslint.org/docs/rules/func-call-spacing) ```javascript // плохо func (); func (); // хорошо func(); ``` <a name="whitespace--key-spacing"></a> - [19.18](#whitespace--key-spacing) Обеспечьте согласованное расстояние между ключами и значениями в свойствах литералов объекта. eslint: [`key-spacing`](https://eslint.org/docs/rules/key-spacing) ```javascript // плохо var obj = { "foo" : 42 }; var obj2 = { "foo":42 }; // хорошо var obj = { "foo": 42 }; ``` <a name="whitespace--no-trailing-spaces"></a> - [19.19](#whitespace--no-trailing-spaces) Избегайте пробелов в конце строки. eslint: [`no-trailing-spaces`](https://eslint.org/docs/rules/no-trailing-spaces) <a name="whitespace--no-multiple-empty-lines"></a> - [19.20](#whitespace--no-multiple-empty-lines) Избегайте множества пустых строк и новой строки в начале файлов. Разрешайте только одну пустую строку в конце файла. eslint: [`no-multiple-empty-lines`](https://eslint.org/docs/rules/no-multiple-empty-lines) <!-- markdownlint-disable MD012 --> ```javascript // плохо - множество пустых строк var x = 1; var y = 2; // плохо - 2+ новых строк в конце файла var x = 1; var y = 2; // плохо - 1+ новая строка в начале файла var x = 1; var y = 2; // хорошо var x = 1; var y = 2; ``` <!-- markdownlint-enable MD012 --> **[⬆ к оглавлению](#Оглавление)** ## <a name="commas">Запятые</a> <a name="commas--leading-trailing"></a><a name="19.1"></a> - [20.1](#commas--leading-trailing) Не начинайте строку с запятой. eslint: [`comma-style`](https://eslint.org/docs/rules/comma-style.html) ```javascript // плохо const story = [ once , upon , aTime ]; // хорошо const story = [ once, upon, aTime, ]; // плохо const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // хорошо const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', }; ``` <a name="commas--dangling"></a><a name="19.2"></a> - [20.2](#commas--dangling) Добавляйте висячие запятые. eslint: [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle.html) > Почему? Такой подход даёт понятную разницу при просмотре изменений. Кроме того, транспиляторы типа Babel удалят висячие запятые из собранного кода, поэтому вы можете не беспокоиться о [проблемах](https://github.com/airbnb/javascript/blob/es5-deprecated/es5/README.md#commas) в старых браузерах. ```diff // плохо - git diff без висячей запятой const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // хорошо - git diff с висячей запятой const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], }; ``` ```javascript // плохо const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // хорошо const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ]; // плохо function createHero( firstName, lastName, inventorOf ) { // ничего не делает } // хорошо function createHero( firstName, lastName, inventorOf, ) { // ничего не делает } // хорошо (обратите внимание, что висячей запятой не должно быть после rest-параметра) function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // ничего не делает } // плохо createHero( firstName, lastName, inventorOf ); // хорошо createHero( firstName, lastName, inventorOf, ); // хорошо (обратите внимание, что висячей запятой не должно быть после rest-аргумента) createHero( firstName, lastName, inventorOf, ...heroArgs ); ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="semicolons">Точка с запятой</a> <a name="semicolons--required"></a><a name="20.1"></a> - [21.1](#semicolons--required) **Да.** eslint: [`semi`](https://eslint.org/docs/rules/semi.html) > Почему? Когда JavaScript встречает перенос строки без точки с запятой, он использует правило под названием [Автоматическая Вставка Точки с запятой (Automatic Semicolon Insertion)](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion), чтобы определить, стоит ли считать этот перенос строки как конец выражения и (как следует из названия) поместить точку с запятой в вашем коде до переноса строки. Однако, ASI содержит несколько странных форм поведения, и ваш код может быть сломан, если JavaScript неверно истолкует ваш перенос строки. Эти правила станут сложнее, когда новые возможности станут частью JavaScript. Явное завершение ваших выражений и настройка вашего линтера для улавливания пропущенных точек с запятыми помогут вам предотвратить возникновение проблем. ```javascript // плохо - выбрасывает исключение const luke = {} const leia = {} [luke, leia].forEach((jedi) => jedi.father = 'vader') // плохо - выбрасывает исключение const reaction = "No! That’s impossible!" (async function meanwhileOnTheFalcon() { // переносимся к `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // плохо - возвращает `undefined` вместо значения на следующей строке. Так всегда происходит, когда `return` расположен сам по себе, потому что ASI (Автоматическая Вставка Точки с запятой)! function foo() { return 'search your feelings, you know it to be foo' } // хорошо const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // хорошо const reaction = "No! That’s impossible!"; (async function meanwhileOnTheFalcon() { // переносимся к `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // хорошо function foo() { return 'search your feelings, you know it to be foo'; } ``` [Читать подробнее](https://stackoverflow.com/questions/7365172/semicolon-before-self-invoking-function/7365214#7365214). **[⬆ к оглавлению](#Оглавление)** ## <a name="type-casting--coercion">Приведение типов</a> <a name="coercion--explicit"></a><a name="21.1"></a> - [22.1](#coercion--explicit) Выполняйте приведение типов в начале инструкции. <a name="coercion--strings"></a><a name="21.2"></a> - [22.2](#coercion--strings) Строки: eslint: [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers) ```javascript // => this.reviewScore = 9; // плохо const totalScore = new String(this.reviewScore); // тип totalScore будет "object", а не "string" // плохо const totalScore = this.reviewScore + ''; // вызывает this.reviewScore.valueOf() // плохо const totalScore = this.reviewScore.toString(); // нет гарантии что вернётся строка // хорошо const totalScore = String(this.reviewScore); ``` <a name="coercion--numbers"></a><a name="21.3"></a> - [22.3](#coercion--numbers) Числа: Используйте `Number` и `parseInt` с основанием системы счисления. eslint: [`radix`](https://eslint.org/docs/rules/radix) [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers) ```javascript const inputValue = '4'; // плохо const val = new Number(inputValue); // плохо const val = +inputValue; // плохо const val = inputValue >> 0; // плохо const val = parseInt(inputValue); // хорошо const val = Number(inputValue); // хорошо const val = parseInt(inputValue, 10); ``` <a name="coercion--comment-deviations"></a><a name="21.4"></a> - [22.4](#coercion--comment-deviations) Если по какой-то причине вы делаете что-то настолько безумное, что `parseInt` является слабым местом и вам нужно использовать побитовый сдвиг из-за [вопросов производительности](https://jsperf.com/coercion-vs-casting/3), оставьте комментарий, объясняющий почему и что вы делаете. ```javascript // хорошо /** * этот код медленно работал из-за parseInt. * побитовый сдвиг строки для приведения её к числу * работает значительно быстрее. */ const val = inputValue >> 0; ``` <a name="coercion--bitwise"></a><a name="21.5"></a> - [22.5](#coercion--bitwise) **Примечание:** Будьте осторожны с побитовыми операциями. Числа в JavaScript являются [64-битными значениями](https://es5.github.io/#x4.3.19), но побитовые операции всегда возвращают 32-битные значения ([источник](https://es5.github.io/#x11.7)). Побитовый сдвиг может привести к неожиданному поведению для значений больше, чем 32 бита. [Обсуждение](https://github.com/airbnb/javascript/issues/109). Верхний предел — 2&nbsp;147&nbsp;483&nbsp;647: ```javascript 2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647 ``` <a name="coercion--booleans"></a><a name="21.6"></a> - [22.6](#coercion--booleans) Логические типы: eslint: [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers) ```javascript const age = 0; // плохо const hasAge = new Boolean(age); // хорошо const hasAge = Boolean(age); // отлично const hasAge = !!age; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="naming-conventions">Соглашение об именовании</a> <a name="naming--descriptive"></a><a name="22.1"></a> - [23.1](#naming--descriptive) Избегайте названий из одной буквы. Имя должно быть наглядным. eslint: [`id-length`](https://eslint.org/docs/rules/id-length) ```javascript // плохо function q() { // ... } // хорошо function query() { // ... } ``` <a name="naming--camelCase"></a><a name="22.2"></a> - [23.2](#naming--camelCase) Используйте `camelCase` для именования объектов, функций и экземпляров. eslint: [`camelcase`](https://eslint.org/docs/rules/camelcase.html) ```javascript // плохо const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // хорошо const thisIsMyObject = {}; function thisIsMyFunction() {} ``` <a name="naming--PascalCase"></a><a name="22.3"></a> - [23.3](#naming--PascalCase) Используйте `PascalCase` только для именования конструкторов и классов. eslint: [`new-cap`](https://eslint.org/docs/rules/new-cap.html) ```javascript // плохо function user(options) { this.name = options.name; } const bad = new user({ name: 'nope', }); // хорошо class User { constructor(options) { this.name = options.name; } } const good = new User({ name: 'yup', }); ``` <a name="naming--leading-underscore"></a><a name="22.4"></a> - [23.4](#naming--leading-underscore) Не используйте `_` в начале или в конце названий. eslint: [`no-underscore-dangle`](https://eslint.org/docs/rules/no-underscore-dangle.html) > Почему? JavaScript не имеет концепции приватности свойств или методов. Хотя подчёркивание в начале имени является распространённым соглашением, которое показывает «приватность», фактически эти свойства являются такими же доступными, как и часть вашего публичного API. Это соглашение может привести к тому, что разработчики будут ошибочно думать, что изменения не приведут к поломке или что тесты не нужны. Итог: если вы хотите, чтобы что-то было «приватным», то оно не должно быть доступно извне. ```javascript // плохо this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // хорошо this.firstName = 'Panda'; // хорошо, в средах, где поддерживается WeakMaps // смотрите https://kangax.github.io/compat-table/es6/#test-WeakMap const firstNames = new WeakMap(); firstNames.set(this, 'Panda'); ``` <a name="naming--self-this"></a><a name="22.5"></a> - [23.5](#naming--self-this) Не сохраняйте ссылку на `this`. Используйте стрелочные функции или [метод bind()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). ```javascript // плохо function foo() { const self = this; return function () { console.log(self); }; } // плохо function foo() { const that = this; return function () { console.log(that); }; } // хорошо function foo() { return () => { console.log(this); }; } ``` <a name="naming--filename-matches-export"></a><a name="22.6"></a> - [23.6](#naming--filename-matches-export) Название файла точно должно совпадать с именем его экспорта по умолчанию. ```javascript // содержание файла 1 class CheckBox { // ... } export default CheckBox; // содержание файла 2 export default function fortyTwo() { return 42; } // содержание файла 3 export default function insideDirectory() {} // в других файлах // плохо import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export // плохо import CheckBox from './check_box'; // PascalCase import/export, snake_case filename import forty_two from './forty_two'; // snake_case import/filename, camelCase export import inside_directory from './inside_directory'; // snake_case import, camelCase export import index from './inside_directory/index'; // requiring the index file explicitly import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly // хорошо import CheckBox from './CheckBox'; // PascalCase export/import/filename import fortyTwo from './fortyTwo'; // camelCase export/import/filename import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" // ^ поддерживает оба варианта: insideDirectory.js и insideDirectory/index.js ``` <a name="naming--camelCase-default-export"></a><a name="22.7"></a> - [23.7](#naming--camelCase-default-export) Используйте `camelCase`, когда экспортируете функцию по умолчанию. Ваш файл должен называться так же, как и имя функции. ```javascript function makeStyleGuide() { // ... } export default makeStyleGuide; ``` <a name="naming--PascalCase-singleton"></a><a name="22.8"></a> - [23.8](#naming--PascalCase-singleton) Используйте `PascalCase`, когда экспортируете конструктор / класс / синглтон / библиотечную функцию / объект. ```javascript const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide; ``` <a name="naming--Acronyms-and-Initialisms"></a> - [23.9](#naming--Acronyms-and-Initialisms) Сокращения или буквенные аббревиатуры всегда должны быть в верхнем или нижнем регистре. > Почему? Имена предназначены для удобства чтения. ```javascript // плохо import SmsContainer from './containers/SmsContainer'; // плохо const HttpRequests = [ // ... ]; // хорошо import SMSContainer from './containers/SMSContainer'; // хорошо const HTTPRequests = [ // ... ]; // также хорошо const httpRequests = [ // ... ]; // отлично import TextMessageContainer from './containers/TextMessageContainer'; // отлично const requests = [ // ... ]; ``` **[⬆ к оглавлению](#Оглавление)** ## <a name="accessors">Аксессоры</a> <a name="accessors--not-required"></a><a name="23.1"></a> - [24.1](#accessors--not-required) Функции-аксессоры для свойств объекта больше не нужны. <a name="accessors--no-getters-setters"></a><a name="23.2"></a> - [24.2](#accessors--no-getters-setters) Не используйте геттеры/сеттеры, т.к. они вызывают неожиданные побочные эффекты, а также их тяжело тестировать, поддерживать и понимать. Вместо этого создавайте методы `getVal()` и `setVal('hello')`. ```javascript // плохо class Dragon { get age() { // ... } set age(value) { // ... } } // хорошо class Dragon { getAge() { // ... } setAge(value) { // ... } } ``` <a name="accessors--boolean-prefix"></a><a name="23.3"></a> - [24.3](#accessors--boolean-prefix) Если свойство/метод возвращает логический тип, то используйте названия `isVal()` или `hasVal()`. ```javascript // плохо if (!dragon.age()) { return false; } // хорошо if (!dragon.hasAge()) { return false; } ``` <a name="accessors--consistent"></a><a name="23.4"></a> - [24.4](#accessors--consistent) Можно создавать функции `get()` и `set()`, но нужно быть последовательным. ```javascript class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } } ``` **[⬆ к оглавлению](#Оглавление)**