一份最合理的 JavasScript 编码规范。 翻译自:https://github.com/airbnb/javascript
注意:本指南假定您使用的是Babel,并要求您使用 babel-preset-airbnb 或相当的预设插件。它还假定您正在您的应用程序中安装 shims/polyfills,实用airbnb-browser-shims或相当的插件。
目录
- 类型
- 引用
- 对象
- 数组
- 解构
- 字符串
- 函数
- 箭头函数
- 类 和 构造函数
- 模块
- 迭代器 和 生成器
- 属性
- 变量
- 提升
- 比较运算符 和 等号
- 代码块
- 控制语句
- 注释
- 空白
- 逗号
- 分号
- 类型转换
- 命名规则
- 存取器
- 事件
- jQuery
- ECMAScript 5 兼容性
- ECMAScript 6+ (ES 2015+) 编码风格
- 标准库
- 测试
- 性能
- 相关资源
类型
– 1.1 基本类型: 当您访问一个基本类型时,您将直接处理它的值。
string
number
boolean
null
undefined
symbol
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
- Symbols 不能被完全 polyfill, 所以不应该在目标浏览器/环境不支持它们的情况下使用它们。
– 1.2 复合类型: 当你访问一个复合类型时,你需要引用它的值。
object
array
function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
引用 References
– 2.1 对所有的引用使用 const
;不要使用 var
。eslint: prefer-const
, no-const-assign
为什么? 这可以确保你无法对引用重新分配,重新分配可能会导致 bug 和难以理解的代码。
// bad var a = 1; var b = 2; // good const a = 1; const b = 2;
– 2.2 如果你一定需要可变动的引用,使用 let
代替 var
。eslint: no-var
jscs: disallowVar
为什么?因为
let
是块级作用域,而var
是函数作用域。
// bad var count = 1; if (true) { count += 1; } // good, 使用 let. let count = 1; if (true) { count += 1; }
– 2.3 注意 let
和 const
都是块级作用域。
// const 和 let 只存在于定义它们的代码块内。 { let a = 1; const b = 1; } console.log(a); // ReferenceError,引用错误 console.log(b); // ReferenceError,引用错误
对象 Objects
– 3.1 使用字面量语法创建对象。 eslint: no-new-object
// bad const item = new Object(); // good const item = {};
– 3.2 当创建带有动态属性名称的对象时使用计算的属性名称。
为什么? 它们允许你在一个地方定义一个对象的所有属性。
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, };
– 3.3 使用对象方法速记语法。 eslint: object-shorthand
jscs: requireEnhancedObjectLiterals
// bad const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { value: 1, addValue(value) { return atom.value + value; }, };
– 3.4 使用对象属性速记语法。eslint: object-shorthand
jscs: requireEnhancedObjectLiterals
为什么?编写代码和描述更加简短。
const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker, }; // good const obj = { lukeSkywalker, };
– 3.5 将速记属性分组写在对象声明的开始处。
为什么?更容易看出哪些属性在使用速记语法。
const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // good const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, };
– 3.6 只用引号引无效标识符的属性。 eslint: quote-props
jscs: disallowQuotedKeysInObjects
为什么?一般来说,我们认为比较容易阅读。它改进了语法高亮显示,并且更容易被许多JS引擎优化。
// bad const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // good const good = { foo: 3, bar: 4, 'data-blah': 5, };
– 3.7 不要直接调用 Object.prototype
的方法,比如 hasOwnProperty
, propertyIsEnumerable
, 和 isPrototypeOf
.
为什么?这些方法可能会被对象的属性所覆盖 – 比如
{ hasOwnProperty: false }
– 或者,对象可能是空(null
)对象(Object.create(null)
)。
// bad console.log(object.hasOwnProperty(key)); // good console.log(Object.prototype.hasOwnProperty.call(object, key)); // best const has = Object.prototype.hasOwnProperty; // 在模块作用域内,缓存查找一次。 /* or */ import has from 'has'; // ... console.log(has.call(object, key));
– 3.8 用对象展开操作符浅复制对象,优先于Object.assign
。使用对象剩余操作符来获得一个省略某些属性的新对象。
// very bad const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // `original` 是可变的 ?_? delete copy.a; // so does this // bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good 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 }
数组 Arrays
– 4.1 使用字面量创建数组。 eslint: no-array-constructor
// bad const items = new Array(); // good const items = [];
– 4.2 在向数组添加元素时使用 Array#push 替代直接赋值。
const someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
– 4.3 使用数组展开操作符 ...
复制数组。
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
– 4.4 使用展开操作符 ...
代替 Array.from,来将一个类数组(array-like) 对象转换成数组。
const foo = document.querySelectorAll('.foo'); // good const nodes = Array.from(foo); // best const nodes = [...foo];
– 4.5 实用 Array.from 代替展开操作符 ...
来映射迭代,因为它避免了创建媒介数组。
// bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar);
– 4.6 在数组方法回调中使用 return
语句。如果函数体由一个返回无副作用的表达式的单个语句组成,那么可以省略返回值,查看8.2 说明。 eslint: array-callback-return
// good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // good [1, 2, 3].map(x => x + 1); // bad - 没有返回值意味着 `memo` 在第一次迭代后变成 undefined [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; }); // good [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; return flatten; }); // bad inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // good inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; });
– 4.7 如果数组有多行,请在打开和关闭数组括号之前使用换行符。
// bad const arr = [ [0, 1], [2, 3], [4, 5], ]; const objectInArray = [{ id: 1, }, { id: 2, }]; const numberInArray = [ 1, 2, ]; // good const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ];
解构 Destructuring
– 5.1 当访问和使用对象的多个属性时,请使用对象解构。eslint: prefer-destructuring
jscs: requireObjectDestructuring
为什么?解构可以在你建这些属性的临时引用时,为你节省时间。
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // best function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; }
– 5.2 使用数组解构。 eslint: prefer-destructuring
jscs: requireArrayDestructuring
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
– 5.3 使用对象解构来实现多个返回值,而不是数组解构。jscs: disallowArrayDestructuringReturn
为什么? 您可以随着时间的推移添加新的属性或更改排序,而不会改变调用时的位置。
// 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, top } = processInput(input);
字符串 Strings
– 6.1 字符串使用单引号 ''
。 eslint: quotes
jscs: validateQuoteMarks
// bad const name = "Capt. Janeway"; // bad - 模板字面量应该包含插值或换行符 const name = `Capt. Janeway`; // good const name = 'Capt. Janeway';
– 6.2 超过100个字符,导致换行的字符串不应使用字符串连接符写成多行。
为什么? 连接字符串是痛苦的工作,而且使代码不易于搜索。
// 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.';
– 6.3 以编程方式构建字符串时,请使用模板字符串而不是字符串连接。eslint: prefer-template
template-curly-spacing
jscs: requireTemplateStrings
为什么? 模板字符串为你提供了更好的可读性,简洁的语法,正确的换行符和字符串插值功能。
// 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}?`; }
– 6.4 永远不要在字符串上使用 eval()
,它会打开太多的漏洞。 eslint: no-eval
– 6.5 不要转义字符串中不必要转义的字符。 eslint: no-useless-escape
为什么?反斜杠会破坏可读性,因此只有在必要时才转义。
// bad const foo = '\'this\' \i\s \"quoted\"'; // good const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`;
函数 Functions
– 7.1 使用命名函数表达式而不是函数声明。 eslint: func-style
jscs: disallowFunctionDeclarations
为什么? 函数声明很容易被提升(Hoisting),你可以在函数被定义之前引用该函数。这对可读性和可维护性来说都是不利的。如果你发现一个函数的定义很大或很复杂,以至于妨碍了解文件的其他部分,那么也许是时候把它提取到自己的模块中去!不要忘记显式地命名表达式,不管该名称是否从包含变量中推断出来的(在现代浏览器中,或在使用编译器如Babel 时经常出现这种情况)。这消除了关于Error的调用堆栈的任何假设。(讨论)
// bad function foo() { // ... } // bad const foo = function () { // ... }; // good // 用明显区别于变量引用调用的词汇命名 const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... };
– 7.2 用圆括号包裹立即调用函数表达式 (IIFE)。 eslint: wrap-iife
jscs: requireParenthesesAroundIIFE
为什么?一个立即调用函数表达式是一个单独的单元 – 将函数表达式包裹在括号中,后面再跟一个调用括号,这看上去很赶紧。请注意,在模块的世界中,你几乎不需要 IIFE。
// 立即调用函数表达式 (IIFE) (function () { console.log('Welcome to the Internet. Please follow me.'); }());
– 7.3 永远不要在一个非函数代码块(if
、while
等)中声明一个函数,把那个函数赋给一个变量代替。浏览器允许你这么做,但是它们都以不同的方式解析。 eslint: no-loop-func
– 7.4 注意: ECMA-262 把 block
定义为一组语句。函数声明不是语句。
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
– 7.5 永远不要把参数命名为 arguments
。这将会覆盖原来函数作用域内的 arguments
对象。
// bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... }
– 7.6 不要使用 arguments
。可以选择 rest 语法 ...
替代。eslint: prefer-rest-params
为什么?使用
...
能明确你要传入的参数。另外 rest(剩余)参数是一个真正的数组,而arguments
是一个类数组(Array-like)。
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
– 7.7 使用默认参数语法,而不要使用一个变化的函数参数。
// really bad function handleThings(opts) { // 不!我们不应该改变函数参数。 // 更加糟糕: 如果参数 opts 是 falsy(假值) 的话,它将被设置为一个对象, // 这可能是你想要的,但它可以引起一些小的错误。 opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }
– 7.8 避免默认参数的副作用。
为什么?因为这样写会让人感到很困惑。
var b = 1; // bad function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3
– 7.9 始终将默认参数放在最后。
// bad function handleThings(opts = {}, name) { // ... } // good function handleThings(name, opts = {}) { // ... }
– 7.10 切勿使用 Function 构造函数来创建新函数。 eslint: no-new-func
为什么? 以这种方式创建一个函数,与 eval() 类似,会对字符串求值,这会打开漏洞。
// bad var add = new Function('a', 'b', 'return a + b'); // still bad var subtract = Function('a', 'b', 'return a - b');
– 7.11 隔开函数签名,括号两边用空格隔开。 eslint: space-before-function-paren
space-before-blocks
为什么?这样做有益代码的一致性,添加或删除函数名时不需要添加或删除空格。
// bad const f = function(){}; const g = function (){}; const h = function() {}; // good const x = function () {}; const y = function a() {};
– 7.12 不要改变参数。 eslint: no-param-reassign
为什么?操作作为参数传入的对象,可能会在调用原始对象时造成不必要的变量副作用。(注:对象是引用类型)
// bad function f1(obj) { obj.key = 1; } // good function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }
– 7.13 参数不要重新赋值。 eslint: no-param-reassign
为什么? 重新分配参数可能会导致意外的行为,特别是在访问
arguments
对象时。它也可能导性能化问题,特别是在V8中。
// bad function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // good function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... }
– 7.14 优先使用展开运算符 ...
来调用可变参数函数。 eslint: prefer-spread
为什么? 它更清洁,你不需要提供一个上下文,而且你不能轻易地实用
apply
和new
。
// bad const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // good const x = [1, 2, 3, 4, 5]; console.log(...x); // bad new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // good new Date(...[2016, 8, 5]);
– 7.15 具有多行签名或调用的函数,应该像本指南中的其他多行列表一样缩进:每一项都独占一行,最后一项上有一个尾逗号。
// bad function foo(bar, baz, quux) { // ... } // good function foo( bar, baz, quux, ) { // ... } // bad console.log(foo, bar, baz); // good console.log( foo, bar, baz, );
箭头函数 Arrow Functions
– 8.1 当您必须使用匿名函数(如在传递一个内联回调时),请使用箭头函数表示法。 eslint: prefer-arrow-callback
, arrow-spacing
jscs: requireArrowFunctions
为什么? 它创建了一个在
this
上下文中执行的函数的版本,这通常是你想要的,而且这样的写法更为简洁。(注:参考 Arrow functions – JavaScript | MDN 和 ES6 arrow functions, syntax and lexical scoping)为什么不? 如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个声明函数上。
// 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; });
– 8.2 如果函数体由一个返回无副作用(side effect)的expression(表达式)的单行语句组成,那么可以省略大括号并使用隐式返回。否则,保留大括号并使用 return
语句。eslint: arrow-parens
, arrow-body-style
jscs: disallowParenthesesAroundArrowParam
, requireShorthandArrowFunctions
注,什么是副作用(side effect)?一段代码,即在不需要的情况下,创建一个变量并在整个作用域内可用。
为什么? 语法糖。 当多个函数链式调用时,可读性更高。
// bad [1, 2, 3].map(number => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map(number => `A string containing the ${number}.`); // good [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map((number, index) => ({ [index]: number, })); // No implicit return with side effects function foo(callback) { const val = callback(); if (val === true) { // Do something if callback returns true } } let bool = false; // bad foo(() => bool = true); // good foo(() => { bool = true; });
– 8.3 如果表达式跨多行,将其包裹在括号中,可以提高可读性。
为什么? 它清楚地显示了函数开始和结束的位置。
// bad ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // good ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ));
– 8.4 如果你的函数只有一个参数并且不使用大括号,则可以省略参数括号。否则,为了清晰和一致性,总是给参数加上括号。
注意:总是使用圆括号也是可以被lint工具接受的,在这种情况下 使用 eslint 的 “always” 选项,或者 jscs 中不要包含 disallowParenthesesAroundArrowParam
选项。 eslint: arrow-parens
jscs: disallowParenthesesAroundArrowParam
为什么? 不造成视觉上的混乱。
// bad [1, 2, 3].map((x) => x * x); // good [1, 2, 3].map(x => x * x); // good [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!` )); // bad [1, 2, 3].map(x => { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
– 8.5 避免使用比较运算符(< =
, >=
)时,混淆箭头函数语法(=>
)。 eslint: no-confusing-arrow
// bad const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize; // bad const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize; // good const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize); // good const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height > 256 ? largeSize : smallSize; };
类 Classes & 构造函数 Constructors
– 9.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; } }
– 9.2 使用 extends
继承。
为什么?因为
extends
是一个内置的原型继承方法并且不会破坏instanceof
。
// 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]; } }
– 9.3 方法可以返回 this
来帮助链式调用。
// bad 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 // good class Jedi { jump() { this.jumping = true; return this; } setHeight(height) { this.height = height; return this; } } const luke = new Jedi(); luke.jump() .setHeight(20);
– 9.4 可以写一个自定义的 toString() 方法,但要确保它能正常运行并且不会引起副作用。
class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } }
– 9.5 如果没有指定,类有一个默认的构造函数。一个空的构造函数或者只是委托给父类则不是必须的。 eslint: no-useless-constructor
// 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'; } }
– 9.6 避免重复类成员。 eslint: no-dupe-class-members
为什么? 重复类成员声明将默认使用最后一个 – 重复类成员几乎肯定是一个错误。
// bad class Foo { bar() { return 1; } bar() { return 2; } } // good class Foo { bar() { return 1; } } // good class Foo { bar() { return 2; } }
模块 Modules
– 10.1 总是使用模块 (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;
– 10.2 不要使用通配符 import(导入)。
为什么?这样能确保你只有一个默认 export(导出)。
// bad import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // good import AirbnbStyleGuide from './AirbnbStyleGuide';
– 10.3 不要从 import(导入) 中直接 export(导出)。
为什么?虽然一行代码简洁明了,但有一个明确的 import(导入) 方法和一个明确的 export(导出) 方法,使事情能保持一致。
// bad // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // good // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6;
– 10.4 一个地方只在一个路径中 import(导入) 。
eslint: no-duplicate-imports
为什么? 从同一路径 import(导入) 多个模块分散在多行代码中,可能会使代码难以维护。
// bad import foo from 'foo'; // … 其他一些 imports … // import { named1, named2 } from 'foo'; // good import foo, { named1, named2 } from 'foo'; // good import foo, { named1, named2, } from 'foo';
– 10.5 不要 export(导出) 可变绑定。eslint: import/no-mutable-exports
为什么? 一般应该避免可变性,特别是在导出可变绑定时。虽然一些特殊情况下,可能需要这种技术,但是一般而言,只应该导出常量引用。
// bad let foo = 3; export { foo }; // good const foo = 3; export { foo };
– 10.6 在只有单个导出的模块中,默认 export(导出) 优于命名 export(导出)。eslint: import/prefer-default-export
为什么?为了鼓励更多的文件只有一个 export(导出),这有利于模块的可读性和可维护性。
// bad export function foo() {} // good export default function foo() {}
– 10.7 将所有 import
导入放在非导入语句的上面。eslint: import/first
由于
import
被提升,保持他们在顶部,防止意外的行为。
// bad import foo from 'foo'; foo.init(); import bar from 'bar'; // good import foo from 'foo'; import bar from 'bar'; foo.init();
– 10.8 多行导入应该像多行数组和对象字面量一样进行缩进。
为什么? 花括号应遵循与编码风格指南中的每个其他花括号相同的缩进规则,末尾的逗号也一样。
// bad import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // good import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path';
– 10.9 禁止在模块 import(导入) 语句中使用 Webpack 加载器语法。
eslint: import/no-webpack-loader-syntax
为什么?由于在 import(导入) 中使用 Webpack 语法会将代码耦合到模块打包器。 首选在
webpack.config.js
中使用加载器语法。
// bad import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // good import fooSass from 'foo.scss'; import barCss from 'bar.css';
迭代器 Iterators 和 生成器 Generators
– 11.1 不要使用 iterators(迭代器) 。请使用高阶函数,例如 map()
和 reduce()
等,而不是像 for-in
或 for-of
这样的循环。 eslint: no-iterator
no-restricted-syntax
为什么? 这是强制执行我们不变性的规则。 处理返回值的纯函数比 Side Effects(副作用) 更容易推理。
使用
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
/ … 来迭代数组, 使用Object.keys()
/Object.values()
/Object.entries()
来生成数组,以便可以迭代对象。
const numbers = [1, 2, 3, 4, 5]; // bad let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // good let sum = 0; numbers.forEach((num) => { sum += num; }); sum === 15; // best (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // bad const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // good const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // best (keeping it functional) const increasedByOne = numbers.map(num => num + 1);
– 11.2 现在不要使用 generators (生成器)。
为什么? 因为目前没有很好地办法将他们转译成 ES5 。
– 11.3 如果您必须使用 generators (生成器),或者如果漠视我们的建议,请确保它们的函数签名恰当的间隔。 eslint: generator-star-spacing
为什么?
function
和*
都是同一概念关键字的组成部分 –*
不是function
的修饰符,function*
是一个独特的构造,与function
不同。
// bad function * foo() { // ... } // bad const bar = function * () { // ... }; // bad const baz = function *() { // ... }; // bad const quux = function*() { // ... }; // bad function*foo() { // ... } // bad function *foo() { // ... } // very bad function * foo() { // ... } // very bad const wat = function * () { // ... }; // good function* foo() { // ... } // good const foo = function* () { // ... };
属性 Properties
– 12.1 使用 点语法(.
) 来访问对象的属性。 eslint: dot-notation
jscs: requireDotNotation
const luke = { jedi: true, age: 28, }; // bad const isJedi = luke['jedi']; // good const isJedi = luke.jedi;
– 12.2 当通过变量访问属性时使用中括号 []
。
const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi');
– 12.3 求幂时使用求幂运算符 **
。eslint: no-restricted-properties
.
// bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10;
变量 Variables
– 13.1 总是使用 const
或 let
来声明变量。 不这样做会导致产生全局变量。 我们希望避免污染全局命名空间。 eslint: no-undef
prefer-const
// bad superPower = new SuperPower(); // good const superPower = new SuperPower();
– 13.2 使用 const
或 let
声明每个变量。 eslint: one-var
jscs: disallowMultipleVarDecl
为什么? 以这种方式添加新的变量声明更容易,你永远不必担心是否需要将
,
换成;
,或引入标点符号差异。您也可以在调试器中遍历每个声明,而不是一次跳过所有的变量。
// bad const items = getItems(), goSportsTeam = true, dragonball = 'z'; // bad // (与上面的比较,并尝试找出错误) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // good const items = getItems(); const goSportsTeam = true; const dragonball = 'z';
– 13.3 将所有的 const
和 let
分组 。
为什么?当你需要把已分配的变量分配给一个变量时非常有用。
// bad let i, len, dragonball, items = getItems(), goSportsTeam = true; // 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;
– 13.4 在你需要的地方分配变量,但请把它们放在一个合理的位置。
为什么?
let
和const
是块级作用域而不是函数作用域。
// bad - 不必要的函数调用 function checkName(hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; } // good function checkName(hasName) { if (hasName === 'test') { return false; } const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; }
– 13.5 变量不要链式赋值。eslint: no-multi-assign
为什么? 链接变量赋值会创建隐式全局变量。
// bad (function example() { // JavaScript 将其解析为 // let a = ( b = ( c = 1 ) ); // let关键字只适用于变量a; // 变量b和c变成了全局变量。 let a = b = c = 1; }()); console.log(a); // 抛出 ReferenceError(引用错误) console.log(b); // 1 console.log(c); // 1 // good (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // 抛出 ReferenceError(引用错误) console.log(b); // 抛出 ReferenceError(引用错误) console.log(c); // 抛出 ReferenceError(引用错误) // 同样适用于 `const`
– 13.6 避免使用一元递增和递减运算符(++
, --
)。 eslint no-plusplus
为什么? 根据 eslint 文档,一元递增和递减语句会受到自动插入分号的影响,并可能导致应用程序中的值递增或递减,从而导致无提示错误。使用像
num += 1
而不是num++
或num ++
这样的语句来改变你的值也更具有表现力。不允许一元递增和递减语句也会阻止您无意中预先递增/递减值,这也会导致程序中的意外行为。
// bad 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++; } } // good 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;
Hoisting
– 14.1 var
声明会被提升至他们作用域的顶部,但它们赋值不会提升。let
和 const
声明被赋予了一种称为「暂时性死区(Temporal Dead Zones, TDZ)」的概念。这对于了解为什么 type of 不再安全相当重要。
// 我们知道这样运行不了 // (假设没有 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; }
– 14.2 匿名函数表达式的变量名会被提升,但函数分配不会。
function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function 输入错误,anonymous 不是一个函数 var anonymous = function () { console.log('anonymous function expression'); }; }
– 14.3 命名的函数表达式的变量名会被提升,但函数名和函数体并不会。
function example() { console.log(named); // => undefined named(); // => TypeError named is not a function,输入错误,named 不是一个函数 superPower(); // => ReferenceError superPower is not defined, ReferenceError(引用错误)superPower 未定义 var named = function superPower() { console.log('Flying'); }; } // 当函数名称与变量名称相同时 // 也是如此。 function example() { console.log(named); // => undefined named(); // => TypeError named is not a function,输入错误,named 不是一个函数 var named = function named() { console.log('named'); }; }
– 14.4 函数声明的名称和函数体都会被提升。
function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } }
- 想了解更多信息,参考 Ben Cherry 的 JavaScript Scoping & Hoisting 。
比较运算符 Comparison Operators 和 等号 Equality
– 15.1 使用 ===
和 !==
优先于 ==
和 !=
。 eslint: eqeqeq
– 15.2 诸如 if
语句之类的条件语句使用 ToBoolean
抽象方法来强制求值它们的表达式,并始终遵循以下简单规则:
- Objects 求值为 true
- Undefined 求值为 false
- Null 求值为 false
- Booleans 求值为 布尔值
- Numbers 如果是 +0、-0、或 NaN 求值为 false , 否则为 true
- Strings 如果是空字符串
''
求值为 false , 否则为 true
if ([0] && []) { // true // 一个数组 (即使是一个空数组) 是一个 object, objects 被求值为 true }
– 15.3 对于布尔值使用简写,但对于字符串和数字使用显式比较。
// bad if (isValid === true) { // ... } // good if (isValid) { // ... } // bad if (name) { // ... } // good if (name !== '') { // ... } // bad if (collection.length) { // ... } // good if (collection.length > 0) { // ... }
– 15.4 想了解更多信息,参考 Angus Croll 的 Truth Equality and JavaScript。
– 15.5 在 case
和 default
子句中,使用大括号来创建包含词法声明的语句块(例如 let
, const
, function
, 和 class
). eslint: no-case-declarations
为什么? 词法声明在整个
switch
语句块中都是可见的,但是只有在分配时才被初始化,这只有当它到达case
时才会发生。这在多个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 {} } }
– 15.6 三元表达式不应该嵌套,通常写成单行表达式。 eslint: no-nested-ternary
// bad const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // 拆分成2个分离的三元表达式 const maybeNull = value1 > value2 ? 'baz' : null; // better const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // best const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
– 15.7 避免不必要的三元表达式语句。 eslint: no-unneeded-ternary
// 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;
– 15.8 当运算符混合在一个语句中时,请将其放在括号内。混合算术运算符时,不要将 **
和 %
与 +
, -
,*
,/
混合在一起。eslint: no-mixed-operators
为什么? 这可以提高可读性,并清晰展现开发者的意图。
// bad const foo = a && b < 0 || c > 0 || d + 1 === 0; // bad const bar = a ** b - 5 % d; // bad if (a || b && c) { return d; } // good const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // good const bar = (a ** b) - (5 % d); // good if ((a || b) && c) { return d; } // good const bar = a + b / c * d;
代码块 Blocks
– 16.1 使用大括号包裹所有的多行代码块。 eslint: nonblock-statement-body-position
// 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; }
– 16.2 如果通过 if
和 else
使用多行代码块,把 else
放在 if
代码块闭合括号的同一行。eslint: brace-style
jscs: disallowNewlineBeforeBlockStatements
// bad if (test) { thing1(); thing2(); } else { thing3(); } // good if (test) { thing1(); thing2(); } else { thing3(); }
– 16.3 如果一个 if
块总是执行一个 return
语句,后面的 else
块是不必要的。在 else if
块中的 return
,可以分成多个 if
块来 return
。eslint: no-else-return
// bad function foo() { if (x) { return x; } else { return y; } } // bad function cats() { if (x) { return x; } else if (y) { return y; } } // bad function dogs() { if (x) { return x; } else { if (y) { return y; } } } // good function foo() { if (x) { return x; } return y; } // good function cats() { if (x) { return x; } if (y) { return y; } } //good function dogs(x) { if (x) { if (z) { return y; } } else { return z; } }
控制语句 Control Statements
- 17.1 如果您的控制语句(
if
,while
的)太长或超过最大行长度,那么每个(分组)条件可以放单独一行。逻辑运算符应该放在每行起始处。
为什么? 在每行起始处要求运算符可以使运算符保持一致,并遵循与方法链式调用类似的模式。这样可以使复杂逻辑更易于查看,以提高可读性。
// bad if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // bad if (foo === 123 && bar === 'abc') { thing1(); } // bad if (foo === 123 && bar === 'abc') { thing1(); } // bad if ( foo === 123 && bar === 'abc' ) { thing1(); } // good if ( foo === 123 && bar === 'abc' ) { thing1(); } // good if ( (foo === 123 || bar === "abc") && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // good if (foo === 123 && bar === 'abc') { thing1(); }
注释 Comments
– 18.1 多行注释使用 /** ... */
。
// bad // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
– 18.2 单行注释使用 //
。将单行注释放在续注释的语句上方。在注释之前放置一个空行,除非它位于代码块的第一行。
// 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; } // also good function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; }
– 18.3 所有注释符和注释内容用一个空格隔开,让它更容易阅读。 eslint: spaced-comment
// bad //is current tab const active = true; // good // is current tab const active = true; // bad /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // good /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
– 18.4 给注释增加 FIXME
或 TODO
的前缀,可以帮助其他开发者快速了解这个是否是一个需要重新复查的问题,或是你正在为需要解决的问题提出解决方案。这将有别于常规注释,因为它们是可操作的。使用 FIXME -- need to figure this out
或者 TODO -- need to implement
。
– 18.5 使用 // FIXME:
来标识需要修正的问题。注:如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明。
class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; } }
– 18.6 使用 // TODO:
来标识需要实现的问题。注:如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会简略说明。
class Calculator extends Abacus { constructor() { super(); // TODO: total should be configurable by an options param this.total = 0; } }
注:还有 // XXX:
注释,如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明。部分 IDE 有这些注释的收集视图,例如任务(task)视图,TODO视图等,在项目发布前,检查一下任务视图是一个很好的习惯。
空白 Whitespace
– 19.1 使用 2 个空格作为缩进。 eslint: indent
jscs: validateIndentation
// bad function foo() { ????let name; } // bad function bar() { ?let name; } // good function baz() { ??let name; }
– 19.2 在大括号前放置 1 个空格。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', });
– 19.3 在控制语句(if
、while
等)的小括号前放一个空格。在函数调用及声明中,不在函数的参数列表前加空格。 eslint: keyword-spacing
jscs: requireSpaceAfterKeywords
// bad if(isJedi) { fight (); } // good if (isJedi) { fight(); } // bad function fight () { console.log ('Swooosh!'); } // good function fight() { console.log('Swooosh!'); }
– 19.4 使用空格把运算符隔开。 eslint: space-infix-ops
jscs: requireSpaceBeforeBinaryOperators
, requireSpaceAfterBinaryOperators
// bad const x=y+5; // good const x = y + 5;
– 19.5 在文件末尾插入一个空行。 eslint: eol-last
// bad import { es6 } from './AirbnbStyleGuide'; // ... export default es6;
// bad import { es6 } from './AirbnbStyleGuide'; // ... export default es6;? ?
// good import { es6 } from './AirbnbStyleGuide'; // ... export default es6;?
– 19.6 长方法链式调用时使用缩进(2个以上的方法链式调用)。使用一个点 .
开头,强调该行是一个方法调用,不是一个新的声明。eslint: newline-per-chained-call
no-whitespace-before-property
// bad $('#items').find('.selected').highlight().end().find('.open').updateCount(); // bad $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // good $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // bad 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); // good 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); // good const leds = stage.selectAll('.led').data(data);
– 19.7 在语句块后和下条语句前留一个空行。jscs: requirePaddingNewLinesAfterBlocks
// 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 const arr = [ function foo() { }, function bar() { }, ]; return arr; // good const arr = [ function foo() { }, function bar() { }, ]; return arr;
– 19.8 不要用空行来填充块。 eslint: padded-blocks
jscs: disallowPaddingNewlinesInBlocks
// bad function bar() { console.log(foo); } // bad if (baz) { console.log(qux); } else { console.log(foo); } // bad class Foo { constructor(bar) { this.bar = bar; } } // good function bar() { console.log(foo); } // good if (baz) { console.log(qux); } else { console.log(foo); }
– 19.9 不要在圆括号内加空格。 eslint: space-in-parens
jscs: disallowSpacesInsideParentheses
// bad function bar( foo ) { return foo; } // good function bar(foo) { return foo; } // bad if ( foo ) { console.log(foo); } // good if (foo) { console.log(foo); }
– 19.10 不要在中括号内添加空格。 eslint: array-bracket-spacing
jscs: disallowSpacesInsideArrayBrackets
// bad const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // good const foo = [1, 2, 3]; console.log(foo[0]);
– 19.11 在大括号内添加空格。 eslint: object-curly-spacing
jscs: requireSpacesInsideObjectBrackets
// bad const foo = {clark: 'kent'}; // good const foo = { clark: 'kent' };
– 19.12 避免有超过100个字符(包括空格)的代码行。注意:根据上面的规则,长字符串可以免除这个规则,不应该被破坏。eslint: max-len
jscs: maximumLineLength
为什么? 这可以确保可读性和可维护性。
// bad const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // 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 = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // good $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.'));
逗号 Commas
– 20.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', };
– 20.2 添加结尾的逗号。 eslint: comma-dangle
jscs: requireTrailingComma
为什么?这会让 git diff(差异比较) 更干净。另外,像Babel这样的转译器会删除转译后代码中的结尾逗号,这意味着您不必担心传统浏览器中的结尾逗号问题。
// bad - 没有结尾逗号的 git diff 差异比较 const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // good - 有结尾逗号的 git diff 差异比较 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', ]; // bad function createHero( firstName, lastName, inventorOf ) { // does nothing } // good function createHero( firstName, lastName, inventorOf, ) { // does nothing } // good (请注意,逗号不能出现在 “rest” 元素的后面) function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // does nothing } // bad createHero( firstName, lastName, inventorOf ); // good createHero( firstName, lastName, inventorOf, ); // good (请注意,逗号不能出现在 “rest” 元素的后面) createHero( firstName, lastName, inventorOf, ...heroArgs );
分号 Semicolons
– 21.1 当然要使用封号 eslint: semi
jscs: requireSemicolons
为什么? 当 JavaScript 遇到没有分号的换行符时,它使用一组称为自动分号插入的规则来确定是否应该将换行符视为语句的结尾,并且(顾名思义)如果被这样认为的话,在换行符前面自动插入一个分号。ASI(自动分号插入)包含了一些稀奇古怪的的行为,不过,如果 JavaScript 错误地解释了你的换行符,你的代码将会被中断执行。随着新功能成为 JavaScript 的一部分,这些规则将变得更加复杂。明确地结束你的语句并配置你的 linter 来捕获缺少的分号,将有助于防止遇到问题。
// bad - 引发异常 const luke = {} const leia = {} [luke, leia].forEach(jedi => jedi.father = 'vader') // bad - 引发异常 const reaction = "No! That's impossible!" (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // bad - 返回`undefined`,而不是下一行的值 - 当 `return` 独占一行时,自动分号插入总是会发生。 function foo() { return 'search your feelings, you know it to be foo' } // good const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // good const reaction = "No! That's impossible!"; (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // good function foo() { return 'search your feelings, you know it to be foo'; }
更多阅读.
类型转换 Type Casting & Coercion
– 22.1 在声明语句的开始处就执行强制类型转换.
– 22.2 字符串: eslint: no-new-wrappers
// => this.reviewScore = 9; // bad const totalScore = new String(this.reviewScore); // typeof totalScore 是 "object" 而不是 "string" // bad const totalScore = this.reviewScore + ''; // 调用 this.reviewScore.valueOf() // bad const totalScore = this.reviewScore.toString(); // 不能保证返回一个字符串 // good const totalScore = String(this.reviewScore);
– 22.3 数字: 使用 Number
进行转换,而 parseInt
则始终以基数解析字串。 eslint: radix
no-new-wrappers
const inputValue = '4'; // bad const val = new Number(inputValue); // bad const val = +inputValue; // bad const val = inputValue >> 0; // bad const val = parseInt(inputValue); // good const val = Number(inputValue); // good const val = parseInt(inputValue, 10);
– 22.4 如果你因为某个原因正在做些疯狂的事情,但是 parseInt
是你的瓶颈,所以你对于 性能方面的原因而必须使用位运算,请留下评论并解释为什么使用,及你做了哪些事情。
// good /** * parseInt was the reason my code was slow. * Bitshifting the String to coerce it to a * Number made it a lot faster. */ const val = inputValue >> 0;
– 22.5 注意: 使用位运算请小心。 数字使用 64位值表示, 但是位运算只返回32位整数 (来源)。 小于32位整数的位运算会导致不可预期的行为. 讨论。最大的有符号整数是 2,147,483,647:
2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647
– 22.6 布尔值: eslint: no-new-wrappers
const age = 0; // bad const hasAge = new Boolean(age); // good const hasAge = Boolean(age); // best const hasAge = !!age;
命名规则 Naming Conventions
– 23.1 避免使用单字母名称。使你的命名具有描述性。 eslint: id-length
// bad function q() { // ... } // good function query() { // ... }
– 23.2 当命名对象,函数和实例时使用驼峰式命名。 eslint: camelcase
jscs: requireCamelCaseOrUpperCaseIdentifiers
// bad const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // good const thisIsMyObject = {}; function thisIsMyFunction() {}
– 23.3 当命名构造函数或类的时候使用 PascalCase 式命名,(注:即单词首字母大写)。 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', });
– 23.4 不要使用下划线开头或结尾。 eslint: no-underscore-dangle
jscs: disallowDanglingUnderscores
为什么? JavaScript 对于属性或方法而言并没有私有的概念。虽然下划线开头通常意味着 ‘private’(私有)是通用的惯例,事实上,这些属性是完全公开的,是公开API的一部分。 这个惯例可能会导致开发人员错误地认为这不重要或者测试也不必要。简而言之:如果你想让其 “private”, 必须使其不可见。
// bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // good this.firstName = 'Panda';
– 23.5 不要存储 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); }; }
– 23.6 basename 应与其默认导出的名称正好匹配。(注:basename 指的是文件名)
// file 1 contents class CheckBox { // ... } export default CheckBox; // file 2 contents export default function fortyTwo() { return 42; } // file 3 contents export default function insideDirectory() {} // in some other file // bad import CheckBox from './checkBox'; // import/export 单词首字母大写命名 , filename 驼峰式命名 import FortyTwo from './FortyTwo'; // import/filename 单词首字母大写命名, export 驼峰式命名 import InsideDirectory from './InsideDirectory'; // import/filename 单词首字母大写命名, export 驼峰式命名 // bad import CheckBox from './check_box'; // import/export 单词首字母大写命名, filename 下划线命名 import forty_two from './forty_two'; // import/filename 下划线命名, export 驼峰式命名 import inside_directory from './inside_directory'; // import 下划线命名, export 驼峰式命名 import index from './inside_directory/index'; // 明确地 require 索引文件 import insideDirectory from './insideDirectory/index'; // 明确地 require 索引文件 // good import CheckBox from './CheckBox'; // export/import/filename 单词首字母大写命名 import fortyTwo from './fortyTwo'; // export/import/filename 驼峰式命名 import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" // ^ supports both insideDirectory.js and insideDirectory/index.js
– 23.7 当 导出(export) 一个默认函数时使用驼峰式命名。你的文件名应该和你的函数的名字一致。
function makeStyleGuide() { // ... } export default makeStyleGuide;
– 23.8 当导出一个 构造函数 / 类 / 单例 / 函数库 / 纯对象时使用 PascalCase 式命名,(注:即单词首字母大写)。
const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide;
– 23.9 首字母缩写词应该总是全部大写,或全部小写。
为什么? 名字是更具可读性,而不是为了满足计算机算法。
// bad import SmsContainer from './containers/SmsContainer'; // bad const HttpRequests = [ // ... ]; // good import SMSContainer from './containers/SMSContainer'; // good const HTTPRequests = [ // ... ]; // also good const httpRequests = [ // ... ]; // best import TextMessageContainer from './containers/TextMessageContainer'; // best const requests = [ // ... ];
存取器 Accessors
– 24.1 属性的存取器函数不是必须的。
– 24.2 別使用 JavaScript 的 getters/setters,因为它们会导致意想不到的副作用,而且很难测试,维护和理解。相反,如果要使用存取器函数,使用 getVal() 及 setVal(‘hello’)。
// bad class Dragon { get age() { // ... } set age(value) { // ... } } // good class Dragon { getAge() { // ... } setAge(value) { // ... } }
– 24.3 如果属性/方法是一个 boolean
, 使用 isVal()
或 hasVal()
方法。
// bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; }
– 24.4 也可以创建 get() 和 set() 函数, 但要保持一致。
class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } }
事件 Events
– 25.1 将绑定数据到事件时 (不论是 DOM 事件还是其他像Backbone一类的事件), 传递 hash 而不是原始值。 这将允许后续的贡献者不用查找和更新事件的每一个处理程序就可以给事件添加更多的数据。例如,不要使用下边的:
// bad $(this).trigger('listingUpdated', listing.id); // ... $(this).on('listingUpdated', (e, listingId) => { // do something with listingId });
prefer:
// good $(this).trigger('listingUpdated', { listingId: listing.id }); // ... $(this).on('listingUpdated', (e, data) => { // do something with data.listingId });
jQuery
– 26.1 jQuery 对象变量命名以 $
为前缀。 jscs: requireDollarBeforejQueryAssignment
// bad const sidebar = $('.sidebar'); // good const $sidebar = $('.sidebar'); // good const $sidebarBtn = $('.sidebar-btn');
– 26.2 缓存 jQuery 选择器的查询结果。
// bad function setSidebar() { $('.sidebar').hide(); // ... $('.sidebar').css({ 'background-color': 'pink', }); } // good function setSidebar() { const $sidebar = $('.sidebar'); $sidebar.hide(); // ... $sidebar.css({ 'background-color': 'pink', }); }
– 26.3 DOM 查询使用后代选择器 $('.sidebar ul')
或者 父类 > 子类 $('.sidebar > ul')
选择器。jsPerf
– 26.4 在某个 jQuery 对象范围内查询使用 find
。
// bad $('ul', '.sidebar').hide(); // bad $('.sidebar').find('ul').hide(); // good $('.sidebar ul').hide(); // good $('.sidebar > ul').hide(); // good $sidebar.find('ul').hide();
ECMAScript 5 兼容性 Compatibility
– 27.1 参考 Kangax 的 ES5 compatibility table.
ECMAScript 6+ (ES 2015+) 编码风格
– 28.1 这是一个各种 ES6+ 新特性的链接集合。
- Arrow Functions
- Classes
- Object Shorthand
- Object Concise
- Object Computed Properties
- Template Strings
- Destructuring
- Default Parameters
- Rest
- Array Spreads
- Let and Const
- Exponentiation Operator
- Iterators and Generators
- Modules
– 28.2 不要使用 TC39 proposals 还未实现的 stage3 的功能。
为什么?他们没有最终确定,他们可能会改变或完全撤回。我们想要使用JavaScript,而且建议性的提案还不是 JavaScript 。
标准库 Standard Library
标准库包含有问题,但由于历史遗留问题而保留下来的功能。
– 29.1 使用 Number.isNaN
代替全局 isNaN
。eslint: no-restricted-globals
为什么?全局的
isNaN
方法会将非数字转换为数字, 任何被转换为 NaN 的东西都会返回 true 。
如果需要这种行为,请明确使用。
// bad isNaN('1.2'); // false isNaN('1.2.3'); // true // good Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true
– 29.2 使用 Number.isFinite
代替全局 isFinite
。eslint: no-restricted-globals
为什么?全局的
isFinite
方法会将非数字转换为数字, 任何被转换为有限大的数字都会返回 true 。
如果需要这种行为,请明确使用。
// bad isFinite('2e3'); // true // good Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true
测试
– 30.1 是的。
function foo() { return true; }
– 30.2 不要这么做,很严重:
– 不论你用哪一个测试框架,都应该写测试用例!
– 尽力写一些简单的纯函数, 并尽量减可变性发生的地方。
– 谨慎的使用 stubs 和 mocks – 它们会使测试变得脆弱.
– 我们主要在 Airbnb 中主要使用 mocha
。 tape
也偶尔会用于小型独立模块。
– 100%的测试覆盖率是很好的追求目标,即使它并不总是实际可行的。
– 每当你修复一个 bug ,写一个回归测试。 未经回归测试的bug修复几乎会在将在再次出现.
性能 Performance
- On Layout & Web Performance
- String vs Array Concat
- Try/Catch Cost In a Loop
- Bang Function
- jQuery Find vs Context, Selector
- innerHTML vs textContent for script text
- Long String Concatenation
- Are Javascript functions like
map()
,reduce()
, andfilter()
optimized for traversing arrays? - Loading…
资源 Resources
学习 ES6+
阅读这个
工具
- Code Style Linters
- ESlint – Airbnb Style .eslintrc
- JSHint – Airbnb Style .jshintrc
- JSCS – Airbnb Style Preset (Deprecated, please use ESlint)
- Neutrino preset – neutrino-preset-airbnb-base
其他编码风格指南
- Google JavaScript Style Guide
- jQuery Core Style Guidelines
- Principles of Writing Consistent, Idiomatic JavaScript
其他风格
- Naming this in nested functions – Christian Johansen
- Conditional Callbacks – Ross Allen
- Popular JavaScript Coding Conventions on GitHub – JeongHoon Byun
- Multiple var statements in JavaScript, not superfluous – Ben Alman
了解更多
- Understanding JavaScript Closures – Angus Croll
- Basic JavaScript for the impatient programmer – Dr. Axel Rauschmayer
- You Might Not Need jQuery – Zack Bloom & Adam Schwartz
- ES6 Features – Luke Hoban
- Frontend Guidelines – Benjamin De Cock
书籍
- JavaScript: The Good Parts – Douglas Crockford
- JavaScript Patterns – Stoyan Stefanov
- Pro JavaScript Design Patterns – Ross Harmes and Dustin Diaz
- High Performance Web Sites: Essential Knowledge for Front-End Engineers – Steve Souders
- Maintainable JavaScript – Nicholas C. Zakas
- JavaScript Web Applications – Alex MacCaw
- Pro JavaScript Techniques – John Resig
- Smashing Node.js: JavaScript Everywhere – Guillermo Rauch
- Secrets of the JavaScript Ninja – John Resig and Bear Bibeault
- Human JavaScript – Henrik Joreteg
- Superhero.js – Kim Joar Bekkelund, Mads Mob?k, & Olav Bjorkoy
- JSBooks – Julien Bouquillon
- Third Party JavaScript – Ben Vinegar and Anton Kovalyov
- Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript – David Herman
- Eloquent JavaScript – Marijn Haverbeke
- You Don’t Know JS: ES6 & Beyond – Kyle Simpson
博客
- JavaScript Weekly
- JavaScript, JavaScript…
- Bocoup Weblog
- Adequately Good
- NCZOnline
- Perfection Kills
- Ben Alman
- Dmitry Baranovskiy
- nettuts
播客
最新评论
写的挺好的
有没有兴趣翻译 impatient js? https://exploringjs.com/impatient-js/index.html
Flexbox playground is so great!
感谢总结。
awesome!
这个好像很早就看到类似的文章了
比其他的教程好太多了
柯理化讲的好模糊…没懂