参考

airbnb:https://github.com/airbnb/javascript

引用

  • 所有的赋值都用 const,避免使用 var

    为什么?因为这个能确保你不会改变你的初始值,重复引用会导致 bug 并且使代码变得难以理解。

  1. // bad
  2. var a = 1;
  3. var b = 2;
  4. // good
  5. const a = 1;
  6. const b = 2;
  • 如果你一定要对参数重新赋值,使用 let,而不是 var

    为什么?因为 let 是块级作用域,而 var 是函数级作用域。

  1. // bad
  2. var count = 1;
  3. if (true) {
  4. count += 1;
  5. }
  6. // good, use the let.
  7. let count = 1;
  8. if (true) {
  9. count += 1;
  10. }

[

](#%E7%9B%AE%E5%BD%95)

对象

  • 使用字面值创建对象。 ```javascript // bad const item = new Object();

// good const item = {};

  1. - 用对象方法简写。
  2. ```javascript
  3. // bad
  4. const atom = {
  5. value: 1,
  6. addValue: function (value) {
  7. return atom.value + value;
  8. },
  9. };
  10. // good
  11. const atom = {
  12. value: 1,
  13. // 对象的方法
  14. addValue(value) {
  15. return atom.value + value;
  16. },
  17. };
  • 用属性值缩写。

    为什么?因为这样写的更少且可读性更高。

  1. const lukeSkywalker = 'Luke Skywalker';
  2. // bad
  3. const obj = {
  4. lukeSkywalker: lukeSkywalker,
  5. };
  6. // good
  7. const obj = {
  8. lukeSkywalker,
  9. };
  • 将你的所有缩写放在对象声明的前面。

    为什么?因为这样能更方便地知道有哪些属性用了缩写。

  1. const anakinSkywalker = 'Anakin Skywalker';
  2. const lukeSkywalker = 'Luke Skywalker';
  3. // bad
  4. const obj = {
  5. episodeOne: 1,
  6. twoJediWalkIntoACantina: 2,
  7. lukeSkywalker,
  8. episodeThree: 3,
  9. mayTheFourth: 4,
  10. anakinSkywalker,
  11. };
  12. // good
  13. const obj = {
  14. lukeSkywalker,
  15. anakinSkywalker,
  16. episodeOne: 1,
  17. twoJediWalkIntoACantina: 2,
  18. episodeThree: 3,
  19. mayTheFourth: 4,
  20. };
  • 只对那些无效的标示使用引号 ''

    为什么?通常我们认为这种方式主观上更易读。不仅优化了代码高亮,而且也更容易被许多 JS 引擎优化。

  1. // bad
  2. const bad = {
  3. 'foo': 3,
  4. 'bar': 4,
  5. 'data-blah': 5,
  6. };
  7. // good
  8. const good = {
  9. foo: 3,
  10. bar: 4,
  11. 'data-blah': 5,
  12. };
  • 不要直接调用 Object.prototype上的方法,如 hasOwnPropertypropertyIsEnumerableisPrototypeOf

    为什么?在一些有问题的对象上,这些方法可能会被屏蔽掉,如:{ hasOwnProperty: false } 或空对象 Object.create(null)

  1. // bad
  2. console.log(object.hasOwnProperty(key));
  3. // good
  4. console.log(Object.prototype.hasOwnProperty.call(object, key));
  5. // best
  6. const has = Object.prototype.hasOwnProperty; // 在模块作用域内做一次缓存。
  7. console.log(has.call(object, key));
  8. /* or */
  9. import has from 'has'; // https://www.npmjs.com/package/has
  10. console.log(has(object, key));
  • 对象浅拷贝时,更推荐使用扩展运算符(即 ... 运算符),而不是 [Object.assign](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)。获取对象指定的几个属性时,用对象的 rest 解构运算符(即 ... 运算符)更好。
  1. // very bad
  2. const original = { a: 1, b: 2 };
  3. const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
  4. delete copy.a; // so does this
  5. // bad
  6. const original = { a: 1, b: 2 };
  7. const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
  8. // good es6 扩展运算符 ...
  9. const original = { a: 1, b: 2 };
  10. // 浅拷贝
  11. const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
  12. // rest 解构运算符
  13. const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

[

](#%E7%9B%AE%E5%BD%95)

数组

  • 用字面量创建数组。 ```javascript // bad const items = new Array();

// good const items = [];

  1. - [Array#push](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push) 代替直接向数组中添加一个值。
  2. ```javascript
  3. const someStack = [];
  4. // bad
  5. someStack[someStack.length] = 'abracadabra';
  6. // good
  7. someStack.push('abracadabra');
  • 用扩展运算符做数组浅拷贝,类似上面的对象浅拷贝。
    ```javascript // bad const len = items.length; const itemsCopy = []; let i;

for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; }

// good const itemsCopy = […items];

  1. - `...` 运算符而不是 `[Array.from](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from)` 来将一个可迭代的对象转换成数组。
  2. ```javascript
  3. const foo = document.querySelectorAll('.foo');
  4. // good
  5. const nodes = Array.from(foo);
  6. // best
  7. const nodes = [...foo];
  • [Array.from](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from) 将一个类数组对象转成一个数组。
    ```javascript const arrLike = { 0: ‘foo’, 1: ‘bar’, 2: ‘baz’, length: 3 };

// bad const arr = Array.prototype.slice.call(arrLike);

// good const arr = Array.from(arrLike);

  1. - `[Array.from](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from)` 而不是 `...` 运算符去做 map 遍历。 因为这样可以避免创建一个临时数组。
  2. ```javascript
  3. // bad
  4. const baz = [...foo].map(bar);
  5. // good
  6. const baz = Array.from(foo, bar);
  • 4.8 如果一个数组有很多行,在数组的 [ 后和 ] 前断行。请看下面示例:
    ```javascript // 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, ];

  1. [
  2. ](#%E7%9B%AE%E5%BD%95)
  3. <a name="2be143d5"></a>
  4. ## 解构
  5. - 用对象的解构赋值来获取和使用对象某个或多个属性值。
  6. > 为什么?解构使您不必为这些属性创建临时引用,并且避免重复引用对象。重复引用对象将造成代码重复、增加阅读次数、提高犯错概率。
  7. ```javascript
  8. // bad
  9. function getFullName(user) {
  10. const firstName = user.firstName;
  11. const lastName = user.lastName;
  12. return `${firstName} ${lastName}`;
  13. }
  14. // good
  15. function getFullName(user) {
  16. const { firstName, lastName } = user;
  17. return `${firstName} ${lastName}`;
  18. }
  19. // best
  20. function getFullName({ firstName, lastName }) {
  21. return `${firstName} ${lastName}`;
  22. }
  • 用数组解构。
    ```javascript const arr = [1, 2, 3, 4];

// bad const first = arr[0]; const second = arr[1];

// good const [first, second] = arr;

  1. - 多个返回值用对象的解构,而不是数组解构。
  2. > 为什么?你可以在后期添加新的属性或者变换变量的顺序而不会破坏原有的引用。
  3. ```javascript
  4. // bad
  5. function processInput(input) {
  6. // 然后就是见证奇迹的时刻
  7. return [left, right, top, bottom];
  8. }
  9. // 调用者需要想一想返回值的顺序
  10. const [left, __, top] = processInput(input);
  11. // good
  12. function processInput(input) {
  13. // oops,奇迹又发生了
  14. return { left, right, top, bottom };
  15. }
  16. // 调用者只需要选择他想用的值就好了
  17. const { left, top } = processInput(input);

[

](#%E7%9B%AE%E5%BD%95)

字符串

  • 字符串应使用单引号 '' 。 ```javascript // bad const name = “Capt. Janeway”;

// bad - 模板字符串应该包含插入文字或换行 const name = Capt. Janeway;

// good const name = ‘Capt. Janeway’;

  1. - 超过 100 个字符的字符串不应该用字符串连接成多行。
  2. > 为什么?字符串折行增加编写难度且不易被搜索。
  3. ```javascript
  4. // bad
  5. const errorMessage = 'This is a super long error that was thrown because \
  6. of Batman. When you stop to think about how Batman had anything to do \
  7. with this, you would get nowhere \
  8. fast.';
  9. // bad
  10. const errorMessage = 'This is a super long error that was thrown because ' +
  11. 'of Batman. When you stop to think about how Batman had anything to do ' +
  12. 'with this, you would get nowhere fast.';
  13. // good
  14. 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.';
  • 当需要动态生成字符串时,使用模板字符串而不是字符串拼接。

    为什么?模板字符串更具可读性、多行语法更简洁以及更方便插入变量到字符串里头。

  1. // bad
  2. function sayHi(name) {
  3. return 'How are you, ' + name + '?';
  4. }
  5. // bad
  6. function sayHi(name) {
  7. return ['How are you, ', name, '?'].join();
  8. }
  9. // bad
  10. function sayHi(name) {
  11. return `How are you, ${ name }?`;
  12. }
  13. // good
  14. function sayHi(name) {
  15. return `How are you, ${name}?`;
  16. }
  • 永远不要使用 eval(),该方法有太多漏洞。

[

](#%E7%9B%AE%E5%BD%95)

函数

  • 使用命名函数表达式而不是函数声明。
  1. // bad
  2. function foo() {
  3. // ...
  4. }
  5. // bad
  6. const foo = function () {
  7. // ...
  8. };
  9. // good
  10. // lexical name distinguished from the variable-referenced invocation(s)
  11. // 函数表达式名和声明的函数名是不一样的
  12. const short = function longUniqueMoreDescriptiveLexicalFoo() {
  13. // ...
  14. };
  • 把立即执行函数包裹在圆括号里。
  1. // immediately-invoked function expression (IIFE)
  2. (function () {
  3. console.log('Welcome to the Internet. Please follow me.');
  4. }());
  • 注意:ECMA-262 中对块(block)的定义是: 一系列的语句。但是函数声明不是一个语句, 函数表达式是一个语句。
    ```javascript // bad if (currentUser) { function test() { console.log(‘Nope.’); } }

// good let test; if (currentUser) { test = () => { console.log(‘Yup.’); }; }

  1. - 不要用 `arguments` 命名参数。他的优先级高于每个函数作用域自带的 `arguments` 对象,这会导致函数自带的 `arguments` 值被覆盖。
  2. ```javascript
  3. // bad
  4. function foo(name, options, arguments) {
  5. // ...
  6. }
  7. // good
  8. function foo(name, options, args) {
  9. // ...
  10. }
  • 不要使用 arguments,用收集参数语法 ... 代替。``

    为什么?... 明确你想用哪个参数。而且收集参数是真数组,而不是类似数组的 arguments

  1. // bad
  2. function concatenateAll() {
  3. const args = Array.prototype.slice.call(arguments);
  4. return args.join('');
  5. }
  6. // good
  7. function concatenateAll(...args) {
  8. return args.join('');
  9. }
  • 用默认参数语法而不是在函数里对参数重新赋值。
    ```javascript // really bad function handleThings(opts) { // 不!我们不该修改 arguments // 第二:如果 opts 的值为 false, 它会被赋值为 {} // 虽然你想这么写,但是这个会带来一些微妙的 bug。 opts = opts || {}; // … }

// still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // … }

// good function handleThings(opts = {}) { // … }

  1. - 避免默认参数的副作用。
  2. > 为什么?他会令人迷惑不解,比如下面这个,a 到底等于几,这个需要想一下。
  3. ```javascript
  4. var b = 1;
  5. // bad
  6. function count(a = b++) {
  7. console.log(a);
  8. }
  9. count(); // 1
  10. count(); // 2
  11. count(3); // 3
  12. count(); // 3
  • 把默认参数赋值放在最后。 ```javascript // bad function handleThings(opts = {}, name) { // … }

// good function handleThings(name, opts = {}) { // … }

  1. - 不要用函数构造器创建函数。
  2. > 为什么?以这种方式创建函数将类似于字符串 eval(),存在漏洞。
  3. ```javascript
  4. // bad
  5. var add = new Function('a', 'b', 'return a + b');
  6. // still bad
  7. var subtract = Function('a', 'b', 'return a - b');
  • 不要修改参数.

    为什么?操作参数对象对原始调用者会导致意想不到的副作用。就是不要改参数的数据结构,保留参数原始值和数据结构。

  1. // bad
  2. function f1(obj) {
  3. obj.key = 1;
  4. };
  5. // good
  6. function f2(obj) {
  7. const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
  8. };
  • 不要对参数重新赋值

    为什么?参数重新赋值会导致意外行为,尤其是对 arguments。这也会导致优化问题,特别是在 V8 引擎里。

  1. // bad
  2. function f1(a) {
  3. a = 1;
  4. // ...
  5. }
  6. function f2(a) {
  7. if (!a) { a = 1; }
  8. // ...
  9. }
  10. // good
  11. function f3(a) {
  12. const b = a || 1;
  13. // ...
  14. }
  15. function f4(a = 1) {
  16. // ...
  17. }

[

](#%E7%9B%AE%E5%BD%95)

箭头函数

  • 当你一定要用函数表达式(在回调函数里)的时候,使用箭头函数。

    为什么?箭头函数中的 this 与定义该函数的上下文中的 this 一致,这通常才是你想要的。而且箭头函数是更简洁的语法。

什么时候不用箭头函数:如果你的函数逻辑较复杂,你应该把它单独写入一个命名函数里头。

  1. // bad
  2. [1, 2, 3].map(function (x) {
  3. const y = x + 1;
  4. return x * y;
  5. });
  6. // good
  7. [1, 2, 3].map((x) => {
  8. const y = x + 1;
  9. return x * y;
  10. });
  • 如果表达式涉及多行,把他包裹在圆括号里以提高可读性。

    为什么?这样能清晰地显示函数的开始位置和结束位置。

  1. // bad
  2. ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
  3. httpMagicObjectWithAVeryLongName,
  4. httpMethod
  5. )
  6. );
  7. // good
  8. ['get', 'post', 'put'].map(httpMethod => (
  9. Object.prototype.hasOwnProperty.call(
  10. httpMagicObjectWithAVeryLongName,
  11. httpMethod
  12. )
  13. ));
  • 避免箭头函数(=>)和比较操作符(<=, >=)``
    ```javascript // 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; };

  1. [
  2. ](#%E7%9B%AE%E5%BD%95)
  3. <a name="5cf3e034"></a>
  4. ## 类与构造函数
  5. - 使用 `class` 语法。避免直接操作 `prototype`
  6. > 为什么?`class` 语法更简洁更易理解。
  7. ```javascript
  8. // bad
  9. function Queue(contents = []) {
  10. this.queue = [...contents];
  11. }
  12. Queue.prototype.pop = function () {
  13. const value = this.queue[0];
  14. this.queue.splice(0, 1);
  15. return value;
  16. };
  17. // good
  18. class Queue {
  19. constructor(contents = []) {
  20. this.queue = [...contents];
  21. }
  22. pop() {
  23. const value = this.queue[0];
  24. this.queue.splice(0, 1);
  25. return value;
  26. }
  27. }
  • extends 实现继承。

    为什么?它是一种内置的方法来继承原型功能而不破坏 instanceof

  1. // bad
  2. const inherits = require('inherits');
  3. function PeekableQueue(contents) {
  4. Queue.apply(this, contents);
  5. }
  6. inherits(PeekableQueue, Queue);
  7. PeekableQueue.prototype.peek = function () {
  8. return this.queue[0];
  9. }
  10. // good
  11. class PeekableQueue extends Queue {
  12. peek() {
  13. return this.queue[0];
  14. }
  15. }
  • 方法可以返回 this 来实现链式调用。
    ```javascript // 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);

  1. - 如果没有特别定义,类有默认的构造方法。一个空的构造函数或只是代表父类的构造函数是不需要写的。
  2. ```javascript
  3. // bad
  4. class Jedi {
  5. constructor() {}
  6. getName() {
  7. return this.name;
  8. }
  9. }
  10. // bad
  11. class Rey extends Jedi {
  12. // 这种构造函数是不需要写的
  13. constructor(...args) {
  14. super(...args);
  15. }
  16. }
  17. // good
  18. class Rey extends Jedi {
  19. constructor(...args) {
  20. super(...args);
  21. this.name = 'Rey';
  22. }
  23. }

模块

  • 一个路径只 import 一次。

    为什么?多行导入同一路径将使代码变得难以维护。

  1. // bad
  2. import foo from 'foo';
  3. // … 其他导入 … //
  4. import { named1, named2 } from 'foo';
  5. // good
  6. import foo, { named1, named2 } from 'foo';
  7. // good
  8. import foo, {
  9. named1,
  10. named2,
  11. } from 'foo';
  • 不要导出可变的东西。

    为什么?变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。

  1. // bad
  2. let foo = 3;
  3. export { foo }
  4. // good
  5. const foo = 3;
  6. export { foo }
  • 在一个单一导出模块里,用 export default 更好。

    为什么?鼓励使用更多文件,每个文件只导出一次,这样可读性和可维护性更好。

  1. // bad
  2. export function foo() {}
  3. // good
  4. export default function foo() {}
  • import 放在其他所有语句之前。

    为什么?因为 import 会被提升到代码最前面运行,因此将他们放在最前面以防止发生意外行为。

  1. // bad
  2. import foo from 'foo';
  3. foo.init();
  4. import bar from 'bar';
  5. // good
  6. import foo from 'foo';
  7. import bar from 'bar';
  8. foo.init();
  • 多行 import 应该缩进,就像多行数组和对象字面量一样。

    为什么?花括号与样式指南中每个其他花括号块遵循相同的缩进规则,逗号也是。

  1. // bad
  2. import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
  3. // good
  4. import {
  5. longNameA,
  6. longNameB,
  7. longNameC,
  8. longNameD,
  9. longNameE,
  10. } from 'path';

[

](#%E7%9B%AE%E5%BD%95)

迭代器与生成器

  • 不要用迭代器。使用 JavaScript 高级函数代替 for-infor-of

    为什么?这强调了我们不可变的规则。 处理返回值的纯函数比处理副作用更容易。

用数组的这些迭代方法: map() / every() / filter() / find() / findIndex() / reduce() / some() / … , 用对象的这些方法 Object.keys() / Object.values() / Object.entries() 去产生一个数组,这样你就能去遍历对象了。

  1. const numbers = [1, 2, 3, 4, 5];
  2. // bad
  3. let sum = 0;
  4. for (let num of numbers) {
  5. sum += num;
  6. }
  7. sum === 15;
  8. // good
  9. let sum = 0;
  10. numbers.forEach(num => sum += num);
  11. sum === 15;
  12. // best (use the functional force)
  13. const sum = numbers.reduce((total, num) => total + num, 0);
  14. sum === 15;
  15. // bad
  16. const increasedByOne = [];
  17. for (let i = 0; i < numbers.length; i++) {
  18. increasedByOne.push(numbers[i] + 1);
  19. }
  20. // good
  21. const increasedByOne = [];
  22. numbers.forEach(num => increasedByOne.push(num + 1));
  23. // best (keeping it functional)
  24. const increasedByOne = numbers.map(num => num + 1);

[

](#%E7%9B%AE%E5%BD%95)

属性

  • 访问属性时使用点符号。 ```javascript const luke = { jedi: true, age: 28, };

// bad const isJedi = luke[‘jedi’];

// good const isJedi = luke.jedi;

  1. - 当使用变量获取属性时用方括号 `[]`
  2. ```javascript
  3. const luke = {
  4. jedi: true,
  5. age: 28,
  6. };
  7. function getProp(prop) {
  8. return luke[prop];
  9. }
  10. const isJedi = getProp('jedi');

[

](#%E7%9B%AE%E5%BD%95)

变量

  • 使用 constlet 声明变量。不这样做会导致全局变量。我们想要避免污染全局命名空间。 ```javascript // bad superPower = new SuperPower();

// good const superPower = new SuperPower();

  1. - 为每个变量声明都用一个 `const` `let`
  2. ```javascript
  3. // bad
  4. const items = getItems(),
  5. goSportsTeam = true,
  6. dragonball = 'z';
  7. // bad
  8. // (与前面的比较,找一找错误)
  9. const items = getItems(),
  10. goSportsTeam = true;
  11. dragonball = 'z';
  12. // good
  13. const items = getItems();
  14. const goSportsTeam = true;
  15. const dragonball = 'z';
  • constlet 分别放一起。

    为什么?在你需要分配一个新的变量,而这个变量依赖之前分配过的变量的时候,这种做法是有帮助的。

  1. // bad
  2. let i, len, dragonball,
  3. items = getItems(),
  4. goSportsTeam = true;
  5. // bad
  6. let i;
  7. const items = getItems();
  8. let dragonball;
  9. const goSportsTeam = true;
  10. let len;
  11. // good
  12. const goSportsTeam = true;
  13. const items = getItems();
  14. let dragonball;
  15. let i;
  16. let length;
  • 在你需要的地方声明变量,但是要放在合理的位置。

    为什么?letconst 都是块级作用域而不是函数级作用域。

  1. // bad - 不必要的函数调用。
  2. function checkName(hasName) {
  3. const name = getName();
  4. if (hasName === 'test') {
  5. return false;
  6. }
  7. if (name === 'test') {
  8. this.setName('');
  9. return false;
  10. }
  11. return name;
  12. }
  13. // good
  14. function checkName(hasName) {
  15. if (hasName === 'test') {
  16. return false;
  17. }
  18. // 在需要的时候分配
  19. const name = getName();
  20. if (name === 'test') {
  21. this.setName('');
  22. return false;
  23. }
  24. return name;
  25. }
  • 不要使用链式声明变量。 eslint: [no-multi-assign](https://eslint.org/docs/rules/no-multi-assign)

    为什么?链式声明变量会创建隐式全局变量。

  1. // bad
  2. (function example() {
  3. // JavaScript 将这一段解释为
  4. // let a = ( b = ( c = 1 ) );
  5. // let 只对变量 a 起作用; 变量 b 和 c 都变成了全局变量
  6. let a = b = c = 1;
  7. }());
  8. console.log(a); // undefined
  9. console.log(b); // 1
  10. console.log(c); // 1
  11. // good
  12. (function example() {
  13. let a = 1;
  14. let b = a;
  15. let c = a;
  16. }());
  17. console.log(a); // undefined
  18. console.log(b); // undefined
  19. console.log(c); // undefined
  20. // `const` 也是如此
  • 不要使用一元自增自减运算符(++--).

    为什么?根据 eslint 文档,一元增量和减量语句受到自动分号插入的影响,并且可能会导致应用程序中的值递增或递减的静默错误。 使用 num + = 1 而不是 num ++num ++ 语句也是含义清晰的。 禁止一元增量和减量语句还会阻止您无意地预增/预减值,这也会导致程序出现意外行为。

  1. // bad
  2. const array = [1, 2, 3];
  3. let num = 1;
  4. num++;
  5. --num;
  6. let sum = 0;
  7. let truthyCount = 0;
  8. for (let i = 0; i < array.length; i++) {
  9. let value = array[i];
  10. sum += value;
  11. if (value) {
  12. truthyCount++;
  13. }
  14. }
  15. // good
  16. const array = [1, 2, 3];
  17. let num = 1;
  18. num += 1;
  19. num -= 1;
  20. const sum = array.reduce((a, b) => a + b, 0);
  21. const truthyCount = array.filter(Boolean).length;
  • 在赋值的时候避免在 = 前/后换行。 如果你的赋值语句超出 [max-len](https://eslint.org/docs/rules/max-len.html),那就用小括号把这个值包起来再换行。

    为什么?在 = 附近换行容易混淆这个赋值语句。

  1. // bad
  2. const foo =
  3. superLongLongLongLongLongLongLongLongFunctionName();
  4. // bad
  5. const foo
  6. = 'superLongLongLongLongLongLongLongLongString';
  7. // good
  8. const foo = (
  9. superLongLongLongLongLongLongLongLongFunctionName()
  10. );
  11. // good
  12. const foo = 'superLongLongLongLongLongLongLongLongString';
  • 不允许有未使用的变量。eslint: [no-unused-vars](https://eslint.org/docs/rules/no-unused-vars)

    为什么?一个声明了但未使用的变量更像是由于重构未完成产生的错误。这种在代码中出现的变量会使阅读者迷惑。

  1. // bad
  2. var some_unused_var = 42;
  3. // 写了没用
  4. var y = 10;
  5. y = 5;
  6. // 变量改了自己的值,也没有用这个变量
  7. var z = 0;
  8. z = z + 1;
  9. // 参数定义了但未使用
  10. function getX(x, y) {
  11. return x;
  12. }
  13. // good
  14. function getXPlusY(x, y) {
  15. return x + y;
  16. }
  17. var x = 1;
  18. var y = a + 2;
  19. alert(getXPlusY(x, y));
  20. // 'type' 即使没有使用也可以可以被忽略, 因为这个有一个 rest 取值的属性。
  21. // 这是从对象中抽取一个忽略特殊字段的对象的一种形式
  22. var { type, ...coords } = data;
  23. // 'coords' 现在就是一个没有 'type' 属性的 'data' 对象

比较运算符与相等

  • ===!== 而不是 ==!=
  • casedefault 分句里用大括号创建一块包含词法声明的区域(例如:letconstfunctionclass)。

    为什么?词法声明在整个 switch 的代码块里都可见,但是只有当其被分配后才会初始化,仅当这个 case 被执行时才被初始化。当多个 case 分句试图定义同一个对象时就会出现问题。

  1. // bad
  2. switch (foo) {
  3. case 1:
  4. let x = 1;
  5. break;
  6. case 2:
  7. const y = 2;
  8. break;
  9. case 3:
  10. function f() {
  11. // ...
  12. }
  13. break;
  14. default:
  15. class C {}
  16. }
  17. // good
  18. switch (foo) {
  19. case 1: {
  20. let x = 1;
  21. break;
  22. }
  23. case 2: {
  24. const y = 2;
  25. break;
  26. }
  27. case 3: {
  28. function f() {
  29. // ...
  30. }
  31. break;
  32. }
  33. case 4:
  34. bar();
  35. break;
  36. default: {
  37. class C {}
  38. }
  39. }
  • 三元表达式不应该嵌套,通常是单行表达式。

image.png

  1. // bad
  2. const foo = maybe1 > maybe2
  3. ? "bar"
  4. : value1 > value2 ? "baz" : null;
  5. // better
  6. const maybeNull = value1 > value2 ? 'baz' : null;
  7. const foo = maybe1 > maybe2
  8. ? 'bar'
  9. : maybeNull;
  10. // best
  11. const maybeNull = value1 > value2 ? 'baz' : null;
  12. const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
  • 避免不必要的三元表达式。 ```javascript // 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;

  1. - 用圆括号来组合操作符。 只有当标准的算术运算符(`+`, `-`, `*`, `/`), 并且它们的优先级显而易见时,才可以不用圆括号括起来。
  2. > 为什么?这提高了可读性,并且明确了开发者的意图。
  3. ```javascript
  4. // bad
  5. const foo = a && b < 0 || c > 0 || d + 1 === 0;
  6. // bad
  7. const bar = a ** b - 5 % d;
  8. // bad
  9. // 别人会陷入(a || b) && c 的迷惑中
  10. if (a || b && c) {
  11. return d;
  12. }
  13. // good
  14. const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
  15. // good
  16. const bar = (a ** b) - (5 % d);
  17. // good
  18. if (a || (b && c)) {
  19. return d;
  20. }
  21. // good
  22. const bar = a + b / c * d;

[

](#%E7%9B%AE%E5%BD%95)

  • 如果 if 语句中总是需要用 return 返回,那后续的 else 就不需要写了。 if 块中包含 return, 它后面的 else if 块中也包含了 return, 这个时候就可以把 return 分到多个 if 语句块中。 eslint: [no-else-return](https://eslint.org/docs/rules/no-else-return)
    ```javascript // 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; } }

  1. [
  2. ](#%E7%9B%AE%E5%BD%95)
  3. <a name="ea5302a4"></a>
  4. ## 控制语句
  5. - 当你的控制语句(`if`, `while` 等)太长或者超过最大长度限制的时候,把每一个(组)判断条件放在单独一行里。逻辑操作符放在行首。
  6. > 为什么?把逻辑操作符放在行首是让操作符的对齐方式和链式函数保持一致。这提高了可读性,也让复杂逻辑更清晰。
  7. ```javascript
  8. // bad
  9. if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
  10. thing1();
  11. }
  12. // bad
  13. if (foo === 123 &&
  14. bar === 'abc') {
  15. thing1();
  16. }
  17. // bad
  18. if (foo === 123
  19. && bar === 'abc') {
  20. thing1();
  21. }
  22. // bad
  23. if (
  24. foo === 123 &&
  25. bar === 'abc'
  26. ) {
  27. thing1();
  28. }
  29. // good
  30. if (
  31. foo === 123
  32. && bar === 'abc'
  33. ) {
  34. thing1();
  35. }
  36. // good
  37. if (
  38. (foo === 123 || bar === 'abc')
  39. && doesItLookGoodWhenItBecomesThatLong()
  40. && isThisReallyHappening()
  41. ) {
  42. thing1();
  43. }
  44. // good
  45. if (foo === 123 && bar === 'abc') {
  46. thing1();
  47. }

[

](#%E7%9B%AE%E5%BD%95)

注释

  • 多行注释用 /** ... */
    ```javascript // 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; } ```

  • 所有注释开头空一格,方便阅读。 ```javascript // 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; } ```

[

](#%E7%9B%AE%E5%BD%95)

空格

  • 一个缩进使用两个空格。 ```javascript // bad function foo() { ∙∙∙∙const name; }

// bad function bar() { ∙const name; }

// good function baz() { ∙∙const name; }

  1. - 在大括号前空一格。
  2. ```javascript
  3. // bad
  4. function test(){
  5. console.log('test');
  6. }
  7. // good
  8. function test() {
  9. console.log('test');
  10. }
  11. // bad
  12. dog.set('attr',{
  13. age: '1 year',
  14. breed: 'Bernese Mountain Dog',
  15. });
  16. // good
  17. dog.set('attr', {
  18. age: '1 year',
  19. breed: 'Bernese Mountain Dog',
  20. });
  • 在控制语句(if, while 等)的圆括号前空一格。在函数调用和定义时,参数列表和函数名之间不空格。 ```javascript // bad if(isJedi) { fight (); }

// good if (isJedi) { fight(); }

// bad function fight () { console.log (‘Swooosh!’); }

// good function fight() { console.log(‘Swooosh!’); }

  1. - 用空格来隔开运算符。
  2. ```javascript
  3. // bad
  4. const x=y+5;
  5. // good
  6. const x = y + 5;
  • 当出现长的方法链式调用时(>2个)用缩进。用点开头强调该行是一个方法调用,而不是一个新的语句。 ```javascript // 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);

  1. - 在一个代码块后下一条语句前空一行。
  2. ```javascript
  3. // bad
  4. if (foo) {
  5. return bar;
  6. }
  7. return baz;
  8. // good
  9. if (foo) {
  10. return bar;
  11. }
  12. return baz;
  13. // bad
  14. const obj = {
  15. foo() {
  16. },
  17. bar() {
  18. },
  19. };
  20. return obj;
  21. // good
  22. const obj = {
  23. foo() {
  24. },
  25. bar() {
  26. },
  27. };
  28. return obj;
  29. // bad
  30. const arr = [
  31. function foo() {
  32. },
  33. function bar() {
  34. },
  35. ];
  36. return arr;
  37. // good
  38. const arr = [
  39. function foo() {
  40. },
  41. function bar() {
  42. },
  43. ];
  44. return arr;
  • 不要用空白行填充块。 ```javascript // bad function bar() {

    console.log(foo);

}

// also bad if (baz) {

console.log(qux); } else { console.log(foo);

}

// good function bar() { console.log(foo); }

// good if (baz) { console.log(qux); } else { console.log(foo); }

  1. - 不要在代码之间使用多个空白行填充。
  2. ```javascript
  3. // bad
  4. class Person {
  5. constructor(fullName, email, birthday) {
  6. this.fullName = fullName;
  7. this.email = email;
  8. this.setAge(birthday);
  9. }
  10. setAge(birthday) {
  11. const today = new Date();
  12. const age = this.getAge(today, birthday);
  13. this.age = age;
  14. }
  15. getAge(today, birthday) {
  16. // ..
  17. }
  18. }
  19. // good
  20. class Person {
  21. constructor(fullName, email, birthday) {
  22. this.fullName = fullName;
  23. this.email = email;
  24. this.setAge(birthday);
  25. }
  26. setAge(birthday) {
  27. const today = new Date();
  28. const age = getAge(today, birthday);
  29. this.age = age;
  30. }
  31. getAge(today, birthday) {
  32. // ..
  33. }
  34. }
  • 圆括号里不要加空格。 ```javascript // bad function bar( foo ) { return foo; }

// good function bar(foo) { return foo; }

// bad if ( foo ) { console.log(foo); }

// good if (foo) { console.log(foo); }

  1. - 方括号里不要加空格。
  2. ```javascript
  3. // bad
  4. const foo = [ 1, 2, 3 ];
  5. console.log(foo[ 0 ]);
  6. // good,逗号分隔符后还是要空格的。
  7. const foo = [1, 2, 3];
  8. console.log(foo[0]);
  • 花括号里加空格 。 ```javascript // bad const foo = {clark: ‘kent’};

// good const foo = { clark: ‘kent’ };

  1. - 避免一行代码超过100个字符(包含空格)。注意:对于 [上面](#strings--line-length),长字符串不受此规则限制,不应换行。
  2. > 为什么?这样确保可读性和可维护性。
  3. ```javascript
  4. // bad
  5. const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
  6. // bad
  7. $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
  8. // good
  9. const foo = jsonData
  10. && jsonData.foo
  11. && jsonData.foo.bar
  12. && jsonData.foo.bar.baz
  13. && jsonData.foo.bar.baz.quux
  14. && jsonData.foo.bar.baz.quux.xyzzy;
  15. // good
  16. $.ajax({
  17. method: 'POST',
  18. url: 'https://airbnb.com/',
  19. data: { name: 'John' },
  20. })
  21. .done(() => console.log('Congratulations!'))
  22. .fail(() => console.log('You have failed this city.'));
  • 作为语句的花括号内也要加空格 —— { 后和 } 前都需要空格。 ```javascript // bad function foo() {return true;} if (foo) { bar = 0;}

// good function foo() { return true; } if (foo) { bar = 0; }

  1. - `,` 前不要空格, `,` 后需要空格。
  2. ```javascript
  3. // bad
  4. var foo = 1,bar = 2;
  5. var arr = [1 , 2];
  6. // good
  7. var foo = 1, bar = 2;
  8. var arr = [1, 2];
  • 调用函数时,函数名和小括号之间不要空格。 ```javascript // bad func ();

func ();

// good func();

  1. - 在对象的字面量属性中, `key` `value` 之间要有空格。
  2. ```javascript
  3. // bad
  4. var obj = { "foo" : 42 };
  5. var obj2 = { "foo":42 };
  6. // good
  7. var obj = { "foo": 42 };
  • 避免出现多个空行。 在文件末尾只允许空一行。避免在文件开始处出现空行。 ```javascript // bad - multiple empty lines var x = 1;

var y = 2;

// bad - 2+ newlines at end of file var x = 1; var y = 2;

// bad - 1+ newline(s) at beginning of file

var x = 1; var y = 2;

// good var x = 1; var y = 2;

  1. [
  2. ](#%E7%9B%AE%E5%BD%95)
  3. <a name="c2d5517a"></a>
  4. ## 逗号
  5. - 不要前置逗号。
  6. ```javascript
  7. // bad
  8. const story = [
  9. once
  10. , upon
  11. , aTime
  12. ];
  13. // good
  14. const story = [
  15. once,
  16. upon,
  17. aTime,
  18. ];
  19. // bad
  20. const hero = {
  21. firstName: 'Ada'
  22. , lastName: 'Lovelace'
  23. , birthYear: 1815
  24. , superPower: 'computers'
  25. };
  26. // good
  27. const hero = {
  28. firstName: 'Ada',
  29. lastName: 'Lovelace',
  30. birthYear: 1815,
  31. superPower: 'computers',
  32. };

[

](#%E7%9B%AE%E5%BD%95)

分号

  • 要分号

    为什么?当 JavaScript 遇到没有分号结尾的一行,它会执行 自动插入分号 这一规则来决定行末是否加分号。如果 JavaScript 在你的断行里错误的插入了分号,就会出现一些古怪的行为。当新的功能加到JavaScript 里后, 这些规则会变得更复杂难懂。清晰的结束语句,并通过配置代码检查去检查没有带分号的地方可以帮助你防止这种错误。

  1. // bad - 抛出异常
  2. const luke = {}
  3. const leia = {}
  4. [luke, leia].forEach((jedi) => jedi.father = 'vader')
  5. // bad - 抛出异常
  6. const reaction = "No! That’s impossible!"
  7. (async function meanwhileOnTheFalcon() {
  8. // 处理 `leia`, `lando`, `chewie`, `r2`, `c3p0`
  9. // ...
  10. }())
  11. // bad - 将返回 `undefined` 而不是下一行的值。由于 ASI,当 `return`单独出现在一行时,这种情况会一直出现。
  12. function foo() {
  13. return
  14. 'search your feelings, you know it to be foo'
  15. }
  16. // good
  17. const luke = {};
  18. const leia = {};
  19. [luke, leia].forEach((jedi) => {
  20. jedi.father = 'vader';
  21. });
  22. // good
  23. const reaction = "No! That’s impossible!";
  24. (async function meanwhileOnTheFalcon() {
  25. // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
  26. // ...
  27. }());
  28. // good
  29. function foo() {
  30. return 'search your feelings, you know it to be foo';
  31. }

[

](#%E7%9B%AE%E5%BD%95)

类型转换与强制转换

  • 字符串: ```javascript // => this.reviewScore = 9;

// bad const totalScore = new String(this.reviewScore); // typeof totalScore is “object” not “string”

// bad const totalScore = this.reviewScore + ‘’; // 将会执行 this.reviewScore.valueOf()

// bad const totalScore = this.reviewScore.toString(); // 不保证返回 string

// good const totalScore = String(this.reviewScore);

  1. - 数字: `Number` 做类型转换,`parseInt` 转换 `string` 应总是带上基数。
  2. > 为什么?函数 `parseInt` 会根据指定的基数将字符串转换为数字。字符串开头的空白字符将会被忽略,如果参数基数(第二个参数)为 `undefined` 或者 `0` ,除非字符串开头为 `0x` `0X`(十六进制),会默认假设为 `10`。这个差异来自 ECMAScript 3,它不鼓励(但是允许)解释八进制。在 2013 年之前,一些实现不兼容这种行为。因为我们需要支持旧浏览器,所以应当始终指定进制。
  3. ```javascript
  4. const inputValue = '4';
  5. // bad
  6. const val = new Number(inputValue);
  7. // bad
  8. const val = +inputValue;
  9. // bad
  10. const val = inputValue >> 0;
  11. // bad
  12. const val = parseInt(inputValue);
  13. // good
  14. const val = Number(inputValue);
  15. // good
  16. const val = parseInt(inputValue, 10);

[

](#%E7%9B%AE%E5%BD%95)

Get-Set 访问器

  • 如果属性/方法是 boolean, 用 isVal()hasVal()
    ```javascript // bad if (!dragon.age()) { return false; }

// good if (!dragon.hasAge()) { return false; }

  1. - `get()` `set()` 函数是可以的,但是要一起用。
  2. ```javascript
  3. class Jedi {
  4. constructor(options = {}) {
  5. const lightsaber = options.lightsaber || 'blue';
  6. this.set('lightsaber', lightsaber);
  7. }
  8. set(key, val) {
  9. this[key] = val;
  10. }
  11. get(key) {
  12. return this[key];
  13. }
  14. }