如何用最合理的方式写你的 JavaScript 代码
BY 张聪(dancerphil@github)
这是一篇在原文基础上演绎的译文,与原文的表达会有出入
原文在不断的更新,本文基于 2022-01-12 的版本,last commit [b4377fb]
除非另行注明,页面上所有内容采用MIT协议共享
注意:指南假设你使用了 Babel,并且使用 babel-preset-airbnb 和 airbnb-browser-shims 或者其他等价的方案。
其他的代码指南(Style Guides)
- 类型 Types
- 引用 References
- 对象 Objects
- 数组 Arrays
- 解构 Destructuring
- 字符串 Strings
- 函数 Functions
- 箭头函数 Arrow Functions
- 类,构造函数 Classes & Constructors
- 模块 Modules
- 迭代器,生成器 Iterators and Generators
- 属性 Properties
- 变量 Variables
- 提升 Hoisting
- 比较运算符,等号 Comparison Operators & Equality
- 块 Blocks
- 控制声明 Control Statements
- 注释 Comments
- 空格 Whitespace
- 逗号 Commas
- 分号 Semicolons
- 类型,强制类型转换 Type Casting & Coercion
- 命名规则 Naming Conventions
- 存取器 Accessors
- 事件 Events
- jQuery
- ECMAScript 5 兼容
- ECMAScript 6+ (ES 2015+) 代码风格
- 标准库 Standard Library
- 测试 Testing
性能 Performance前往原文学习资源 Resources使用人群其他翻译指南的指南和我们一起讨论JS贡献者许可修订
-
1.1 基本类型:访问到基本类型时,直接得到值。
string
number
boolean
null
undefined
symbol
bigint
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
- Symbol 或 BigInt 很难被可靠的 polyfill,使用时需要注意浏览器/环境是否原生支持,如果不能原生支持,就不要使用他们。
-
1.2 复杂类型:访问复杂类型时,通过引用得到值。
object
array
function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
-
2.1 所有引用都应该使用
const
,避免使用var
。 eslint:prefer-const
,no-const-assign
为什么?这就确保了你不会对引用(reference)重新赋值,否则会出现难以理解的 bug。
// 差评 var a = 1; var b = 2; // 好评 const a = 1; const b = 2;
-
2.2 如果你一定要修改一个引用,使用
let
代替var
。 eslint:no-var
为什么?
let
是块级作用域变量,而var
是函数作用域变量。// 差评 var count = 1; if (true) { count += 1; } // 好评,使用 let let count = 1; if (true) { count += 1; }
-
2.3 注意
let
和const
都是块级作用域,而var
是函数级作用域。// const 和 let 只在它们定义的代码块里才存在 { let a = 1; const b = 1; var c = 1; } console.log(a); // 抛出 ReferenceError console.log(b); // 抛出 ReferenceError console.log(c); // 打印 1
在以上代码中,你可以看到引用
a
和b
会引起 ReferenceError,而c
引用了一个数字。这是因为a
和b
是块级作用域,而c
的作用域范围是它所在的函数。
-
3.1 使用字面语法来创建对象。 eslint:
no-new-object
// 差评 const item = new Object(); // 好评 const item = {};
-
3.2 创建有动态属性名的对象时,使用可被计算的属性名称。
为什么?这使你你可以在一个地方定义所有的对象属性。
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, // 可被计算属性名称 };
-
3.3 使用对象方法的简写写法。 eslint:
object-shorthand
// 差评 const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // 好评 const atom = { value: 1, addValue(value) { return atom.value + value; }, };
-
3.4 使用对象属性值的简写写法。 eslint:
object-shorthand
为什么?这样写简短有力。
const lukeSkywalker = 'Luke Skywalker'; // 差评 const obj = { lukeSkywalker: lukeSkywalker, }; // 好评 const obj = { lukeSkywalker, };
-
3.5 把简写的属性放在一起,然后写在对象的开头处。
为什么?这样能清楚地分辨哪些属性使用了简写。
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, };
-
3.6 只有拥有不合法标识符的属性名才使用引号。 eslint:
quote-props
为什么?这样更易读,且可以改善 IDE 语法高亮,同时帮助很多 JS 引擎进行优化。
// 差评 const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // 好评 const good = { foo: 3, bar: 4, 'data-blah': 5, };
-
3.7 不要直接调用
Object.prototype
方法,比如hasOwnProperty
,propertyIsEnumerable
和isPrototypeOf
。 eslint:no-prototype-builtins
为什么?这些方法可能会被问题对象的属性所覆盖(Shadowed) - 考虑
{ hasOwnProperty: false }
- 或者干脆,这是一个空对象(Object.create(null)
)。// 差评 console.log(object.hasOwnProperty(key)); // 好评 console.log(Object.prototype.hasOwnProperty.call(object, key)); // 最佳 const has = Object.prototype.hasOwnProperty; // 在模块域内缓存一个查找(lookup) /* or */ const has = require('has'); // https://www.npmjs.com/package/has console.log(has.call(object, key));
-
3.8 浅拷贝时,使用 Spread 语法
...
代替Object.assign
。 忽略某些属性得到一个新对象时,使用 Rest 语法...
。 eslint:prefer-object-spread
// 非常差 const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ delete copy.a; // so does this // 差评 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 }
-
4.1 使用字面语法创建数组。 eslint:
no-array-constructor
// 差评 const items = new Array(); // 好评 const items = [];
-
4.2 不要用下标方式直接在数组中加上一个项,使用 Array#push 来代替。
const someStack = []; // 差评 someStack[someStack.length] = 'abracadabra'; // 好评 someStack.push('abracadabra');
-
4.3 使用数组 Spread
...
来复制数组。// 差评 const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // 好评 const itemsCopy = [...items];
-
4.4 使用 Spread
...
把一个 可迭代对象 转换成数组,而非 Array.fromconst foo = document.querySelectorAll('.foo'); // 好评 const nodes = Array.from(foo); // 最佳 const nodes = [...foo];
-
4.5 使用
Array.from
把一个 类数组对象 转换成数组const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // 差评 const arr = Array.prototype.slice.call(arrLike); // 好评 const arr = Array.from(arrLike);
-
4.6 使用
Array.from
映射 可迭代对象,而非 Spread 操作符...
。因为这样少创建一个临时的 Array。// 差评 const baz = [...foo].map(bar); // 好评 const baz = Array.from(foo, bar);
-
4.7 在数组函数的回调中,使用 return 声明。如果一个函数体只需要一句简单的声明,那么你可以省略 return。参见 8.2。 eslint:
array-callback-return
// 好评 [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // 好评 [1, 2, 3].map((x) => x + 1); // 差评 [[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; });
-
4.8 当一个 Array 有多行时,方括号需要换行。
// 差评 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, ];
-
5.1 使用对象解构(Destructuring)访问和使用多属性对象。 eslint:
prefer-destructuring
为什么?解构使你不用频繁的创建临时引用。重复访问对象带来了更多的代码,容易出错。解构可以在一个块中提供一个找到定义点的位置,而不需要在整个块中寻找
注 BY dancerphil:在异步代码中,解构和重复访问的行为是不同的
// 差评 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}`; }
-
5.2 解构数组。 eslint:
prefer-destructuring
const arr = [1, 2, 3, 4]; // 差评 const first = arr[0]; const second = arr[1]; // 好评 const [first, second] = arr;
-
5.3 要返回多个值时,使用对象解构,而不是数组解构。
为什么?你可以轻松的增减返回值或更改它们的顺序,而不需要改变调用者的代码
// 差评 function processInput(input) { // 一些奇怪的事情发生了(then a miracle occurs) return [left, right, top, bottom]; } // 调用者需要思考返回值的顺序问题 const [left, __, top] = processInput(input); // 好评 function processInput(input) { // 一些奇怪的事情发生了 return { left, right, top, bottom }; } // 调用者只拿取它们需要的数据 const { left, top } = processInput(input);
-
6.1 字符串使用单引号
''
。 eslint:quotes
// 差评 const name = "Capt. Janeway"; // 差评 - 模版字符串应当包含变量或者换行 const name = `Capt. Janeway`; // 好评 const name = 'Capt. Janeway';
-
6.2 字符串超过 100 个字节时,不要 使用字符串连接号换行。
为什么?分离的字符串难以阅读修改和搜索
// 差评 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.';
-
6.3 用程序生成字符串时,使用模板字符串代替字符串连接。 eslint:
prefer-template
template-curly-spacing
为什么?模板字符串更为简洁,更具可读性,其恰当的换行和字符串插值非常好用。
// 差评 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}?`; }
-
6.5 不要在字符串里使用无谓的转义符号。 eslint:
no-useless-escape
为什么?反斜杠影响了可读性,所以只有必要的时候才转义。
// 差评 const foo = '\'this\' \i\s \"quoted\"'; // 好评 const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`;
-
7.1 使用函数表达式,不要使用函数声明。
为什么?函数声明会把整个函数提升(Hoist),这使得函数太容易在定义之前被引用,破坏了可读性。如果一个函数定义复杂到影响到理解文件其余的内容,此时这个文件应当被提取到它自己的模块中。无论是什么情况,不要忘了显式的命名你的函数表达式(有的时候 浏览器或 babel 会推断一个命名,但你仍然应该自己命名),这有助于优化错误堆栈。 (相关讨论)
// 差评 function foo() { // ... } // 差评 const foo = function () { // ... }; // 好评 - 给一个较长的区别于调用的函数词法名称 const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... };
-
7.2 在立即调用的函数表达式的两侧用括号包裹。 eslint:
wrap-iife
为什么?立即调用的函数表达式(immediately invoked function expression)是一个单独的单元,你需要强调这一点,所以把表达式和它的调用括号包裹起来。还需要提醒一下的是:在写模块的时候,你永远不会需要一个 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
定义为一组语句。而函数声明并不是语句。// 差评 if (currentUser) { function test() { console.log('Nope.'); } } // 好评 let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
-
7.5 永远不要把参数命名为
arguments
。这将取代原来函数作用域内的arguments
对象。// 差评 function foo(name, options, arguments) { // ... } // 好评 function foo(name, options, args) { // ... }
-
7.6 与此同时,不要使用
arguments
。可以选择 rest 语法...
替代。 eslint:prefer-rest-params
为什么?
...
可以确切的指定你想要获取的变量,而且rest
参数是一个真正的数组,而argument
是一个类数组对象// 差评 function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // 好评 function concatenateAll(...args) { return args.join(''); }
-
7.7 使用预设参数的语法,而不是改变函数参数。
// 非常差 function handleThings(opts) { // 不该改变函数参数 // 同时,如果 opts 被计算为 false ,它会成为一个对象 // 或许你想这么做,但是这样会引起 bug opts = opts || {}; // ... } // 差评 function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // 好评 function handleThings(opts = {}) { // ... }
-
7.8 避免预设参数的语法的副作用。
为什么?这样做完之后,代码变得没有逻辑。
var b = 1; // 差评 function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3
-
7.9 总是把预设参数写在参数的最后。 eslint:
default-param-last
// 差评 function handleThings(opts = {}, name) { // ... } // 好评 function handleThings(name, opts = {}) { // ... }
-
7.10 不要使用函数构造器来构造一个新函数。eslint:
no-new-func
为什么?通过这种方式来构造函数与
eval()
相类似。会造成很多漏洞// 差评 var add = new Function('a', 'b', 'return a + b'); // 差评 var subtract = Function('a', 'b', 'return a - b');
-
7.11 在函数的 signature 后放置空格。eslint:
space-before-function-paren
space-before-blocks
为什么?一致性是必须考虑的。在你新增和删除名称时,你不需要改变空格。
// 差评 const f = function(){}; const g = function (){}; const h = function() {}; // 好评 const x = function () {}; const y = function a() {};
-
7.12 不要改变对象的参数。 eslint:
no-param-reassign
为什么?将传入对象的参数重新赋值,可能会引起一些不期望的副作用。
// 差评 function f1(obj) { obj.key = 1; } // 好评 function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }
-
7.13 永远不要对参数重新赋值。 eslint:
no-param-reassign
为什么?对参数重新赋值会引起意外的行为,尤其是访问
arguments
对象。在 V8 引擎里,会导致优化问题。// 差评 function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // 好评 function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... }
-
7.14 使用 Spread 语法
...
调用可变函数。 eslint:prefer-spread
为什么?这样清晰,不需要提供一个上下文,且可以简单的组合
new
和apply
。// 差评 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]);
-
7.15 带有多个参数的函数的签名或者调用都应该有一致的缩进:每一行一个参数,末尾逗号。 eslint:
function-paren-newline
// 差评 function foo(bar, baz, quux) { // ... } // 好评 function foo( bar, baz, quux, ) { // ... } // 差评 console.log(foo, bar, baz); // 好评 console.log( foo, bar, baz, );
-
8.1 当你必须使用匿名函数(或传递一个内联函数)时,使用箭头函数符号。 eslint:
prefer-arrow-callback
,arrow-spacing
为什么?箭头函数创造了一个新的
this
上下文,通常符合你的期望,而且箭头函数非常简洁。什么时候不?如果你有一个相当复杂的函数,你或许想要把逻辑部分转移到一个命名函数表达式上。
// 差评 [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; });
-
8.2 如果一个函数体只有很简单的一行声明并返回一个没有副作用的 表达式,那就把花括号、圆括号和
return
都省略掉。如果不是,那就保留括号和return
。 eslint:arrow-parens
,arrow-body-style
为什么?这种语法糖在函数的链式调用中可读性很高
// 差评 [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, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // 好评 [1, 2, 3].map((number, index) => ({ [index]: number })); // 有副作用时不要省略 return function foo(callback) { const val = callback(); if (val === true) { // Do something if callback returns true } } let bool = false; // 差评 foo(() => bool = true); // 好评 foo(() => { bool = true; });
-
8.3 如果你们表达式跨行了,请使用括号把它们包裹起来,增加可读性。
为什么?这样做使得表达式的开始和结束的位置更加清楚。
// 差评 ['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // 好评 ['get', 'post', 'put'].map((httpMethod) => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ));
-
8.4 总是使用括号包裹参数,清晰一致。 eslint:
arrow-parens
为什么?增加或移除函数参数时最小化 diff。
// 差评 [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; });
-
8.5 避免把箭头函数语法 (
=>
) 和比较运算符 (<=
,>=
) 相混淆。 eslint:no-confusing-arrow
// 差评 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; };
-
8.6 省略 return 时,保持返回值的位置一致。 eslint:
implicit-arrow-linebreak
// 差评 (foo) => bar; (foo) => (bar); // 好评 (foo) => bar; (foo) => (bar); (foo) => ( bar )
-
9.1 总是使用
class
。避免直接操作prototype
。为什么? class 语法更简洁易读
// 差评 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; } }
-
9.2 使用
extends
来继承。为什么?
extends
是一个内建的原型继承方法,它不会破坏instanceof
。// 差评 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]; } }
-
9.3 方法返回
this
可以帮助链式调用。// 差评 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);
-
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
// 差评 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'; } }
-
9.6 避免重复的类成员。 eslint:
no-dupe-class-members
为什么?重复的类成员中,只有最后一个声明会被静静的选中,重复类成员几乎必然会引起 bug
// 差评 class Foo { bar() { return 1; } bar() { return 2; } } // 好评 class Foo { bar() { return 1; } } // 好评 class Foo { bar() { return 2; } }
-
9.7 类方法应该使用
this
或者就应该是一个静态方法 (Static),除非其他的库或框架要求了非静态方法。实例方法在每个实例上的表现应该不同。 eslint:class-methods-use-this
// 差评 class Foo { bar() { console.log('bar'); } } // 好评 - 使用了 this class Foo { bar() { console.log(this.bar); } } // 好评 - constructor 除外 class Foo { constructor() { // ... } } // 好评 - 静态方法不使用 this class Foo { static bar() { console.log('bar'); } }
-
10.1 总是使用模块 (
import
/export
) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。为什么?模块是未来,让我们开始使用未来吧!
// 差评 const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // 还可以 import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // 最佳 import { es6 } from './AirbnbStyleGuide'; export default es6;
-
10.2 不要使用通配符
*
引入。为什么?这样能确保你只有一个默认的
export
。// 差评 import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // 好评 import AirbnbStyleGuide from './AirbnbStyleGuide';
-
10.3 不要从
import
中直接export
。为什么?虽然写在一行看起来很棒,但是它们毕竟一个是引入,一个是导出,分开写会让所有事都井井有条。
// 差评 // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // 好评 // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6;
-
10.4 来自相同路径的
import
,就把他们写在同一个地方。 eslint:no-duplicate-imports
为什么?写在不同行的话,程序的维护和更新变得非常困难。
// 差评 import foo from 'foo'; // … 一些其他的 imports … // import { named1, named2 } from 'foo'; // 好评 import foo, { named1, named2 } from 'foo'; // 好评 import foo, { named1, named2, } from 'foo';
-
10.5 不要
export
可变的绑定。 eslint:import/no-mutable-exports
为什么?在特定的情况下也许会导出可变绑定,一般来说我们都要避免变化(Mutation)的发生,只使用常数引用。
// 差评 let foo = 3; export { foo }; // 好评 const foo = 3; export { foo };
-
10.6 如果模块只有一个简单的
export
,使用default
模块而不是带名称模块。 eslint:import/prefer-default-export
为什么?鼓励更多模块只 export 一样东西,有助于可读性和可维护性。
// 差评 export function foo() {} // 好评 export default function foo() {}
-
10.7 把所有的
import
放在其他的声明之前。 eslint:import/imports-first
为什么?因为
import
是提升的(Hoisted),把它们放在最上方可以避免许多令人惊讶的代码行为。// 差评 import foo from 'foo'; foo.init(); import bar from 'bar'; // 好评 import foo from 'foo'; import bar from 'bar'; foo.init();
-
10.8 多行的 import 应该与 Array 和 Object 的规则保持一致。 eslint:
object-curly-newline
为什么?这里的大括号与其他代码块的缩进保持一致,逗号也是。
// 差评 import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // 好评 import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path';
-
10.9 禁止在 import 语句中使用 webpack loader 语法。 eslint:
import/no-webpack-loader-syntax
为什么?这个语法依赖具体的打包库。应当在
webpack.config.js
中定义 loader。// 差评 import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // 好评 import fooSass from 'foo.scss'; import barCss from 'bar.css';
-
10.10 不要包括文件后缀。 eslint:
import/extensions
为什么?这影响了重构,消费端写死了实现的细节。
// 差评 import foo from './foo.js'; import bar from './bar.jsx'; import baz from './baz/index.jsx'; // 好评 import foo from './foo'; import bar from './bar'; import baz from './baz';
-
11.1 不要使用迭代器(iterators)。选择 JS 高阶函数比如
map()
和reduce()
,不要使用循环如for-in
和for-of
。 eslint:no-iterator
no-restricted-syntax
为什么?这增加了代码的不变性,处理纯函数的返回值非常方便,超过了其可以造成的副作用。
使用
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
等方法来迭代数组,并且使用Object.keys()
/Object.values()
/Object.entries()
来生成一些数组,以便你迭代对象。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;
-
11.2 暂时不要使用生成器(Generators)。
为什么?他们不能很好的被编译到 ES5
-
11.3 如果拒绝听从 劝告 使用生成器,请先确保他们的函数签名是正确的。 eslint:
generator-star-spacing
为什么?
function
和*
是同一个概念关键字的两个部分 -*
不是function
的一个修饰符,function*
与function
不同,它是一个独有的关键字。// 差评 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* () { // ... }
-
12.1 使用
.
符号来访问对象属性。 eslint:dot-notation
const luke = { jedi: true, age: 28, }; // 差评 const isJedi = luke['jedi']; // 好评 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
.// 差评 const binary = Math.pow(2, 10); // 好评 const binary = 2 ** 10;
-
13.1 总是用
const
或let
来声明变量。不这么做会导致全局变量。我们想要尽量避免污染全局命名空间。 eslint:no-undef
prefer-const
// 差评 superPower = new SuperPower(); // 好评 const superPower = new SuperPower();
-
13.2 每个变量或赋值都对应一个
const
或let
。 eslint:one-var
为什么?增加新的变量会更容易,而且你永远不用再担心把
,
和;
换来换去的事情,你肯定觉得 git diff 里包含太多标点符号的变换是很蠢的事情。而且,调试时你可以步进,而不是一次性跳过了所有的声明。// 差评 const items = getItems(), goSportsTeam = true, dragonball = 'z'; // 差评 - 与上面比较一下,再说说哪里写错了 const items = getItems(), goSportsTeam = true; dragonball = 'z'; // 好评 const items = getItems(); const goSportsTeam = true; const dragonball = 'z';
-
13.3 把你所有的
const
放在一起,再把所有的let
放在一起。为什么?等到你想要把已赋值的变量赋给一个新变量的时候,这很有帮助。
// 差评 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;
-
13.4 在你需要的地方赋值变量,把他们放到合理的位置。
为什么?
let
和const
是块级作用域的,不是函数级作用域的。// 差评 - 没有必要的函数调用 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; }
-
13.5 不要链式赋值。 eslint:
no-multi-assign
为什么?链式赋值创建了全局变量。
// 差评 (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 // 好评 (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 禁止使用一元增减操作符(
++
,--
)。 eslintno-plusplus
为什么?根据 eslint 文档所说,一元增减操作符会服从自动分号插入(ASI),从而导致问题。同时,
num += 1
比num++
或num ++
更有表现力一些。同时,避免预增预减值带来的非预期行为。注 BY dancerphil:依据场景选择。
// 差评 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;
-
13.7 避免在
=
前后换行,如果赋值违反了max-len
,用括号包裹(注:也可忽略max-len
)。 eslintoperator-linebreak
.为什么?这混淆了赋值的值。
// 差评 const foo = superLongLongLongLongLongLongLongLongFunctionName(); // 差评 const foo = 'superLongLongLongLongLongLongLongLongString'; // 好评 const foo = ( superLongLongLongLongLongLongLongLongFunctionName() ); // 好评 const foo = 'superLongLongLongLongLongLongLongLongString';
-
13.8 禁止未使用的变量。 eslint:
no-unused-vars
为什么?这种情况很可能是重构没有做完产生的。占用了代码的空间,同时容易让读者混淆。
// 差评 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' 被忽略了,虽然它没有被使用,因为这是在忽略某个对象中的某个属性。 var { type, ...coords } = data; // 'coords' 现在是没有 'type' 属性的 'data' 对象。
-
14.1
var
声明会被提升至最近的函数作用域的顶部,但其赋值不会提升。let
和const
被赋予了一种新概念,称为暂时性死区(Temporal Dead Zones)。这对于了解为什么 typeof 不再安全相当重要// 这样是运行不了的 // (如果 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 var anonymous = function () { console.log('anonymous function expression'); }; }
-
14.3 命名函数表达式的变量名会被提升,但函数名和函数函数内容不会
function example() { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var named = function superPower() { console.log('Flying'); }; } // 当函数名和变量名相同时,结论也同样成立 function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); }; }
-
14.4 函数声明的名称和函数体都会被提升,所以你知道该怎么写
function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } }
-
想了解更多信息,参考 Scoping & Hoisting 来自 Ben Cherry
注 BY dancerphil:没有特殊需要的时候,把定义放在使用之前就可以了。
-
15.2 条件声明如
if
会把表达式的值转换为Boolean
,依据以下的规则:- Objects 被视为 true
- Undefined 被视为 false
- Null 被视为 false
- Booleans 被视为 boolean的值
- 如果 Numbers 是 +0, -0, or NaN ,将被视为 false ,否则被视为 true
- 如果 Strings 是一个空字符串
''
,将被视为 false ,否则被视为 true
if ([0] && []) { // true // 数组(即使它是空的)是一个对象,而对象被视为 true }
-
15.3 对于
Boolean
使用简写(Shortcuts),而String
和Number
使用显式比较。// 差评 if (isValid === true) { // ... } // 好评 if (isValid) { // ... } // 差评 if (name) { // ... } // 好评 if (name !== '') { // ... } // 差评 if (collection.length) { // ... } // 好评 if (collection.length > 0) { // ... }
-
15.4 想了解更多信息,可以参考 Angus Croll 的 Truth Equality and JavaScript。
// 注 BY dancerphil:文章中的部分内容 console.log([] == true); // false console.log(![] == true); // false console.log(!![] == true); // true if([]) { console.log('why?'); // why? }
-
15.5 如果在
case
和default
中包含了宣告语法(lexical declarations),比如let
,const
,function
, 和class
。那么用大括号来建立代码块。 eslint:no-case-declarations
为什么?宣告语法在整个
switch
块都可见,但是只在我们定义的地方才得到了初始值,也就是当进入其所在的case
时才触发。如果不加大括号,可能引起多个case
尝试定义同一事物的情况。// 差评 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 {} } }
-
15.6 三元运算符通常应该是简单的单行表达式,而不是嵌套的。 eslint rules:
no-nested-ternary
.// 差评 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;
-
15.7 避免不必要的三元运算符。 eslint rules:
no-unneeded-ternary
.// 差评 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;
-
15.8 混合使用多种操作符时,用括号声明优先级,唯一的例外是
+
,-
和**
,因为它们的优先级被大部分知道。推荐括起/
和*
,根据场景不同它们的优先级是模棱两可的。 eslint:no-mixed-operators
为什么?增强可读性,并且更好的阐明开发者的意图。
// 差评 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;
-
16.1 用大括号包裹所有的多行代码块。 eslint:
nonblock-statement-body-position
// 差评 if (test) return false; // 好评 if (test) return false; // 好评 if (test) { return false; } // 差评 function foo() { return false; } // 好评 function bar() { return false; }
-
16.2 如果你在使用带
if
和else
的多行代码块,把else
放在if
代码块关闭括号的同一行。 eslint:brace-style
// 差评 if (test) { thing1(); thing2(); } else { thing3(); } // 好评 if (test) { thing1(); thing2(); } else { thing3(); }
-
16.3 如果一个
if
块总是return
,那么对应的else
块是不必要的。且else if
块可以写成多个if
块(如果每个块都 return 的话)。 eslint:no-else-return
// 差评 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; } }
-
17.1 如果你的控制语句(
if
,while
)过于长,你可以把他们分成多组,每组一行,以逻辑运算符开始每一行。为什么?与链式方法调用的模式保持一致,且有助于提升可读性。
// 差评 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(); }
-
17.2 不要使用选择运算符代替控制语句。
// 差评 !isRunning && startRunning(); // 好评 if (!isRunning) { startRunning(); }
-
18.1 使用
/** ... */
作为多行注释。// 差评 // make() returns a new element // based on the passed in tag name function make(tag) { // ... return element; } // 好评 /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
-
18.2 使用
//
作为单行注释。在需要注释的代码上方另起一行写注释。在注释前空一行,除非注释在块的开头。// 差评 const active = true; // is current tab // 好评 // is current tab const active = true; // 差评 function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // 好评 function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // 同样好评 function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; }
-
18.3 所有注释前有一个空格。 eslint:
spaced-comment
// 差评 //is current tab const active = true; // 好评 // is current tab const active = true; // 差评 /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // 好评 /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
-
18.4 在注释前加上
FIXME
或TODO
可以帮助其他开发者快速的了解写这段注释的原因。也许你正在指出一个需要复查的问题,或者你正在给需要实现的功能提供一个解决方式。和一般的注解不用,它们是可被执行的,对应的动作为FIXME: -- 需要搞清楚这一点
或者TODO: -- 需要实现
。 -
18.5 使用
// FIXME:
标注问题。class Calculator extends Abacus { constructor() { super(); // FIXME: 这里不应该存在全局变量 total = 0; } }
-
18.6 使用
// TODO:
标注问题的解决方式。class Calculator extends Abacus { constructor() { super(); // TODO: total 应该是个可被传入的可选变量 this.total = 0; } }
-
19.1 使用两个空格作为缩进。 eslint:
indent
// 差评 function foo() { ∙∙∙∙let name; } // 差评 function bar() { ∙let name; } // 好评 function baz() { ∙∙let name; }
-
19.2 在起始的大括号
{
的左侧放上一个空格。 eslint:space-before-blocks
// 差评 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', });
-
19.3 在控制语句
if
,while
的括号前放一个空格。在函数调用及声明中,括号前不加空格。 eslint:keyword-spacing
// 差评 if(isJedi) { fight (); } // 好评 if (isJedi) { fight(); } // 差评 function fight () { console.log ('Swooosh!'); } // 好评 function fight() { console.log('Swooosh!'); }
-
19.4 使用空格隔开运算符。 eslint:
space-infix-ops
// 差评 const x=y+5; // 好评 const x = y + 5;
-
19.5 在文件末尾插入一个空行。 eslint:
eol-last
// 差评 import { es6 } from './AirbnbStyleGuide'; // ... export default es6;
// 差评 import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ↵
// 好评 import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵
-
19.6 链式调用时(超过两个方法的链),使用符合调用逻辑的缩进,并且在方法的左侧放置点
.
,不要放到后面,强调这是方法调用而不是新语句。 eslint:newline-per-chained-call
no-whitespace-before-property
// 差评 $('#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); const svg = leds.enter().append('svg:svg'); svg.classed('led', true).attr('width', (radius + margin) * 2); const g = svg.append('svg:g'); g.attr('transform', `translate(${radius + margin},${radius + margin})`).call(tron.led);
-
19.7 在块结束后、新语句开始前,插入一个空行。
// 差评 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;
-
19.8 不要使用空白行来垫起你的代码块。 eslint:
padded-blocks
// 差评 function bar() { console.log(foo); } // 差评 if (baz) { console.log(qux); } else { console.log(foo); } // 差评 class Foo { constructor(bar) { this.bar = bar; } } // 好评 function bar() { console.log(foo); } // 好评 if (baz) { console.log(qux); } else { console.log(foo); }
-
19.9 不要使用多个空行来垫起你的代码块。 eslint:
no-multiple-empty-lines
// 差评 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) { // .. } } // 好评 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) { // .. } }
-
19.10 不要在括号里加空格。 eslint:
space-in-parens
// 差评 function bar( foo ) { return foo; } // 好评 function bar(foo) { return foo; } // 差评 if ( foo ) { console.log(foo); } // 好评 if (foo) { console.log(foo); }
-
19.11 不要在方括号内加空格。 eslint:
array-bracket-spacing
// 差评 const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // 好评 const foo = [1, 2, 3]; console.log(foo[0]);
-
19.12 不要在花括号内加空格。 eslint:
object-curly-spacing
// 差评 const foo = {clark: 'kent'}; // 好评 const foo = { clark: 'kent' };
-
19.13 避免你的某一行代码超过了 100 字符,当然空格也算在里面。注意:长字符串除外。 eslint:
max-len
为什么?这种做法保证了可读性和可维护性
// 差评 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.'));
-
20.1 不要使用行首逗号。 eslint:
comma-style
// 差评 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', };
-
20.2 在结尾增加额外的逗号。 eslint:
comma-dangle
为什么? 这会让 git diffs 更干净。另外,像 babel 这样的转译器会移除结尾多余的逗号,也就是说你不必担心老旧浏览器的 行尾逗号问题。
// 差评 - 没有行尾逗号时, 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'], };
// 差评 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 );
-
为什么?当 JavaScript 遇到没有分号的换行符是,它会使用一套规则 Automatic Semicolon Insertion 来决定是否视为语句语句的结束,并且自动添加一个分号。ASI 包含一些异常行为,并且如果 JavaScript 错误的解释了换行符,代码将中断。随着新功能的加入,这些规则也越来越复杂。显式的声明分号有助于防止你遇到问题。
// 差评 - 产生报错 const luke = {} const leia = {} [luke, leia].forEach((jedi) => jedi.father = 'vader') // 差评 - 产生报错 const reaction = "No! That’s impossible!" (async function meanwhileOnTheFalcon() { // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // 差评 - 返回 `undefined` 而不是第二行的值。 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() { // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // 好评 function foo() { return 'search your feelings, you know it to be foo'; }
IIFE - 立即调用的函数表达式
-
22.1 执行类型转换的位置应当是语句的开头。
-
22.2 对字符串:
// => this.reviewScore = 9; // 差评 const totalScore = new String(this.reviewScore); // totalScore 是一个对象而非字符串 // 差评 const totalScore = this.reviewScore + ''; // 调用 this.reviewScore.valueOf() // 差评 const totalScore = this.reviewScore.toString(); // 不能保证返回字符串 // 好评 const totalScore = String(this.reviewScore);
-
21.3 数字:使用
Number
来进行类型转换,在使用parseInt
时,总是带上类型转换的基数。 eslint:radix
no-new-wrappers
为什么?
parseInt
在没有指定进位基数的时候,不同的浏览器对于八进制和十六进制的行为不同,且与 ECMAScript 3 的规范不一致。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);
-
22.4 如果因为某些原因,
parseInt
成为了性能瓶颈,如果你一定要使用 位操作 (Bitshift),那么留个注释解释一下原因吧。// 好评 /** * parseInt 使我的代码变慢了 * 改成使用位操作后来提升性能 */ const val = inputValue >> 0;
-
22.5 注意 : 小心使用位操作运算符。数字会被当成 64位变量,但是位操作运算符总是返回 32 位的整数(参考)。位操作在处理大于 32 位的整数值时,并不会如你所料。相关讨论。最大的 32 位整数是 2,147,483,647 :
2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647
-
22.6 布尔值:eslint:
no-new-wrappers
const age = 0; // 差评 const hasAge = new Boolean(age); // 好评 const hasAge = Boolean(age); // 最佳 const hasAge = !!age;
-
23.1 别用单字母命名。让你的命名具备描述性。 eslint:
id-length
// 差评 function q() { // ... } // 好评 function query() { // ... }
-
23.2 使用驼峰式命名对象、函数和实例。 eslint:
camelcase
// 差评 const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // 好评 const thisIsMyObject = {}; function thisIsMyFunction() {}
-
23.3 使用帕斯卡式命名构造函数或类。 eslint:
new-cap
// 差评 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', });
-
23.4 不要以下划线作为开头或结尾。 eslint:
no-underscore-dangle
为什么?事实上,Javascript 没有隐私方面的概念。虽然在一般的约定中,在前面加下划线的意思是 private ,但事实上,这些属性完完全全的就是公开的,也因此,意味着它们也是你公共 API 的一部分。这个一般的约定误导一部分程序员,使他们错误的认为一个小小的变化不会引起什么结果,或者认为测试是可有可无的。简单的说,如果你真的需要一些私密的属性,它就不应该被人看到。
// 差评 this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // 好评 this.firstName = 'Panda'; // 好评 // 参见 https://kangax.github.io/compat-table/es6/#test-WeakMap const firstNames = new WeakMap(); firstNames.set(this, 'Panda');
-
23.5 别保存
this
的引用。使用箭头函数或 Function#bind。// 差评 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); }; }
-
23.6 你的文件名应当和默认的类名保持完全一致。
// file 1 contents class CheckBox { // ... } export default CheckBox; // file 2 contents export default function fortyTwo() { return 42; } // file 3 contents 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" // ^ supports both insideDirectory.js and insideDirectory/index.js
-
23.7 使用驼峰式命名导出默认函数。你的文件名应当和函数名保持完全一致。
function makeStyleGuide() { // ... } export default makeStyleGuide;
-
23.8 使用帕斯卡式命名导出构造函数、类、单例(singleton)、函数库、空对象。
const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide;
-
23.9 缩写词应该始终全大写,或始终全小写。
为什么?可读性比向一个计算机算法妥协更重要。
// 差评 import SmsContainer from './containers/SmsContainer'; // 差评 const HttpRequests = [ // ... ]; // 好评 import SMSContainer from './containers/SMSContainer'; // 好评 const HTTPRequests = [ // ... ]; // 好评 const httpRequests = [ // ... ]; // 最佳 import TextMessageContainer from './containers/TextMessageContainer'; // 最佳 const requests = [ // ... ];
-
23.10 对于以下变量采用大写:1、被导出,2、是
const
,3、开发者可以信任它(以及他的属性)不变。为什么?在开发者不确定一个值是否会改变的时候,UPPERCASE_VARIABLES 提供了一种信任。
-
普通的
const
变量,没有必要,对于单个文件内的变量不需要大写。 -
被导出的对象,只在最外层大写(如
EXPORTED_OBJECT.key
),此对象的所有嵌套的属性都不会改变。
// 差评 const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file'; // 差评 export const THING_TO_BE_CHANGED = 'should obviously not be uppercased'; // 差评 export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables'; // --- // 可以,但不保证其值不改变 export const apiKey = 'SOMEKEY'; // 大多数情况下更好 export const API_KEY = 'SOMEKEY'; // --- // 差评 export const MAPPING = { KEY: 'value' }; // 好评 export const MAPPING = { key: 'value' };
-
-
24.1 并非所有属性的存取器函数都是必须的。
-
24.2 不要使用 JavaScript 的 getters/setters ,因为它们会导致你不期待的副作用,而且使测试、维护和推论。如果你一定要存取器函数,使用
getVal()
和setVal('hello')
。// 差评 class Dragon { get age() { // ... } set age(value) { // ... } } // 好评 class Dragon { getAge() { // ... } setAge(value) { // ... } }
-
24.3 如果属性或方法是一个
boolean
,使用isVal()
或hasVal()
。// 差评 if (!dragon.age()) { return false; } // 好评 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]; } }
-
25.1 当事件需要一些附加数据时(无论是 DOM 事件还是私有事件),传入一个对象(也被称为哈希)而不是原始值。这样可以让后面的开发者简单的增加更多数据到事件中,而无需找出并更新事件的每一个处理器。比如不要写:
// 差评 $(this).trigger('listingUpdated', listing.id); // ... $(this).on('listingUpdated', (e, listingID) => { // 使用 listingID });
而写:
// 好评 $(this).trigger('listingUpdated', { listingID: listing.id }); // ... $(this).on('listingUpdated', (e, data) => { // 使用 data.listingID });
-
26.1 jQuery 对象的变量名统一加上前缀
$
。// 差评 const sidebar = $('.sidebar'); // 好评 const $sidebar = $('.sidebar'); // 好评 const $sidebarBtn = $('.sidebar-btn');
-
26.2 缓存 jQuery 查询,避免无用的查询。
// 差评 function setSidebar() { $('.sidebar').hide(); // ... $('.sidebar').css({ 'background-color': 'pink', }); } // 好评 function setSidebar() { const $sidebar = $('.sidebar'); $sidebar.hide(); // ... $sidebar.css({ 'background-color': 'pink', }); }
-
26.3 查询 DOM 时,使用层叠
$('.sidebar ul')
或 parent > child 的模式$('.sidebar > ul')
。 jsPerf -
26.4 查询有作用域的 jQuery 对象时,使用
find
。// 差评 $('ul', '.sidebar').hide(); // 差评 $('.sidebar').find('ul').hide(); // 好评 $('.sidebar ul').hide(); // 好评 $('.sidebar > ul').hide(); // 好评 $sidebar.find('ul').hide();
- 28.1 ES6 代码风格分布在各个篇目中,我们整理了一下,以下是链接到 ES6 的各个特性的列表:
- 箭头函数 Arrow Functions
- 类 Classes
- 对象方法简写 Object Shorthand
- 对象属性简写 Object Concise
- 对象可计算属性 Object Computed Properties
- 模版字符串 Template Strings
- 解构 Destructuring
- 默认参数 Default Parameters
- Rest
- 拓展运算符 Array Spreads
- Let 和 Const
- 阶乘运算符 Exponentiation Operator
- 迭代器和生成器 Iterators and Generators
- 模块 Modules
标准库 中存在一些功能已经损坏,但仍然保留的工具。
-
29.1 使用
Number.isNaN
代替isNaN
。 eslint:no-restricted-globals
为什么?
isNaN
会强制转换非数字类型,然后对于被转换为NaN
的任何值返回true
。如果这是期望的行为,显式声明它。
// 差评 isNaN('1.2'); // false isNaN('1.2.3'); // true // 好评 Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true
-
29.2 使用
Number.isFinite
代替isFinite
。 eslint:no-restricted-globals
为什么?
isFinite
会强制转换非数字类型,然后对于被转换为有限值的任何值返回true
。如果这是期望的行为,显式声明它。
// 差评 isFinite('2e3'); // true // 好评 Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true
以下一些章节均为链接,前往原文
(The MIT License)
Copyright (c) 2012 Airbnb
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
警告 :转载时必须包含此许可证
非常鼓励你 fork
这份代码风格指南,稍加改动后作为你们团队的指南。