用更合理的方式写 JavaScript
<a name="table-of-contents"></a>
目录
<a name="types"></a>
声明变量
1.1 <a name='1.1'></a> 使用let和const代替var
//bad var $cat = $('.cat')//goodconst $cat = $('.cat')
//bad var count = 1if (count<10) { count += 1}//goodlet count = 1if (count<10) { count += 1}
会变动的声明用let
不会变的声明用const
1.2 <a name='1.2'></a> 将所有的
const
和let
分组为什么?当你需要把已赋值变量赋值给未赋值变量时非常有用。
// badlet i, len, dragonball, items = getItems(), goSportsTeam = true;// badlet i;const items = getItems();let dragonball;const goSportsTeam = true;let len;// goodconst goSportsTeam = true;const items = getItems();let dragonball;let i;let length;
1.3 <a name='1.3'></a> 在你需要的地方给变量赋值,但请把它们放在一个合理的位置。
为什么?
let
和const
是块级作用域而不是函数作用域。// bad - unnecessary function callconst checkName = function (hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; };// goodconst checkName = function (hasName) { if (hasName === 'test') { return false; } const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; };
<a name="objects"></a>
对象 Objects
2.1 <a name='2.1'></a> 使用字面值创建对象。eslint:
no-new-object
// badconst item = new Object();// goodconst item = {};
<a name="es6-computed-properties"></a>
2.2 <a name='2.2'></a> 创建有动态属性名的对象时,使用可被计算的属性名称。
为什么?因为这样可以让你在一个地方定义所有的对象属性。
const getKey = function (k) { return `a key named ${k}`; };// badconst obj = { id: 1, name: 'San Francisco', }; obj[getKey('enabled')] = true;// goodconst obj = { id: 1, name: 'San Francisco', [getKey('enabled')]: true, };
<a name="es6-object-shorthand"></a>
2.3 <a name='2.3'></a> 使用对象方法的简写。eslint:
object-shorthand
// badconst atom = { value: 1, addValue: function (value) { return atom.value + value; }, };// goodconst atom = { value: 1, addValue(value) { return atom.value + value; }, };
<a name="es6-object-concise"></a>
2.4 <a name='2.4'></a> 使用对象属性值的简写。eslint:
object-shorthand
为什么?因为这样更短更有描述性。
const lukeSkywalker = 'Luke Skywalker';// badconst obj = { lukeSkywalker: lukeSkywalker, };// goodconst obj = { lukeSkywalker, };
2.5 <a name='2.5'></a> 在对象属性声明前把简写的属性分组。
为什么?因为这样能清楚地看出哪些属性使用了简写。
const anakinSkywalker = 'Anakin Skywalker';const lukeSkywalker = 'Luke Skywalker';// badconst obj = { episodeOne: 1, twoJedisWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, };// goodconst obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJedisWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, };
2.6 <a name='2.6'></a> ==只==把对象中是无效标识符的属性用引号括起来。 eslint:
quote-props
为什么?通常认为这样写更容易阅读,更好的支持语法高亮,也更容易被许多 JS 引擎优化。
// badconst bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, };// goodconst good = { foo: 3, bar: 4, 'data-blah': 5, };
2.7 <a name='2.7'></a> 不要直接调用
Object.prototype
方法,比如hasOwnProperty
,propertyIsEnumerable
,和isPrototypeOf
。为什么?这些方法有可能被对象本身的方法所遮蔽 - 比如
{ hasOwnProperty: false }
- 或者, 这个对象是个 null object (Object.create(null)
)。// badconsole.log(object1.hasOwnProperty(key));// goodconsole.log(Object.prototype.hasOwnProperty.call(object, key));// bestconst has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope./* or */import has from 'has'; // https://www.npmjs.com/package/has// ...console.log(has.call(object, key));
2.8 <a name='2.8'></a> 使用对象字面量创建对象时,大括号必须换行。使用对象解构赋值时,如果对象属性超过2个,必须换行。eslint:
object-curly-newline
// badconst original = {a: 1, b: 2};const {height, largeSize, smallSize} = item;// goodconst original = { a: 1, b: 2, };const { height, largeSize, smallSize, } = item;const {height, largeSize} = item;
2.9 <a name='2.9'></a>
单行定义对象时,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。// badconst foo = { k1: v1, k2: v2, };const bar = { k1: v1, k2: v2 };// goodconst foo = { k1: v1, k2: v2 };const bar = { k1: v1, k2: v2, };
<a name="arrays"></a>
数组 Arrays
3.1 <a name='3.1'></a> 使用字面值创建数组。eslint:
no-array-constructor
// badconst items = new Array();// goodconst items = [];
3.2 <a name='3.2'></a> 向数组添加元素时使用 Array#push 替代直接赋值。
const someStack = [];// badsomeStack[someStack.length] = 'abracadabra';// goodsomeStack.push('abracadabra');
<a name="es6-array-spreads"></a>
3.3 <a name='3.3'></a> 使用扩展运算符
...
复制数组。// badconst len = items.length;const itemsCopy = [];let i;for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; }// goodconst itemsCopy = [...items];
3.4 <a name='3.4'></a> 使用扩展运算符
...
代替 Array.from 把一个类数组对象转换成数组。const foo = document.querySelectorAll('.foo');// goodconst nodes = Array.from(foo);// bestconst nodes = [...foo];
3.5 <a name='3.5'></a> 使用 Array.from 代替扩展运算符
...
来映射迭代器,因为这样可以避免创建一个中间数组。(array.from可以把类数组或者字符串转化为数组)// badconst baz = [...foo].map(bar);// goodconst baz = Array.from(foo, bar);
3.6 <a name='3.6'></a> 在数组方法的回调中使用return语句。如果函数只有一行代码,并且只返回了一个表达式,那么可以省略返回值。关联 7.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 - no returned value means `memo` becomes undefined after the first iteration[[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; });// badinbox.filter((msg) => { const {subject, author} = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } });// goodinbox.filter((msg) => { const {subject, author} = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; });
3.7 如果数组有多行,请在打开和关闭数组括号前使用换行符。eslint:
array-bracket-newline
// badconst arr = [ [0, 1], [2, 3], [4, 5], ];const objectInArray = [{ id: 1, }, { id: 2, }];const numberInArray = [ 1, 2, ];// goodconst arr = [[0, 1], [2, 3], [4, 5]];const objectInArray = [ { id: 1, }, { id: 2, }, ];const numberInArray = [ 1, 2, ];
3.8 禁止使用稀疏数组。eslint:
no-sparse-arrays
// badconst color = ['red',, 'blue'];// goodconst color = ['red', 'blue'];
<a name="strings"></a>
字符串 Strings
4.1 <a name='4.1'></a> 字符串使用单引号
''
。eslint:quotes
// badconst name = "Capt. Janeway";// bad - template literals should contain interpolation or newlinesconst name = `Capt. Janeway`;// goodconst name = 'Capt. Janeway';
4.2 <a name='4.2'></a> 字符串超过 ==80== 个字节应该使用字符串连接号换行。
4.3 <a name='4.3'></a> 注:过度使用字串连接符号可能会对性能造成影响。jsPerf 和 讨论。但是打包工具会在打包压缩后的代码中处理好字符串的拼接。而且字串连接符号对性能影响有限。
// badconst 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.';// badconst 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.';// goodconst errorMessage = 'This is a super long error that was thrown because ' + 'of Batman. When you stop to think about how Batman had anything to do ' + 'with this, you would get nowhere fast.';
<a name="es6-template-literals"></a>
4.4 <a name='4.4'></a> 程序化生成字符串时,使用模板字符串代替字符串连接。eslint:
prefer-template
template-curly-spacing
为什么?模板字符串更为简洁,更具可读性。
// badconst sayHi = function (name) { return 'How are you, ' + name + '?'; };// badconst sayHi = function (name) { return ['How are you, ', name, '?'].join(); };// badconst sayHi = function (name) { return `How are you, ${ name }?`; };// goodconst sayHi = function (name) { return `How are you, ${name}?`; };
<a name="strings--eval"></a>
<a name="strings--escaping"></a>
4.6 <a name='4.6'></a> 不要在字符串中使用不必要的转义字符。 eslint:
no-useless-escape
为什么?反斜杠导致代码不可读,因此应该在必要的时候使用。
// badconst foo = '\'this\' \i\s \"quoted\"';// goodconst foo = '\'this\' is "quoted"';const foo = `my name is '${name}'`;
<a name="functions"></a>
函数 Functions
5.1 <a name='5.1'></a> 使用函数表达式代替函数声明(顶级函数不受限制)。eslint:
func-style
为什么?函数声明会把整个函数提升(hoisted),这导致在函数定义之前就可以引用该函数。这会影响代码的可读性和可维护性。
// badfunction foo() { // ...}// goodconst foo = function () { // ...};
5.2 <a name='5.2'></a> 用括号包裹立即执行函数表达式。 eslint:
wrap-iife
为什么?立即执行函数表达式是个单独的模块,用括号将函数和表示函数执行的括号包裹起来,会使代码更清晰。
// 立即执行函数表达式 (IIFE)(function () { console.log('Welcome to the Internet. Please follow me.'); }());
5.3 <a name='5.3'></a> 永远不要在一个非函数代码块(
if
、while
等)中声明一个函数,应该把函数赋给代码块外部的一个变量。浏览器允许你这么做,但它们的解析表现不一致。eslint:no-loop-func
注意: ECMA-262 把block
定义为一组语句。函数声明不是语句。阅读 ECMA-262 关于这个问题的说明//badif (foo) { function test () { ... }//goodlet test;if(foo) { test = () => { ... } }
5.4 <a name='6.4'></a> 永远不要把参数命名为
arguments
。这将取代原来函数作用域内的arguments
对象。// badconst nope = function (name, options, arguments) { // ...stuff...};// goodconst yup = function (name, options, args) { // ...stuff...};
<a name="es6-rest"></a>
5.5 <a name='5.5'></a> 不要使用
arguments
。可以选择 rest 语法...
替代。eslint:prefer-rest-params
为什么?使用
...
能明确你要传入的参数。另外 rest 参数是一个真正的数组,而arguments
是一个类数组。// badconst concatenateAll = function () { const args = Array.prototype.slice.call(arguments); return args.join(''); };// goodconst concatenateAll = function (...args) { return args.join(''); };
<a name="es6-default-parameters"></a>
5.6 <a name='5.6'></a> 直接给函数的参数指定默认值,不要使用一个变化的函数参数。
// really badconst handleThings = function (opts) { // 不!我们不应该改变函数参数。 // 更加糟糕: 如果参数 opts 是 false 的话,它就会被设定为一个对象。 // 但这样的写法会造成一些 Bugs。 //(译注:例如当 opts 被赋值为空字符串,opts 仍然会被下一行代码设定为一个空对象。) opts = opts || {}; // ...};// still badconst handleThings = function (opts) { if (opts === void 0) { opts = {}; } // ...};// goodconst handleThings = function (opts = {}) { // ...};
5.7 <a name='5.7'></a> 直接给函数参数赋值时需要避免副作用。
为什么?因为这样的写法让人感到很困惑。
var b = 1;// badconst count = function (a = b++) { console.log(a); }; count(); // 1count(); // 2count(3); // 3count(); // 3
<a name="functions--constructor"></a><a name="5.8"></a>
5.8 不要使用构造函数的方式创建一个新函数。 eslint:
no-new-func
为什么?用构造函数的方式创建函数类似于用 eval() 创建字符串,会有很多缺陷。
// badvar add = new Function('a', 'b', 'return a + b');// still badvar subtract = Function('a', 'b', 'return a - b');
(所以是不使用class语法的意思吗)
<a name="functions--signature-spacing"></a><a name="5.9"></a>
6.9 function 关键词的空格。 eslint:
space-before-function-paren
space-before-blocks
为什么?保持一致性,这样在添加和删除函数时就不需要修改空格了。
// badconst fetch = function(){};const get = function (){};const hold = function() {};// goodconst fetch = function () {};
<a name="functions--spread-vs-apply"></a><a name="5.10"></a>
5.10 优先使用扩展运算符
...
来调用参数可变的函数。 eslint:prefer-spread
为什么? 这样写代码更干净,不必提供上下文。
// badconst x = [1, 2, 3, 4, 5];console.log.apply(console, x);// goodconst x = [1, 2, 3, 4, 5];console.log(...x);// badnew (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));// goodnew Date(...[2016, 8, 5]);
<a name="functions--defaults-last"></a><a name="5.11"></a>
5.11 有默认值的参数总是放在最后一个。
// badconst handleThings = function (opts = {}, name) { // ...};// goodconst handleThings = function (name, opts = {}) { // ...};``` ```
<a name="functions--complexity"></a><a name="5.12"></a>
5.12 建议函数的圈复杂度(代码的独立现行路径条数)==在20以内==。 eslint:
complexity
为什么? 这样可以减少函数的复杂度,使函数容易看懂。
<a name="arrow-functions"></a>
箭头函数
6.1 <a name='6.1'></a> 当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数符号。eslint:
prefer-arrow-callback
,arrow-spacing
为什么?因为箭头函数创造了新的一个
this
执行环境(译注:参考 Arrow functions - JavaScript | MDN 和 ES6 arrow functions, syntax and lexical scoping),通常情况下都能满足你的需求,而且这样的写法更为简洁。为什么不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。
// bad[1, 2, 3].map(function (num) { const sum = num + 1; return num * sum; });// good[1, 2, 3].map((num) => { const sum = num + 1; return num * sum; });
6.2 <a name='6.2'></a> 如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和
return
都省略掉。如果不是,那就不要省略。eslint:arrow-parens
,arrow-body-style
为什么?语法糖。在链式调用中可读性很高。
// 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 effectsconst foo = function (callback) { const val = callback(); if (val === true) { // Do something if callback returns true } };let bool = false;// badfoo(() => bool = true);// goodfoo(() => { bool = true; });
<a name="arrows--paren-wrap"></a><a name="6.3"></a>
6.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, ) ));
<a name="arrows--one-arg-parens"></a><a name="6.4"></a>
6.4 入参必须用括号括起来。eslint:
arrow-parens
为什么? 保持代码清晰和一致性。
// bad[1, 2, 3].map(num => num * num);// good[1, 2, 3].map((num) => num * num);// bad[1, 2, 3].map(num => { const sum = num + 1; return num * sum; });// good[1, 2, 3].map((num) => { const sum = num + 1; return num * sum; });
<a name="arrows--confusing"></a><a name="6.5"></a>
6.5 避免混用箭头函数的语法 (
=>
) 和比较运算符 (<=
,>=
)。 eslint:no-confusing-arrow
// badconst itemHeight = item => item.height > 1000 ? item.largeSize : item.smallSize;// badconst itemHeight = (item) => item.height > 1000 ? item.largeSize : item.smallSize;// goodconst itemHeight = (item) => (item.height > 1000 ? item.largeSize : item.smallSize);// goodconst itemHeight = (item) => { const { height, largeSize, smallSize, } = item; return height > 1000 ? largeSize : smallSize; };
<a name="modules"></a>
模块 Modules
7.1 <a name='7.1'></a> 总是使用模组 (
import
/export
) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。为什么?模块化是未来趋势。
// badconst AirbnbStyleGuide = require('./AirbnbStyleGuide');module.exports = AirbnbStyleGuide.es6;// okimport AirbnbStyleGuide from './AirbnbStyleGuide';export default AirbnbStyleGuide.es6;// bestimport {es6} from './AirbnbStyleGuide';export default es6;
7.2 <a name='7.2'></a> 不要使用通配符 import。
为什么?这样能确保你只有一个默认 export。
// badimport * as AirbnbStyleGuide from './AirbnbStyleGuide';// goodimport AirbnbStyleGuide from './AirbnbStyleGuide';
7.3 <a name='7.3'></a>不要从 import 中直接 export。
为什么?虽然一行代码简洁明了,但让 import 和 export 各司其职让事情能保持一致。
// bad// filename es6.jsexport {es6 as default} from './airbnbStyleGuide';// good// filename es6.jsimport {es6} from './AirbnbStyleGuide';export default es6;
<a name="modules--no-duplicate-imports"></a>
7.4 不要多次 import 同一个文件。eslint:
no-duplicate-imports
Why? 多个地方引入同一个文件会使代码难以维护。
// badimport foo from 'foo';// … some other imports … //import {named1, named2} from 'foo';// goodimport foo, {named1, named2} from 'foo';// goodimport foo, { named1, named2, } from 'foo';
<a name="iterators-and-generators"></a>
Iterators and Generators
8.1 <a name='8.1'></a> 不要使用 iterators。使用高阶函数例如
map()
和reduce()
替代for-of
和for-in
。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];// badlet sum = 0;for (let num of numbers) { sum += num; } sum === 10;// goodlet sum = 0; numbers.forEach((num) => { sum += num; }); sum === 10;// best (use the functional force)const sum = numbers.reduce((total, num) => total + num, 0); sum === 10;
8.2 <a name='8.2'></a> 现在还不要使用 generators。
为什么?因为它们现在还没法很好地编译到 ES5。
<a name="properties"></a>
属性 Properties
9.1 <a name='9.1'></a> 使用
.
来访问对象的属性。eslint:dot-notation
const luke = { jedi: true, age: 12, };// badconst isJedi = luke['jedi'];// goodconst isJedi = luke.jedi;
9.2 <a name='9.2'></a> 当通过变量访问属性时使用中括号
[]
。const luke = { jedi: true, age: 12, };const getProp = (prop) => { return luke[prop]; }const isJedi = getProp('jedi');
<a name="variables"></a>
变量 Variables
<a name="variables--no-chain-assignment"></a><a name="10.1"></a>
11.1 不要链式的给变量赋值。eslint:
no-multi-assign
为什么?链式变量赋值会创建隐式的全局变量。
// bad(function example() { // JavaScript interprets this as // let a = ( b = ( c = 1 ) ); // The let keyword only applies to variable a; variables b and c become // global variables. let cat = dog = bird = 1; }());console.log(cat); // throws ReferenceErrorconsole.log(dog); // 1console.log(bird); // 1// good(function example() { let cat = 1; let dog = cat; let bird = cat; }());console.log(cat); // throws ReferenceErrorconsole.log(dogb); // throws ReferenceErrorconsole.log(bird); // throws ReferenceError// the same applies for `const`
<a name="variables--unary-increment-decrement"></a><a name="10.2"></a>
10.2 避免使用累加和累减符号 (++, --)。 eslint
no-plusplus
为什么?根据 eslint 文档,累加和累减会受到自动插入分号的影响,并可能导致一些意外情况发生。
// badconst 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++; } }// goodconst array = [1, 2, 3];let num = 1; num += 1; num -= 1;const sum = array.reduce((a, b) => a + b, 0);const truthyCount = array.filter(Boolean).length;
<a name="variables--no-magic-numbers"></a><a name="10.3"></a>
10.3 避免使用魔法数字(白名单如下)。 eslint
no-magic-numbers
为什么?魔法数字让人难难以明白开发者的意图是什么,破坏代码的可读性和可维护性。
// 以下为魔法数字白名单let magicNumberIgnore = [];// baisc numbermagicNumberIgnore = magicNumberIgnore.concat([ -1, 0, 100, 1000, 10000]);// datetime numbermagicNumberIgnore = magicNumberIgnore.concat([ 12, 24, 60, 3600]);// httpcode numbermagicNumberIgnore = magicNumberIgnore.concat([ 200, 301, 302, 303, 304, 400, 401, 402, 403, 404, 500, 501, 502, 503, 504]);// bit numbermagicNumberIgnore = magicNumberIgnore.concat([ 1024]);// number 1-49for (i = 1; i <= 49; i++) { if (magicNumberIgnore.indexOf(i) === -1) { magicNumberIgnore.push(i); } }
// badlet soldNum = 102;let initNum = 80;// goodconst APPLE = 102;const STOCK = 80;let soldNum = APPLE;let initNum = STOCK;
<a name="hoisting"></a>
Hoisting
11.1 <a name='11.1'></a>
var
声明会被提升至该作用域的顶部,但它们赋值不会提升。let
和const
被赋予了一种称为「暂时性死区(Temporal Dead Zones, TDZ)」的概念。这对于了解为什么 typeof 不再安全相当重要。// 我们知道这样运行不了// (假设 notDefined 不是全局变量)const example = function () { console.log(notDefined); // => throws a ReferenceError};// 由于变量提升的原因,// 在引用变量后再声明变量是可以运行的。// 注:变量的赋值 `true` 不会被提升。const example = function () { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; };// 编译器会把函数声明提升到作用域的顶层,// 这意味着我们的例子可以改写成这样:const example = function () { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; };// 使用 const 和 letconst example = function () { console.log(declaredButNotAssigned); // => throws a ReferenceError console.log(typeof declaredButNotAssigned); // => throws a ReferenceError const declaredButNotAssigned = true; };
11.2 <a name='11.2'></a> 匿名函数表达式的变量名会被提升,但函数内容并不会。
const example = function () { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function() { console.log('anonymous function expression'); }; };
11.3 <a name='11.3'></a> 命名的函数表达式的变量名会被提升,但函数名和函数内容并不会。
const example = function () { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var named = function superPower() { console.log('Flying'); }; };// the same is true when the function name// is the same as the variable name.const example = function () { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); } };
11.4 <a name='11.4'></a> 函数声明的名称和函数体都会被提升。
const example = function () { superPower(); // => Flying function superPower() { console.log('Flying'); } };
想了解更多信息,参考 Ben Cherry 的 JavaScript Scoping & Hoisting。
<a name="comparison-operators--equality"></a>
比较运算符和等号
12.1 <a name='12.1'></a> 使用
===
和!==
而不是==
和!=
。eslint:eqeqeq
12.2 <a name='12.2'></a> 条件表达式例如
if
语句通过抽象方法ToBoolean
强制计算它们的表达式并且总是遵守下面的规则:if ([0] && []) { // true // an array (even an empty one) is an object, objects will evaluate to true}
对象 被计算为 true
Undefined 被计算为 false
Null 被计算为 false
布尔值 被计算为 布尔的值
数字 如果是 +0、-0、或 NaN 被计算为 false, 否则为 true
字符串 如果是空字符串
''
被计算为 false,否则为 true12.3 <a name='12.3'></a> 布尔值使用简写,但是需要显示比较字符串和数字。
// badif (isValid === true) { // ...}// goodif (isValid) { // ...}// badif (name) { // ...}// goodif (name !== '') { // ...}// badif (collection.length) { // ...}// goodif (collection.length > 0) { // ...}
12.4 <a name='12.4'></a> 想了解更多信息,参考 Angus Croll 的 Truth Equality and JavaScript。
<a name="comparison--switch-blocks"></a><a name="12.5"></a>
12.5 当
case
和default
包含的子句中有词法声明时,用大括号包裹。(例如let
,const
,function
, andclass
). eslint:no-case-declarations
为什么?词法声明在整个
switch
块中是可见的,但只有在代码执行到case
中变量被赋值时才会被初始化,当多个case
子句中定义相同的变量是时会出现问题。// badswitch (foo) { case 1: let xx = 1; break; case 2: const yy = 2; break; case 3: const func = function () { // ... }; break; default: get(); }// goodswitch (foo) { case 1: { let xx = 1; break; } case 2: { const yy = 2; break; } case 3: { const func = function () { // ... }; break; } case 4: bar(); break; default: { get(); } }
<a name="comparison--nested-ternaries"></a><a name="12.6"></a>
12.6 三元表达式不能嵌套使用。 eslint:
no-nested-ternary
// badconst foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null;// split into 2 separated ternary expressionsconst maybeNull = value1 > value2 ? 'baz' : null;// betterconst foo = maybe1 > maybe2 ? 'bar' : maybeNull;// bestconst foo = maybe1 > maybe2 ? 'bar' : maybeNull;
<a name="comparison--unneeded-ternary"></a><a name="12.7"></a>
12.7 避免出现不必要的三元表达式。 eslint:
no-unneeded-ternary
// badconst foo = maybe1 ? maybe1 : maybe2;const bar = correct ? true : false;const baz = wrong ? false : true;// goodconst foo = maybe1 || maybe2;const bar = !!correct;const baz = !wrong;
<a name="comparison--no-mixed-operators"></a>
12.8 当混用操作符时,要用括号括起来。唯一的例外是标准的算术运算符 (
+
,-
,*
, &/
),因为它们很容易理解。 eslint:no-mixed-operators
为什么?这提高了代码的可读性,并且使得开发者的意图很清晰。
// badconst foo = p1 && p2 < 0 || p3 > 0 || p4 + 1 === 0;// badconst bar = p1 ** p2 - 5 % p4;// bad// one may be confused into thinking (p1 || p2) && p3if (p1 || p2 && p3) { return p4; }// goodconst foo = (p1 && p2 < 0) || p3 > 0 || (p4 + 1 === 0);// goodconst bar = (p1 ** p2) - (5 % p4);// goodif (p1 || (p2 && p3)) { return p4; }// goodconst bar = p1 + (p2 / p3 * p4);
<a name="blocks"></a>
代码块 Blocks
13.1 <a name='13.1'></a> 使用大括号包裹所有的多行代码块。eslint:
nonblock-statement-body-position
// badif (test) return false;// goodif (test) { return false; }// goodif (test) { return false; }// badconst func = function () {return false};// goodconst func = function () { return false; };
13.2 <a name='13.2'></a> 如果通过
if
和else
使用多行代码块,把else
放在if
代码块关闭括号的同一行。eslint:brace-style
// badif (test) { thing1(); thing2(); }else { thing3(); }// goodif (test) { thing1(); thing2(); } else { thing3(); }
<a name="blocks--no-else-return"></a><a name="13.3"></a>
13.3 如果
if
代码块中肯定会执行return
语句,那么后面的else
就没必要写了。如果在else if
代码块中又有if
,并且if
代码块会执行return
语句,那么可以分拆成多个if
代码块。 eslint:no-else-return
// badconst foo = function () { if (correct) { return correct; } else { return wrong; } };// badconst cats = function () { if (correct) { return correct; } else if (wrong) { return wrong; } };// badconst dogs = function () { if (correct) { return correct; } else { if (wrong) { return wrong; } } };// goodconst foo = function () { if (correct) { return correct; } return wrong; };// goodconst cats = function () { if (correct) { return correct; } if (wrong) { return wrong; } return false; };//goodconst dogs = function (correct) { if (correct) { if (confuse) { return wrong; } } else { return confuse; } };
<a name="comments"></a>
注释 Comments
14.1 <a name='14.1'></a> 所有的注释开头都加一个空格,这样更利于阅读。eslint:
spaced-comment
// bad//is current tabconst active = true;// good// is current tabconst active = true;// bad/** *make() returns a new element *based on the passed-in tag name */const make = function (tag) { // ... return element; };// good/** * make() returns a new element * based on the passed-in tag name */const make = function (tag) { // ... return element; };
14.2 <a name='14.2'></a> 给注释增加
FIXME
或TODO
的前缀可以帮助其他开发者快速了解这是一个需要复查的问题,或是给需要实现的功能提供一个解决方式。这将有别于常见的注释,因为它们是可操作的。使用FIXME -- need to figure this out
或者TODO -- need to implement
。14.3 <a name='14.3'></a> 使用
// FIXME
: 标注问题。class Calculator { constructor() { // FIXME: shouldn't use a global here total = 0; } }
14.4 <a name='14.4'></a> 使用
// TODO
: 标注问题的解决方式。class Calculator { constructor() { // TODO: total should be configurable by an options param this.total = 0; } }
<a name="whitespace"></a>
空格 Whitespace
15.1 <a name='15.1'></a> 使用 4 个空格作为缩进。eslint:
indent
// badconst func = function () { ∙∙const name = ''; }// badconst func = function () { ∙const name = ''; }// goodconst func = function () { ∙∙∙∙const name = ''; }
15.2 <a name='15.2'></a> 在大括号前放一个空格。eslint:
space-before-blocks
// badconst test = function (){ console.log('test'); }// goodconst test = function () { console.log('test'); }// baddog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', });// gooddog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', });
15.3 <a name='15.3'></a> 在控制语句(
if
、while
等)的小括号前放一个空格。在函数调用及声明中,不要在函数的参数列表前加空格。eslint:keyword-spacing
// badif(isJedi) { fight (); }// goodif (isJedi) { fight(); }// badconst fight = function() { console.log ('Swooosh!'); }// goodconst fight = function () { console.log('Swooosh!'); }
15.4 <a name='15.4'></a> 使用空格把运算符隔开。eslint:
space-infix-ops
// badconst i=j+5;// goodconst i = j + 5;
15.5 <a name='15.5'></a> 在文件末尾插入一个空行。eslint:
eol-last
// badimport {es6} from './AirbnbStyleGuide'; // ...export default es6;
// badimport {es6} from './AirbnbStyleGuide'; // ...export default es6;
// goodimport {es6} from './AirbnbStyleGuide'; // ...export default es6;
15.6 <a name='15.6'></a> 在使用长方法链时进行缩进。使用前面的点
.
强调这是方法调用而不是新语句。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();// badconst leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')') .call(tron.led);// goodconst 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);// goodconst leds = stage.selectAll('.led').data(data);
15.7 <a name='15.7'></a> 在块末和新语句前插入空行。
// badif (foo) { return bar; }return baz;// goodif (foo) { return bar; }return baz;// badconst obj = { foo() { }, bar() { }, };return obj;// goodconst obj = { foo() { }, bar() { }, };return obj;
<a name="whitespace--padded-blocks"></a><a name="15.8"></a>
15.8 不要在 block 代码块中加空行。 eslint:
padded-blocks
// badconst bar = function () { console.log(foo); };// badif (baz) { console.log(qux); } else { console.log(foo); }// badclass Foo { constructor(bar) { this.bar = bar; } }// goodconst bar = function () { console.log(foo); };// goodif (baz) { console.log(qux); } else { console.log(foo); }
<a name="whitespace--in-parens"></a><a name="15.9"></a>
15.9 不要在小括号中加空格。 eslint:
space-in-parens
// badconst bar = function ( foo ) { return foo; };// goodconst bar = function (foo) { return foo; };// badif ( foo ) { console.log(foo); }// goodif (foo) { console.log(foo); }
<a name="whitespace--in-brackets"></a><a name="15.10"></a>
15.10 不要在中括号中加空格。 eslint:
array-bracket-spacing
// badconst foo = [ 1, 2, 3 ];console.log(foo[ 0 ]);// goodconst foo = [1, 2, 3];console.log(foo[0]);
<a name="whitespace--in-braces"></a><a name="15.11"></a>
15.11 不要在大括号中加空格。 eslint:
object-curly-spacing
// badconst foo = { clark: 'kent' };// goodconst foo = {clark: 'kent'};
<a name="whitespace--max-len"></a><a name="15.12"></a>
15.12 尽量控制一行代码在==100==个字符以内(包括空格)。字符串、字符串模板和 URL 不受限制。eslint:
max-len
为什么?这么做保证了可读性和可维护性。
// badconst 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.'));// goodconst 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(() => { // do something... }) .fail(() => { // do something... });
<a name="commas"></a>
逗号 Commas
16.1 <a name='16.1'></a> 行首逗号:禁用。eslint:
comma-style
// badconst story = [ once , upon , aTime ];// goodconst story = [ once, upon, aTime, ];// badconst hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: '1815' , superPower: 'computers'};// goodconst hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: '1815', superPower: 'computers', };
16.2 <a name='16.2'></a> 增加结尾的逗号:需要。eslint:
comma-dangle
为什么? 这会让 git diffs 更干净。另外,像 babel 这样的转译器会移除结尾多余的逗号,也就是说你不必担心老旧浏览器的尾逗号问题。
// bad - git diff without trailing commaconst hero = { firstName: 'Florence', - lastName: 'Nightingale'+ lastName: 'Nightingale', + inventorOf: ['coxcomb graph', 'modern nursing'] };// good - git diff with trailing commaconst hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], };// badconst hero = { firstName: 'Dana', lastName: 'Scully'};const heroes = [ 'Batman', 'Superman'];// goodconst hero = { firstName: 'Dana', lastName: 'Scully', };const heroes = [ 'Batman', 'Superman', ];// badconst createHero = function ( firstName, lastName, inventorOf) { // does nothing};// goodconst createHero = function ( firstName, lastName, inventorOf,) { // does nothing};// good (note that a comma must not appear after a "rest" element)const createHero = function ( firstName, lastName, inventorOf, ...heroArgs) { // does nothing};// badcreateHero( firstName, lastName, inventorOf );// goodcreateHero( firstName, lastName, inventorOf, );// goodcreateHero( firstName, lastName, inventorOf, ...heroArgs );
<a name="semicolons"></a>
分号 Semicolons
17.1 <a name='17.1'></a> 使用分号。eslint:
semi
Why? When JavaScript encounters a line break without a semicolon, it uses a set of rules called Automatic Semicolon Insertion to determine whether or not it should regard that line break as the end of a statement, and (as the name implies) place a semicolon into your code before the line break if it thinks so. ASI contains a few eccentric behaviors, though, and your code will break if JavaScript misinterprets your line break. These rules will become more complicated as new features become a part of JavaScript. Explicitly terminating your statements and configuring your linter to catch missing semicolons will help prevent you from encountering issues.
// bad - raises exceptionconst luke = {}const leia = {} [luke, leia].forEach(jedi => jedi.father = 'vader')// bad - raises exceptionconst reaction = "No! That's impossible!"(async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ...}())// bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI!const foo = foo() { return 'search your feelings, you know it to be foo'};// goodconst luke = {};const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; });// goodconst reaction = "No! That's impossible!"; (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ...}());// goodconst foo = foo() { return 'search your feelings, you know it to be foo'; };
<a name="type-casting--coercion"></a>
类型转换
18.1 <a name='18.1'></a> 在语句开始时执行类型转换。
18.2 <a name='18.2'></a> 字符串:eslint:
no-new-wrappers
// => this.reviewScore = 9;// badconst totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"// badconst totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()// badconst totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string// goodconst totalScore = String(this.reviewScore);
18.3 <a name='18.3'></a> 对数字使用
parseInt
转换,并带上类型转换的基数。(不强制)eslint:radix
no-new-wrappers
const inputValue = '4';// badconst val = new Number(inputValue);// badconst val = +inputValue;// badconst val = inputValue >> 0;// badconst val = parseInt(inputValue);// goodconst val = Number(inputValue);// goodconst val = parseInt(inputValue, 10);
18.4 <a name='18.4'></a> 如果因为某些原因 parseInt 成为你所做的事的瓶颈而需要使用位操作解决性能问题时,留个注释说清楚原因和你的目的。
// good/** * 使用 parseInt 导致我的程序变慢, * 改成使用位操作转换数字快多了。 */const val = inputValue >> 0;
18.5 <a name='18.5'></a> 注: 小心使用位操作运算符。数字会被当成 64 位值,但是位操作运算符总是返回 32 位的整数(参考)。位操作处理大于 32 位的整数值时还会导致意料之外的行为。关于这个问题的讨论。最大的 32 位整数是 2,147,483,647:
2147483647 >> 0 //=> 21474836472147483648 >> 0 //=> -21474836482147483649 >> 0 //=> -2147483647
18.6 <a name='18.6'></a> 布尔:eslint:
no-new-wrappers
const age = 0;// badconst hasAge = new Boolean(age);// goodconst hasAge = Boolean(age);// goodconst hasAge = !!age;
<a name="naming-conventions"></a>
命名规则
19.1 <a name='19.1'></a> 避免单字母命名(
e、i、j、v、k、t除外
)。避免超长变量名(长度不超过60)。命名应具备描述性和可读性。eslint:id-length
// badconst q = function () { // ...stuff...};// badconst getWebBeibei11MaylikeRecommendPageSize20Page1XidMTcxNTM2Njcxsa = function () { // ...stuff...};// goodconst query = function () { // ...stuff..。};
19.2 <a name='19.2'></a> 使用驼峰式命名对象、函数和实例。(对象的属性不限制)eslint:
camelcase
// badconst OBJEcttsssss = {};const this_is_my_object = {};// goodconst thisIsMyObject = {};const thisIsMyFunction = function () {}
19.3 <a name='19.3'></a> 使用帕斯卡式命名构造函数或类。eslint:
new-cap
// badfunction user(options) { this.name = options.name; }const bad = new user({ name: 'nope', });// goodclass User { constructor(options) { this.name = options.name; } }const good = new User({ name: 'yup', });
19.4 <a name='19.4'></a> 别保存
this
的引用。使用箭头函数或 Function#bind。// badconst foo = function () { const self = this; return function() { console.log(self); }; };// badconst foo = function () { const that = this; return function() { console.log(that); }; };// goodconst foo = function () { return () => { console.log(this); }; };
19.5 <a name='19.5'></a> 如果你的文件只输出一个类,那你的文件名必须和类名完全保持一致。
// file contentsclass CheckBox { // ...}export default CheckBox;// in some other file// badimport CheckBox from './checkBox';// badimport CheckBox from './check_box';// goodimport CheckBox from './CheckBox';
19.6 <a name='19.6'></a> 当你导出默认的函数时使用驼峰式命名。你的文件名必须和函数名完全保持一致。
const makeStyleGuide = function () { }export default makeStyleGuide;
19.7 <a name='19.7'></a> 当你导出单例、函数库、空对象时使用帕斯卡式命名。
const AirbnbStyleGuide = { es6: { } };export default AirbnbStyleGuide;
作者:小红依
链接:https://www.jianshu.com/p/3eee4acbf071