对所有的变量使用const
或let
声明,不要使用var
。"no-var": 2
// bad
var a = 1;
// good
const a = 1;
优先使用const
声明变量, 除非如果你一定要改变它的值,那就使用let
。"prefer-const": 1
, "no-const-assign": 2
// bad
let a = 1;
const b = 1;
b = b + 1;
// good
const a = 1;
let b = 1;
b = b + 1;
// good
const a = { b: 1 };
a.b = 2;
对使用const
和let
声明的变量进行分组。
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
创建有动态属性名的对象时,使用可被计算的属性名称。
为什么?因为这样可以让你在一个地方定义所有的对象属性。
function getKey(k) {
return `a key named ${k}`;
}
// bad
const obj = {
id: 5,
name: 'San Francisco'
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true
};
使用对象方法的简写。
// bad
const atom = {
value: 1,
addValue: function (value) {
return atom.value + value;
}
};
// good
const atom = {
value: 1,
addValue(value) {
return atom.value + value;
}
};
使用对象属性值的简写。
为什么?因为这样更短更有描述性。
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker
};
// good
const obj = {
lukeSkywalker
};
在对象属性声明前把简写的属性分组。
为什么?因为这样能清楚地看出哪些属性使用了简写。
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJedisWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJedisWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4
};
只有当属性名不合法时才用引号扩起来。"quote-props": [2, "as-needed"]
为什么?一般情况下我们考虑它更容易阅读,另外它能提供语法高亮,以及更容易被许多JS引擎优化。
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5
};
使用拓展运算符...
复制数组。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
使用Array.from
把一个类数组对象转换成数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
在数组方法的回调中使用return语句。"array-callback-return": 2
// bad
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = flatten;
});
// good
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = flatten;
return flatten;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map(x => x + 1);
使用解构存取和使用多属性对象。
为什么?因为解构能减少临时引用属性。
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// good
function getFullName(obj) {
const { firstName, lastName } = obj;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
对数组使用解构赋值。
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
需要回传多个值时,使用对象解构,而不是数组解构。
为什么?增加属性或者改变排序不会改变调用时的位置。
// bad
function processInput(input) {
return [left, right, top, bottom];
}
// 调用时需要考虑回调数据的顺序。
const [left, __, top] = processInput(input);
// good
function processInput(input) {
return { left, right, top, bottom };
}
// 调用时只选择需要的数据
const { left, right } = processInput(input);
字符串使用单引号''
。"quotes": [2, "single"]
// bad
const name = "Capt. Janeway";
// good
const name = 'Capt. Janeway';
程序化生成字符串时,使用模板字符串代替字符串连接,并且模版字符串中的变量两边没有空格${variable}
。"prefer-template": 1
,"template-curly-spacing": 2
为什么?模板字符串更为简洁,更具可读性。
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name) {
return `How are you, ${ name }?`;
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
在字符串中不要使用不必要的转义字符。"no-useless-escape": 2
为什么?降低了可读性,只有在需要的时候使用它们。
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `'this' is "quoted"`;
永远不要在一个非函数代码块(if、while等)中声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但它们的解析表现不一致。
注意: ECMA-262把block定义为一组语句。函数声明不是语句。阅读ECMA-262关于这个问题的说明。
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
不要使用arguments
,可以选择rest语法...
替代。
为什么?使用
...
能明确你要传入的参数。另外rest参数是一个真正的数组,而arguments`是一个类数组对象。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
直接给函数的参数指定默认值,不要使用一个变化的函数参数。
// bad
function handleThings(opts) {
// 如果参数opts是false或者空字符串的话,它就会被设定为一个对象。
opts = opts || {};
// ...
}
// bad
function handleThings(opts) {
if (!opts) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
总是把有默认值的参数放在最后。
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
不要使用Function构造函数来创建一个函数。
为什么?通过执行字符串这种方式创建函数就相当于调用
eval()
,会让代码变的脆弱。
// bad
var add = new Function('a', 'b', 'return a + b');
// still bad
var subtract = Function('a', 'b', 'return a - b');
=>
前后保留空格。"arrow-spacing": 2
// bad
a =>{};
a=> {};
// good
a => {};
当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数。
为什么?因为箭头函数创造了新的一个this执行环境,通常情况下都能满足你的需求,而且这样的写法更为简洁。
为什么不?当回调函数作为参数传入另一个函数,并且这个函数内部修改了回调函数的this执行环境时,不要使用箭头函数。
// bad
[1, 2, 3].map(function (x) {
return x * x;
});
// good
[1, 2, 3].map((x) => {
return x * x;
});
// good
element.addEventListener('click', function() {
// ...
});
如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和return
都省略掉。如果不是,那就不要省略。**注意:**当你返回的是一个对象时,请在对象的大括号外加上小扩号a => ({ x: 1 });
为什么?语法糖。在链式调用中可读性很高。
// good
[1, 2, 3].map(x => x * x);
// good
[1, 2, 3].reduce((total, n) => {
return total + n;
}, 0);
// good
a => ({ x: 1 });
总是使用class
,避免直接操作prototype
。
为什么? 因为
class
语法更为简洁易读。
// bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
// good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}
使用extends
继承。
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
return this._queue[0];
}
// good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}
class
在不指定构造函数的情况下就拥有一个默认的构造函数,一个空的构造函数或者仅仅调用了super
方法是没有必要的。"no-useless-constructor": 2
// bad
class Jedi {
constructor() {}
getName() {
return this.name;
}
}
// bad
class Rey extends Jedi {
constructor(...args) {
super(...args);
}
}
// good
class Rey extends Jedi {
constructor(...args) {
super(...args);
this.name = 'Rey';
}
}
不要出现重复命名的class的成员。"no-dupe-class-members": 2
为什么?重复命名的class成员默认后面的会生效,因此重复命名的情况基本可以认为是一个错误的书写。
// bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}
// good
class Foo {
bar() { return 1; }
}
不要修改class
声明的变量。"no-class-assign": 2
// bad
class A { }
A = 0;
总是使用模组ES6的模块系统(import/export),而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。
为什么?模块就是未来,让我们开始迈向未来吧。
// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;
// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;
// best
import { es6 } from './AirbnbStyleGuide';
export default es6;
对于一个路径只能import一次。"no-duplicate-imports": 2
// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';
// good
import foo, { named1, named2 } from 'foo';
import语句只能出现在文件头部,并且import绝对路径的语句(如node_modules下面的package)要放在import相对路径的语句前。"import/imports-first": [2, "absolute-first"]
// bad
import foo from './foo';
initWith(foo);
import bar from './bar';
// good
import foo from './foo';
import bar from './bar';
initWith(foo);
// bad
import foo from 'foo';
import bar from './bar';
import * as _ from 'lodash';
// good
import foo from 'foo';
import * as _ from 'lodash';
import bar from './bar';
最后一条import语句下应该保留一个空行。"import/newline-after-import": 2
// bad
import defaultExport from './foo';
const FOO = 'BAR';
// good
import defaultExport from './foo';
const FOO = 'BAR';
不允许重复导出相同name的变量以及default
。"import/export": 2
// bad
export const foo = function () { /*...*/ };
function bar() { /*...*/ }
export { bar as foo };
// bad
export default class MyClass { /*...*/ }
function makeClass() { return new MyClass(...arguments) }
export default makeClass;
不允许导出let修饰的变量。"import/no-mutable-exports": 2
// bad
let foo = 3;
export { foo };
// good
const foo = 3;
export { foo };
如果一个模块只对外导出一个变量,优先使用默认导出,而不是通过变量名导出。"import/prefer-default-export": 2
// There is only a single module export and its a named export.
// bad
export const foo = 'foo';
// good
const foo = 'foo';
export default foo;
generator的函数签名中空格的书写应为function* () {}
。"generator-star-spacing": [2, "after"]
为什么?
function
和*
是generator函数关键字的一部分,不同于function
,应该连在一起写。
// bad
function * foo() {}
function *foo() {}
function*foo() {}
const bar = function * () {};
const baz = function *() {};
const quux = function*() {};
// good
function* foo() {}
const bar = function* () {};
使用.
来访问对象的属性。"dot-notation": 2
const luke = {
jedi: true,
age: 28
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
当通过变量访问属性时使用中括号[]
。
const luke = {
jedi: true,
age: 28
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
优先使用===
和!==
,而不是==
和!=
。"eqeqeq": 2
如果在switch
的case
和default
从句中声明变量,从句最好被大括号包裹起来形成块级作用域。"no-case-declarations": 1
为什么?在
switch
的case
和default
从句中直接声明的变量,它们的作用域是整个switch语句,当我们尝试在多个case
语句中定义相同名字的变量就会引起问题。
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {}
break;
default:
class C {}
}
// good
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 {}
}
}
避免不必要的三元运算符。"no-unneeded-ternary": 2
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
使用大括号包裹代码块。
// bad
if (test)
return false;
// bad
if (test) return false;
// good
if (test) {
return false;
}
使用如下风格书写if-else
,try-catch
等语句。"brace-style": 2
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
使用/** ... */
作为多行注释。包含描述、指定所有参数和返回值的类型和值。
// 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
*
* @param {String} tag
* @return {Element} element
*/
function make(tag) {
// ...stuff...
return element;
}
使用//
作为单行注释。在评论对象上面另起一行使用单行注释。在注释前插入空行。
// bad
const active = true; // is current tab
// good
// is current tab
const active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
给注释增加FIXME
,TODO
或者XXX
的前缀可以帮助其他开发者快速了解这是一个需要复查的问题,或是给需要实现的功能提供一个解决方式。这将有别于常见的注释,因为它们是可操作的。FIXME
表示有待修改,TODO
表示有待实现,XXX
表示hack逻辑,黑科技以及实现不够优雅等情况。
// good
class Calculator {
constructor() {
// FIXME: shouldn't use a global here
total = 0;
}
}
// good
class Calculator {
constructor() {
// TODO: total should be configurable by an options param
this.total = 0;
}
}
使用4个空格作为缩进。"indent": [2, 4, { "SwitchCase": 1 }]
// bad
function() {
∙∙const name;
}
// good
function() {
∙∙∙∙const name;
}
在花括号前放一个空格。"space-before-blocks": 2
// 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'
});
在控制语句(if、while等)的小括号前放一个空格。"keyword-spacing": 2
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
使用空格把运算符隔开。"space-infix-ops": 2
// bad
const x=y+5;
// good
const x = y + 5;
在文件末尾插入一个空行。"eol-last": 2
// bad
(function(global) {
// ...stuff...
})(this);
// good
(function(global) {
// ...stuff...
})(this);↵
在块末和新语句前插入空行。
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;
// bad
const obj = {
foo() {
},
bar() {
}
};
return obj;
// good
const obj = {
foo() {
},
bar() {
}
};
return obj;
大括号内的开始和结束处不要留有空行。
// bad
function bar() {
console.log(foo);
}
// also bad
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
// good
function bar() {
console.log(foo);
}
// good
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
不要在圆括号内加空格。"space-in-parens": 2
// bad
function bar( foo ) {
return foo;
}
// good
function bar(foo) {
return foo;
}
// bad
if ( foo ) {
console.log(foo);
}
// good
if (foo) {
console.log(foo);
}
使用分号。"semi": 2
// bad
(function() {
const name = 'Skywalker'
return name
})()
// good
(() => {
const name = 'Skywalker';
return name;
})();
// good (防止函数在两个 IIFE 合并时被当成一个参数)
;(() => {
const name = 'Skywalker';
return name;
})();
避免单字母命名,命名应具备描述性。
// bad
function q() {
// ...stuff...
}
// good
function query() {
// ..stuff..
}
使用驼峰式命名对象、函数和引用。"camelcase": 2,
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
使用帕斯卡式命名构造函数或类。"new-cap": [2, { "capIsNew": false }]
// 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'
});
不要保存this
的引用。使用箭头函数或Function#bind
。
// 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);
};
}