Пишем JavaScript красиво.
За основу взята статья "Airbnb - JavaScript style guide". Убрано лишнее, нужные моменты переосмысленны и переведены, добавлено некоторые мои видения.
- Переменные
- Объекты
- Массивы
- Деструктурирование
- Строки
- Функции
- Стрелочные функции
- Импорты
- Операторы равенства и идентичности
- Блоки кода
- Комментарии
- Табы и пробелы
- Запятые
- Точки с запятой
- Соглашение об именовании
-
1.1 В ES6 не принято использовать
var
. Используйтеconst
если не хотите чтобы значение переменной или ссылка на значение менялось. Во всех остальных случаях используйтеlet
.eslint:
prefer-const
,no-const-assign
no-var
jscs:disallowVar
Используя
const
можно предотвратить перезаписи простых типов данных (string
,number
,boolean
,null
,undefined
) или перезаписи ссылки на сложные типы данных (array
,object
,function
). Улучшает понимание кода другими программистами, какие переменные можно менять, а какие нет.Отказываясь от
var
получаем везде переменные с блочной областью видимости.// bad var a = 1; var b = 2; var count = 0; if (true) { count += 1; } // good const a = 1; const b = 2; let count = 0; if (true) { count += 1; }
-
1.2 Блочная область видимости
const
иlet
.// const and let only exist in the blocks they are defined in. { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError
-
1.3 Всегда используйте
const
илиlet
для объявления переменных. Иначе переменная будет объявлена глобально, а это не есть хорошо.// bad superPower = new SuperPower(); // good const superPower = new SuperPower();
-
2.1 Для создания объекта используйте литерал объекта
{}
. Так проще и читабельней.eslint:
no-new-object
// bad const item = new Object(); // good const item = {};
-
2.2 Используйте сокращенный синтаксис для определения методов объекта.
eslint:
object-shorthand
jscs:requireEnhancedObjectLiterals
// bad const item = { value: 1, addValue: function (value) { return this.value + value; }, }; // good const item = { value: 1, addValue(value) { return this.value + value; }, };
-
2.3 Используйте сокращенный синтаксис для значений свойств.
eslint:
object-shorthand
jscs:requireEnhancedObjectLiterals
const name = 'Awesome item'; // bad const item = { name: name, }; // good const item = { name, };
Группируйте сокращенные свойства в начале объекта.
const name = 'Awesome item'; const link = 'https://www.abc.com'; // bad const item = { index: 1, name, type: 'new', link }; // good const item = { name, link, index: 1, type: 'new' };
-
2.4 Имена свойств не нужно обрамлять в кавычки. Обрамляйте только синтактически неправильные имена (но такие имена лучше не использовать =) ).
eslint:
quote-props
jscs:disallowQuotedKeysInObjects
// bad const bad = { 'foo': 3, 'bar': 4, 'foo-bar': 5, }; // good const good = { foo: 3, bar: 4, 'foo-bar': 5, };
-
2.5 Используйте точечную нотацию для доступа к свойствам объекта.
eslint:
dot-notation
jscs:requireDotNotation
const item = { foo: true, bar: 28, }; // bad const hasFoo = item['foo']; // good const hasFoo = item.foo;
-
2.6 Используйте скобочную нотацию
[]
, когда вы обращаетесь к свойству через имя хранимое в переменной.const item = { foo: true, bar: 28, }; function getProp(prop) { return item[prop]; } const hasFoo = getProp('foo');
-
3.1 Для создания массива используйте литерал массива
[]
. Так проще и читабельней.eslint:
no-array-constructor
// bad const items = new Array(); // good const items = [];
-
3.2 Используйте оператор расширения
...
для копирование массивов.// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
-
4.1 Используйте деструктуризацию объекта в параметрах функции.
jscs:
requireObjectDestructuring
Это позволит обойтись без временных переменных.
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName({firstName, lastName}) { return `${firstName} ${lastName}`; }
-
4.2 Используйте деструктуризацию массивов.
jscs:
requireArrayDestructuring
const arr = [1, 2, 3, 4, 5, 6, 7]; // bad const first = arr[0]; // 1 const second = arr[1]; // 2 const fourth = arr[3]; // 4 const rest = arr.slice(4); // 5, 6, 7 // good const [first, second, , fourth, ...rest] = arr; // bad function sum(arr) { let foo = arr[0]; let bar = arr[1]; let foobar = foo + bar; return foobar; } sum([1, 2]); // good function sum([foo, bar]) { let foobar = foo + bar; return foobar; } sum([1, 2]);
-
4.3 Множество значений возвращайте в виде объекта, чтобы потом использовать деструктуризацию объекта. Это удобней чем возвращать массив.
Со временем можно добавлять и возвращать новые переменные не боясь поломать места вызова и использования функции.
// bad function processInput(input) { // calculating... return [left, right, top, bottom]; } // the caller needs to think about the order of return data const [left, __, top] = processInput(input); // good function processInput(input) { // calculating... return {left, right, top, bottom}; } // the caller selects only the data they need const {left, right} = processInput(input);
-
5.1 Используйте одинарные кавычки
''
для строк.eslint:
quotes
jscs:validateQuoteMarks
В текстах внутри строки часто встречаются
"
, обрамляя строки в''
упрощаем себе жизнь.// bad const name = "James Bond. James."; const hello = "Say \"Hello world\""; // good const name = 'James. James Bond.'; const hello = 'Say "Hello world"';
-
5.2 Используйте конкатенацию для переноса длинных строк.
Крутые IDE переносят длинные строки именно таким способом.
// bad 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.'; // bad 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.'; // good 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.';
-
5.3 Используйте литералы шаблонов ``, вместо конкатенации, когда программно строите строки.
eslint:
prefer-template
jscs:requireTemplateStrings
Шаблоны более читаемы, имеют лаконичный синтаксис, могут быть многострочными и могут интерполироваться. Что нам еще нужно?
// bad function sayHi(name) { return 'How are you, ' + name + '?'; } // bad function sayHi(name) { return ['How are you, ', name, '?'].join(); } // good function sayHi(name) { return `How are you, ${name}?`; } // bad const errorMessage = 'This is a super long error that was thrown because \n' + 'of Batman. When you stop to think about how Batman had anything to do \n' + 'with this, you would get nowhere fast.'; // good 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.`;
-
6.1 Старайтесь объявлять именованные функции вместо анонимных (функционального литерала).
jscs:
requireFunctionDeclarations
Функциональный литерал создает анонимную функцию, ее сложнее идентифицировать в стеке вызовов в отличие от именованной функции. К тому же все тело именованной фунции поднимается при объявлении, а в случае функционального литерала, поднимается только ссылка на функцию. Следовательно именованную функцию можно использовать даже если в коде она идет ниже чем место вызова.
// bad const foo = function () { }; foo(); // good function foo() { } foo(); // still work foo(); function foo() { }
-
6.2 Пишите чистые функции — не мутируйте аргументы, используйте значения аргументов по умолчанию, с
ES6
это стало возможно.// really bad function handleThings(opts) { // No! We shouldn't mutate function arguments. // Double bad: if opts is falsy it'll be set to an object which may // be what you want but it can introduce subtle bugs. opts = opts || {}; // ... } // still bad function handleThings(opts) { // if opts is undefined, then opts = {} if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }
-
6.3 Значении по умолчанию ставьте в конце списка.
// bad function handleThings(opts = {}, name) { // ... } // good function handleThings(name, opts = {}) { // ... }
-
6.4 Только будьте осторожны с сайд-эффектами.
var b = 1; // very bad function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3
-
6.5 Пробелы в вызовах функции.
Единообразность это хорошо, не нужно добавлять или удалять пробел, когда будете добавлять или удалять имя функции.
// bad const f = function(){}; const g = function (){}; const h = function() {}; // good const x = function () {}; const y = function a() {};
-
7.1 Когда нужно использовать функциональный литерал (например, передача анонимной функции как параметр другой функции), то используйте стрелочные функции.
eslint:
prefer-arrow-callback
,arrow-spacing
jscs:requireArrowFunctions
Создается функция, которая будет выполняться в контексте текущего
this
, а это то, что нам нужно в большинстве случаях.Но если у вас очень сложная функция, то можете вынести эту логику и объявить отдельную функцию.
// bad [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
-
7.2 Разный синтаксис стрелочной функции.
7.2.1 Однострочная функция — используется неявный
return
.Если у функции всего один аргумент, то можно опустить фигурные скобки. Но для единообразия рекомендуется всегда ставить фигурные скобки вокруг аргументов.
// not recommended [1, 2, 3].map(number => `A string containing the ${number}.`); // one argument [1, 2, 3].map((number) => `A string containing the ${number}.`); // to return object include parentheses around it [1, 2, 3].map((number, index) => ({num: number, index: index})); // no argument [1, 2, 3].map(() => 'No argument');
7.2.2 Если выражение не помещается в одну строку, то обрамляем его в круглые скобки.
[1, 2, 3].map(number => ( `As time went by, the string containing the ${number} became much ` + 'longer. So we needed to break it over multiple lines.' ));
7.2.3 Многострочная функция — блок кода обрамляется в фигурные скобки, используется явный
return
.[1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; });
-
8.1 Сначала идут импорты стандартных библиотек, потом импорты модулей из проекта. Можно отделить эти импорты одной пустой строкой. После импортов отступаем две строки и пишем код.
import React from 'react'; import _ from 'lodash'; import {gettext} from 'gettext'; import {view} from './views/index'; import {model} from './model'; import {update} from './update'; function initApp() {};
-
9.1 Используйте
===
и!==
вместо==
и!=
.eslint:
eqeqeq
При сравнивании неявно выполняется приведение типов переменных. Чтобы избежать этой путаницы всегда используйте операторы
===
и!==
, которые сравнивают и значения, и типы выражений.// bad false == 0; // true false == ''; // true // good false === 0; // false false === ''; // false
-
9.2 Условные выражения, такие как
if
, вычисляются посредством приведения к логическому типуBoolean
через методToBoolean
, и всегда следует таким правилам:- Objects соответствует true
- Undefined соответствует false
- Null соответствует false
- Booleans не меняется
- Numbers соответствует false если является +0, 0, -0 или NaN, иначе true
- Strings соответствует false если это пустая строка
''
, иначе true
if ([0] && []) { // true // an array (even an empty one) is an object, objects will evaluate to true }
-
9.3 Используйте короткий синтаксис.
// bad if (name !== '') { // ...stuff... } // good if (name) { // ...stuff... } // bad if (collection.length > 0) { // ...stuff... } // good if (collection.length) { // ...stuff... }
-
10.1 Используйте фигурные скобки для всех многострочных блоков.
// bad if (test) return false; // good if (test) return false; // good if (test) { return false; } // bad function foo() { return false; } // good function bar() { return false; }
-
11.1 Используйте
/* ... */
для многострочных комментариев. Дляjsdoc
используйте/** ... */
.// bad // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ...stuff... return element; } // good /* * make() returns a new element * based on the passed in tag name */ function make(tag) { // ...stuff... return element; } // jsdoc format /** * make() returns a new element * based on the passed in tag name * * @param {String} tag * @return {Element} element */ function make(tag) { // ...stuff... return element; }
-
11.2 Используйте
//
для однострочного комментария. Размещайте комментарий на новой строке над тем что комментируете. Можете добавить пустую строку над комментарием, чтобы визуально его выделить.// bad const active = true; // is current tab // good // is current tab const active = true; // good function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this._type || 'no type'; return type; } // also good function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this._type || 'no type'; return type; }
-
12.1 Используйте программную табуляцию из 4 пробелов (делая
Tab
редактор подставит 4 пробела).// bad function foo() { ∙const name; } // bad function bar() { ∙∙const name; } // good function baz() { ∙∙∙∙const name; }
-
12.2 Поставьте один пробел перед открывающей фигурной скобкой.
eslint:
space-before-blocks
jscs:requireSpaceBeforeBlockStatements
// bad function test(){ console.log('test'); } // good function test() { console.log('test'); } // bad dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // good dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', });
-
12.3 Поставьте один пробел перед открывающей круглой скобкой в операторах управления (
if
,while
,for
и т.д.).eslint:
space-after-keywords
,space-before-keywords
jscs:requireSpaceAfterKeywords
// bad if(isJedi) { fight (); } // good if (isJedi) { fight(); }
-
12.4 Пробелы в вычислениях.
eslint:
space-infix-ops
jscs:requireSpaceBeforeBinaryOperators
,requireSpaceAfterBinaryOperators
// bad const x=y+5; // good const x = y + 5;
-
12.5 Длинна строки устанавливается в 100 символов, все что длиннее переносим. Допускается выход за границы в разумных пределах.
Длинные строки неудобно читать. На современных FullHD мониторах можно расположить два окна рядом по горизонтали. Можно писать и под 80 символов, но это узковато.
// bad const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. Whatever wizard constrains a helpful ally. The counterpart ascends!'; // bad $.ajax({method: 'POST', url: 'https://airbnb.com/', data: {name: 'John'}}).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // good const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. ' + 'Whatever wizard constrains a helpful ally. The counterpart ascends!'; // good $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: {name: 'John'}, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.'));
-
13.1 Скажите нет запятым в начале строки.
eslint:
comma-style
jscs:requireCommaBeforeLineBreak
// bad const story = [ once , upon , aTime ]; // good const story = [ once, upon, aTime, ]; // bad const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // good const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', };
-
13.2 Можно ставить дополнительные запятые в конце объектов и массивов.
eslint:
comma-dangle
jscs:requireTrailingComma
Это улучшит
diff
'ы, меняется одна строка, а не две. Удобней добавлять свойства и элементы, не нужно беспокоиться есть или нет запятые, потому что они всегда есть.Если вы используете Babel — Babel при транскомпиляции игнорирует эти запятые, поэтому можно не беспокоиться об trailing comma problem.
Если вы не используете Babel — современные браузеры, такие как
Chrome
,Firefox
адекватно обрабатывают запятые в конце списка. Проблемы появляются только в браузерахIE
(ниже 9 версии), так что тестируйте и будьте аккуратны.// bad - diff without trailing comma const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb graph', 'modern nursing'] }; // good - diff with trailing comma const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], }; // bad const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // good const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ];
-
14.1 Ставим в конце каждой инструкции.
eslint:
semi
jscs:requireSemicolons
// bad (function () { const name = 'Skywalker' return name })() // good (() => { const name = 'Skywalker'; return name; }());
-
15.1 Избегайте однобуквенные имена. Имена должны давать представление о том, что это такое.
// bad function q() { // ...stuff... } // good function query() { // ..stuff.. }
-
15.2 Используйте
camelCase
, когда именуете объекты, функции и переменные.eslint:
camelcase
jscs:requireCamelCaseOrUpperCaseIdentifiers
Все популярные библиотеки для
javascript
используютcamelCase
, чтобы не путаться и соблюдать единообразие, пишем вcamelCase
. Исключением являются данные, которые передаются на сервер, там могут быть другие правила.// bad const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // good const thisIsMyObject = {}; function thisIsMyFunction() {}
-
15.3 Используйте
UpperCamelCase
, когда именуете классы или конструкторы классов.eslint:
new-cap
jscs:requireCapitalizedConstructors
// bad function user(options) { this.name = options.name; } const bad = new user({ name: 'nope', }); // good class User { constructor(options) { this.name = options.name; } } const good = new User({ name: 'yup', });
-
15.4 Используйте подчеркивание
_
в качестве префикса для именования приватных методов и переменных объекта.eslint:
no-underscore-dangle
jscs:disallowDanglingUnderscores
// bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; // good this._firstName = 'Panda';
-
15.5 Можете использовать
CONSTANT_CASE
для именования констант.const CAT_INIT_NUMBER_OF_LIVES = 9;
-
15.6 В
ES6
можно не сохранять ссылку наthis
. Используйте стрелочные функции илиFunction#bind
.jscs:
disallowNodeTypes
// bad function foo() { const self = this; return function () { console.log(self); }; } // bad function foo() { const that = this; return function () { console.log(that); }; } // good function foo() { return () => { console.log(this); }; }
Жить не можете без ссылки на
this
? Используйте тогда_this
.function() { const _this = this; return function() { console.log(_this); }; }