JavaScript 风格指南 Airbnb

68 阅读23分钟

本文翻译至开源 github.com/airbnb/java…

Types  类型

1.1 基元 :当你访问一个基元类型时,你直接处理它的值。

  • string
  • number
  • boolean
  • null
  • undefined
  • symbol
  • bigint
const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

符号和 BigInt 不能忠实地填充,因此在针对本机不支持它们的浏览器/环境时不应使用它们。

1.2 复杂:  当您访问复杂类型时,您需要处理对其值的引用。

  • object
  • array
  • function
const foo = [1, 2];
const bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

References  引用

2.1 对您的所有引用使用 const;避免使用 var。eslint: prefer-const no-const-assign

为什么?这可确保您无法重新分配引用,这可能会导致错误和难以理解的代码。

// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;

2.2 如果必须重新分配引用,请使用 let 而不是 var。ESLint: 无变异

为什么?let 是块作用域的,而不是像 var 那样的函数作用域。

// bad
var count = 1;
if (true) {
  count += 1;
}

// good, use the let.
let count = 1;
if (true) {
  count += 1;
}

2.3 请注意,let 和 const 都是块作用域的,而 var 是函数作用域的。

// const and let only exist in the blocks they are defined in.
{
  let a = 1;
  const b = 1;
  var c = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError
console.log(c); // Prints 1

在上面的代码中,你可以看到引用 a 和 b 会产生一个 ReferenceError,而 c 包含数字。这是因为 a 和 b 是块作用域,而 c 的作用域是包含函数。

Objects  对象

3.1 使用文字语法创建对象。eslint: no-new-object

// bad
const item = new Object();

// good
const item = {};

3.2 创建具有动态属性名称的对象时,请使用计算属性名称。

为什么?它们允许您在一个地方定义对象的所有属性。

function getKey(k) {
  return `a key named ${k}`;
}

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};

3.3 使用对象方法简写。eslint: object-shorthand

// bad
const atom = {
  value: 1,

  addValue: function (value) {
    return atom.value + value;
  },
};

// good
const atom = {
  value: 1,

  addValue(value) {
    return atom.value + value;
  },
};

3.4 使用属性值简写。eslint: object-shorthand

为什么?它更短且具有描述性。

const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  lukeSkywalker: lukeSkywalker,
};

// good
const obj = {
  lukeSkywalker,
};

3.5 在对象声明的开头对简写的属性进行分组。

为什么? 更容易判断哪些属性使用简写。

const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  lukeSkywalker,
  episodeThree: 3,
  mayTheFourth: 4,
  anakinSkywalker,
};

// good
const obj = {
  lukeSkywalker,
  anakinSkywalker,
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  episodeThree: 3,
  mayTheFourth: 4,
};

3.6 仅引用无效标识符的属性。eslint: quote-props

为什么? 一般来说,我们认为它在主观上更容易阅读。它改进了语法突出显示,并且也更容易被许多 JS 引擎优化。

// bad
const bad = {
  'foo': 3,
  'bar': 4,
  'data-blah': 5,
};

// good
const good = {
  foo: 3,
  bar: 4,
  'data-blah': 5,
};

3.7 不要直接调用 Object.prototype 方法,例如 hasOwnPropertypropertyIsEnumerable 和 isPrototypeOf。eslint: no-prototype-builtins

为什么? 这些方法可能被相关对象上的属性所掩盖 - 考虑 { hasOwnProperty: false } - 或者,该对象可能是 null 对象 (Object.create(null))。 在支持 ES2022 或具有 npmjs.com/object.haso… 等 polyfill 的现代浏览器中,Object.hasOwn 也可以用作 Object.prototype.hasOwnProperty.call 的替代品。

// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// better
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
console.log(has.call(object, key));

// best
console.log(Object.hasOwn(object, key)); // only supported in browsers that support ES2022

/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key));
/* or */
console.log(Object.hasOwn(object, key)); // https://www.npmjs.com/package/object.hasown

3.8 优先使用对象分布语法而不是 Object.assign 来浅拷贝对象。使用 object rest 参数语法获取省略某些属性的新对象。eslint: prefer-object-spread

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

Arrays  数组

4.1 使用文字语法创建数组。eslint: no-array-constructor

// bad
const items = new Array();

// good
const items = [];

4.2 使用 Array#push 而不是直接赋值将项目添加到数组中。

const someStack = [];

// bad
someStack[someStack.length] = 'abracadabra';

// good
someStack.push('abracadabra');

4.3 使用数组展开 ... 复制数组。

// bad
const len = items.length;
const itemsCopy = [];
let i;

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

// good
const itemsCopy = [...items];

4.4 要将可迭代对象转换为数组,请使用 spreads ... 而不是 Array.from

const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];

4.5 使用 Array.from 将类似数组的对象转换为数组。

const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };

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

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

4.6 使用 Array.from 而不是 spread ... 来映射可迭代对象,因为它避免了创建中间数组。

// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

4.7 在数组方法回调中使用 return 语句。如果函数体由返回一个没有副作用的表达式组成,则可以省略返回,遵循 8.2。eslint: array-callback-return

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => x + 1);

// bad - no returned value means `acc` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
  return flatten;
});

// bad
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  } else {
    return false;
  }
});

// good
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  }

  return false;
});

4.8 如果数组有多行,则在打开数组括号后和关闭数组括号之前使用换行符

// bad
const arr = [
  [0, 1], [2, 3], [4, 5],
];

const objectInArray = [{
  id: 1,
}, {
  id: 2,
}];

const numberInArray = [
  1, 2,
];

// good
const arr = [[0, 1], [2, 3], [4, 5]];

const objectInArray = [
  {
    id: 1,
  },
  {
    id: 2,
  },
];

const numberInArray = [
  1,
  2,
];

Destructuring  解构

5.1 在访问和使用对象的多个属性时使用对象解构。eslint: prefer-destructuring

为什么? 解构使您免于为这些属性创建临时引用,也无需重复访问对象。重复对象访问会创建更多重复的代码,需要更多阅读,并产生更多错误的机会。解构对象还提供了块中使用的对象结构的单个定义站点,而不是需要读取整个块来确定使用的内容。

// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;

  return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
  const { firstName, lastName } = user;
  return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}

5.2 使用数组解构。eslint: prefer-destructuring

const arr = [1, 2, 3, 4];

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

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

5.3 对多个返回值使用对象解构,而不是数组解构。

Why? You can add new properties over time or change the order of things without breaking call sites.
为什么? 您可以随着时间的推移添加新属性或更改事物的顺序,而不会中断呼叫站点。

// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

Strings  字符串

6.1 对字符串使用单引号 ''。eslint:  quotes

// bad
const name = "Capt. Janeway";

// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;

// good
const name = 'Capt. Janeway';

6.2 导致行超过 100 个字符的字符串不应使用字符串串联跨多行写入。

为什么? 使用断开的字符串很痛苦,并且使代码更难搜索。

// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';

// bad
const errorMessage = 'This is a super long error that was thrown because ' +
  'of Batman. When you stop to think about how Batman had anything to do ' +
  'with this, you would get nowhere fast.';

// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';

6.3 以编程方式构建字符串时,使用模板字符串而不是串联。eslint: prefer-templatetemplate-curly-spacing

为什么? 模板字符串为您提供可读、简洁的语法,并具有适当的换行符和字符串插值功能。

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
  return ['How are you, ', name, '?'].join();
}

// bad
function sayHi(name) {
  return `How are you, ${ name }?`;
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}

6.4 永远不要在字符串上使用 eval(); 它会打开太多漏洞。eslint: no-eval

6.5 不要在字符串中不必要地转义字符。eslint: no-useless-escape

为什么? 反斜杠会损害可读性,因此它们应该只在必要时出现。

// bad
const foo = ''this' \i\s "quoted"';

// good
const foo = ''this' is "quoted"';
const foo = `my name is '${name}'`;

Functions  功能

7.1 使用命名函数表达式而不是函数声明。eslint:func-style,func-names

为什么? 函数声明被提升,这意味着在文件中定义函数之前引用函数很容易——太容易了。这损害了可读性和可维护性。如果您发现函数的定义足够大或足够复杂,以至于它干扰了对文件其余部分的理解,那么也许是时候将其提取到它自己的模块中了!不要忘记显式命名表达式,无论该名称是否是从包含的变量中推断出来的(这在现代浏览器中或使用 Babel 等编译器时经常出现)。这消除了对 Error 调用堆栈所做的任何假设。( 讨论 

// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};

7.2 将立即调用的函数表达式包裹在括号中。eslint:wrap-iife

为什么? 立即调用的函数表达式是一个单元 - 将它和它的调用括号包装在括号中,干净地表达了这一点。请注意,在一个到处都是模块的世界中,您几乎不需要 IIFE。

// immediately-invoked function expression (IIFE)
(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());

7.3 永远不要在非函数块中声明函数(ifwhile 等)。改为将函数赋值给变量。浏览器会允许你这样做,但它们的解释都不同,这是个坏消息。eslint: no-loop-func

7.4 注意: ECMA-262 将定义为语句列表。函数声明不是语句。

// bad
if (currentUser) {
  function test() {
    console.log('Nope.');
  }
}

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

7.5 永远不要命名参数参数 。这将优先于分配给每个函数作用域的参数对象。

// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}

7.6 永远不要使用参数 ,选择使用 rest 语法 ...。eslint: prefer-rest-params

为什么?... 明确了您要拉取的参数。另外,其余参数是一个真正的数组,而不仅仅是类似数组的参数

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}

7.7 使用默认参数语法而不是更改函数参数。

// really bad
function handleThings(opts) {
  // No! We shouldn’t mutate function arguments.
  // Double bad: if opts is falsy it'll be set to an object which may
  // be what you want but it can introduce subtle bugs.
  opts = opts || {};
  // ...
}

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

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

7.8 避免默认参数的副作用。

为什么?它们令人困惑。

let b = 1;
// bad
function count(a = b++) {
  console.log(a);
}
count();  // 1
count();  // 2
count(3); // 3
count();  // 3

7.9 始终将默认参数放在最后。eslint: default-param-last

// bad
function handleThings(opts = {}, name) {
  // ...
}

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

7.10 切勿使用 Function 构造函数创建新函数。ESLint: no-new-func

为什么?以这种方式创建函数会像 eval() 一样计算字符串,这会打开漏洞。

// bad
const add = new Function('a', 'b', 'return a + b');

// still bad
const subtract = Function('a', 'b', 'return a - b');

7.11 函数签名中的间距。eslint: space-before-function-paren space-before-blocks

为什么?一致性很好,添加或删除名称时不必添加或删除空格。

// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function () {};
const y = function a() {};

7.12 切勿改变参数。eslint: no-param-reassign

为什么?作为参数传入的对象可能会导致原始调用方出现不必要的变量副作用。

// bad
function f1(obj) {
  obj.key = 1;
}

// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

7.13 切勿重新分配参数。eslint: no-param-reassign

为什么?重新分配参数可能会导致意外行为,尤其是在访问参数对象时。它还可能导致优化问题,尤其是在 V8 中。

// bad
function f1(a) {
  a = 1;
  // ...
}

function f2(a) {
  if (!a) { a = 1; }
  // ...
}

// good
function f3(a) {
  const b = a || 1;
  // ...
}

function f4(a = 1) {
  // ...
}

7.14 更喜欢使用 spread 语法 ... 来调用可变参数函数。eslint: prefer-spread

为什么?它更干净,您不需要提供上下文,并且您无法轻松地使用 apply 编写新的内容

// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);

// good
const x = [1, 2, 3, 4, 5];
console.log(...x);

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));

// good
new Date(...[2016, 8, 5]);

7.15 具有多行签名或调用的函数应像本指南中的所有其他多行列表一样缩进:每个项目单独在一行上,最后一项上有一个尾随逗号。eslint: function-paren-newline

// bad
function foo(bar,
              baz,
              quux) {
  // ...
}

// good
function foo(
  bar,
  baz,
  quux,
) {
  // ...
}

// bad
console.log(foo,
  bar,
  baz);

// good
console.log(
  foo,
  bar,
  baz,
);

Arrow Functions  箭头函数

8.1 当必须使用匿名函数时(例如传递内联回调时),请使用箭头函数表示法。eslint: prefer-arrow-callback arrow-spacing

为什么?它创建了一个在上下文中执行的函数版本,这通常是您想要的,并且是一种更简洁的语法。

为什么不呢?如果您有一个相当复杂的函数,您可以将该逻辑移出到它自己的命名函数表达式中。

// bad
[1, 2, 3].map(function (x) {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

8.2 如果函数体由返回没有副作用的表达式的单个语句组成,则省略大括号并使用隐式返回。否则,请保留大括号并使用 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 + 1}.`);

// good
[1, 2, 3].map((number) => {
  const nextNumber = number + 1;
  return `A string containing the ${nextNumber}.`;
});

// good
[1, 2, 3].map((number, index) => ({
  [index]: number,
}));

// No implicit return with side effects
function foo(callback) {
  const val = callback();
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false;

// bad
foo(() => bool = true);

// good
foo(() => {
  bool = true;
});

8.3 如果表达式跨越多行,请将其括在括号中以提高可读性。

为什么?它清楚地显示了函数的开始和结束位置。

// bad
['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
);

// good
['get', 'post', 'put'].map((httpMethod) => (
  Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
));

8.4 为了清晰和一致,始终在参数周围包含括号。eslint: arrow-parens

为什么?在添加或删除参数时最大程度地减少差异流失。

// bad
[1, 2, 3].map(x => x * x);

// good
[1, 2, 3].map((x) => x * x);

// bad
[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!`
));

// good
[1, 2, 3].map((number) => (
  `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
));

// bad
[1, 2, 3].map(x => {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

8.5 避免将箭头函数语法 (=>) 与比较运算符 (<=, >=) 混淆。eslint: no-confusing-arrow

// bad
const itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize;

// bad
const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;

// good
const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);

// good
const itemHeight = (item) => {
  const { height, largeSize, smallSize } = item;
  return height <= 256 ? largeSize : smallSize;
};

8.6 强制使用隐式返回的箭头函数体的位置。eslint: implicit-arrow-linebreak

// bad
(foo) =>
  bar;

(foo) =>
  (bar);

// good
(foo) => bar;
(foo) => (bar);
(foo) => (
    bar
)

Classes & Constructors  类和构造函数

9.1 始终使用。避免直接作原型

为什么?语法更简洁,更容易推理。

// bad
function Queue(contents = []) {
  this.queue = [...contents];
}
Queue.prototype.pop = function () {
  const value = this.queue[0];
  this.queue.splice(0, 1);
  return value;
};

// good
class Queue {
  constructor(contents = []) {
    this.queue = [...contents];
  }
  pop() {
    const value = this.queue[0];
    this.queue.splice(0, 1);
    return value;
  }
}

9.2 使用扩展进行继承。

为什么?这是一种在不破坏 instanceof 的情况下继承原型功能的内置方法。

// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
  Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
  return this.queue[0];
};

// good
class PeekableQueue extends Queue {
  peek() {
    return this.queue[0];
  }
}

9.3 方法可以返回this以帮助方法链接。

// bad
Jedi.prototype.jump = function () {
  this.jumping = true;
  return true;
};

Jedi.prototype.setHeight = function (height) {
  this.height = height;
};

const luke = new Jedi();
luke.jump(); // => true
luke.setHeight(20); // => undefined

// good
class Jedi {
  jump() {
    this.jumping = true;
    return this;
  }

  setHeight(height) {
    this.height = height;
    return this;
  }
}

const luke = new Jedi();

luke.jump()
  .setHeight(20);

9.4 编写自定义的 toString() 方法是可以的,只要确保它成功工作并且不会产生副作用即可。

class Jedi {
  constructor(options = {}) {
    this.name = options.name || 'no name';
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}

9.5 如果未指定,则类具有默认构造函数。不需要空的构造函数或仅委托给父类的构造函数。eslint: no-useless-constructor

// bad
class Jedi {
  constructor() {}

  getName() {
    return this.name;
  }
}

// bad
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
  }
}

// good
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
    this.name = 'Rey';
  }
}

9.6 避免重复的类成员。eslint: no-dupe-class-members

为什么?重复的类成员声明会默默地更喜欢最后一个 - 几乎可以肯定,重复是一个错误。

// bad
class Foo {
  bar() { return 1; }
  bar() { return 2; }
}

// good
class Foo {
  bar() { return 1; }
}

// good
class Foo {
  bar() { return 2; }
}

9.7 类方法应该使用它或变成静态方法,除非外部库或框架需要使用特定的非静态方法。作为实例方法,应指示它根据接收器的属性以不同的方式行事。eslint: class-methods-use-this

// bad
class Foo {
  bar() {
    console.log('bar');
  }
}

// good - this is used
class Foo {
  bar() {
    console.log(this.bar);
  }
}

// good - constructor is exempt
class Foo {
  constructor() {
    // ...
  }
}

// good - static methods aren't expected to use this
class Foo {
  static bar() {
    console.log('bar');
  }
}

Modules  模块

10.1 始终在非标准模块系统上使用模块(import/export)。您始终可以转译到您喜欢的模块系统。

为什么?模块是未来,让我们现在就开始使用未来吧。

// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;

// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;

// best
import { es6 } from './AirbnbStyleGuide';
export default es6;

10.2 不要使用通配符导入。

为什么?这可确保您有一个默认导出。

// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';

10.3 并且不要直接从进口出口。

为什么?虽然一句简洁,但有一个清晰的导入方式和一种清晰的导出方式可以使事情保持一致。

// bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';

// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;

10.4 仅从一个地方的路径导入。eslint: no-duplicate-imports

为什么?从同一路径导入多行会使代码更难维护。

// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';

// good
import foo, { named1, named2 } from 'foo';

// good
import foo, {
  named1,
  named2,
} from 'foo';

10.5 不要导出可变绑定。eslint: no-mutable-exports

为什么?通常应避免突变,尤其是在导出可变绑定时。虽然某些特殊情况下可能需要此技术,但通常,只应导出常量引用。

// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };

10.6 在具有单个导出的模块中,首选默认导出而不是命名导出。eslint:prefer-default-export

为什么?鼓励更多只导出一个东西的文件,这对可读性和可维护性更好。

// bad
export function foo() {}

// good
export default function foo() {}

10.7 将所有 import 放在非 import 语句之上。eslint: import/first

为什么?由于导入是提升的,因此将它们全部放在顶部可以防止意外行为。

// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();

10.8 多行导入应该缩进,就像多行数组和对象文字一样。eslint: object-curly-newline

为什么?大括号遵循与样式指南中所有其他大括号块相同的缩进规则,尾随逗号也是如此。

// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';

10.9 不允许在模块导入语句中使用 Webpack 加载器语法。eslint: import/no-webpack-loader-syntax

为什么?由于在导入中使用 Webpack 语法将代码耦合到模块打包器。首选在 webpack.config.js 中使用加载器语法。

// bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';

// good
import fooSass from 'foo.scss';
import barCss from 'bar.css';

10.10 不包含 JavaScript 文件扩展名 eslint: import/extensions

为什么?包含扩展会抑制重构,并且不恰当地对每个使用者中导入的模块的实现细节进行硬编码。

// bad
import foo from './foo.js';
import bar from './bar.jsx';
import baz from './baz/index.jsx';

// good
import foo from './foo';
import bar from './bar';
import baz from './baz';

Iterators and Generators  迭代器和生成器

11.1 不要使用迭代器。更喜欢 JavaScript 的高阶函数,而不是像 for-in 或 for-of 这样的循环。eslint: no-iteratorno-restricted-syntax

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

使用 map() / every() / filter() / find() / findIndex``() / reduce() / some() / ...迭代数组,以及 Object.keys() / Object.values() / Object.entries``() 生成数组,以便您可以迭代对象。

const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach((num) => {
  sum += num;
});
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
  increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
  increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map((num) => num + 1);

11.2 暂时不要使用生成器。

为什么?它们不能很好地转换为 ES5。

11.3 如果您必须使用生成器,或者您无视我们的建议 ,请确保它们的功能签名间隔正确。eslint: generator-star-spacing

为什么?function 和 * 是同一个概念关键字的一部分 - * 不是 function 的修饰符,function* 是一个唯一的结构,与 function 不同。

// bad
function * foo() {
  // ...
}

// bad
const bar = function * () {
  // ...
};

// bad
const baz = function *() {
  // ...
};

// bad
const quux = function*() {
  // ...
};

// bad
function*foo() {
  // ...
}

// bad
function *foo() {
  // ...
}

// very bad
function
*
foo() {
  // ...
}

// very bad
const wat = function
*
() {
  // ...
};

// good
function* foo() {
  // ...
}

// good
const foo = function* () {
  // ...
};

Properties  属性

12.1 访问属性时使用点表示法。eslint: dot-notation

const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;

12.2 使用变量访问属性时使用括号表示法 []。

const luke = {
  jedi: true,
  age: 28,
};

function getProp(prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');

12.3 计算幂时使用幂运算符 **。eslint: prefer-exponentiation-operator 

// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;

Variables  变量

13.1 始终使用 const 或 let 来声明变量。不这样做将导致全局变量。我们希望避免污染全局命名空间。星球队长警告过我们这一点。eslint: no-undef prefer-const

// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();

13.2 每个变量或赋值使用一个 const 或 let 声明。eslint: one-var

为什么?以这种方式添加新的变量声明更容易,而且您永远不必担心换掉 ;for a 或引入仅标点符号的差异。还可以使用调试器单步执行每个声明,而不是一次跳过所有声明。

// bad
const items = getItems(),
    goSportsTeam = true,
    dragonball = 'z';

// bad
// (compare to above, and try to spot the mistake)
const items = getItems(),
    goSportsTeam = true;
    dragonball = 'z';

// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';

13.3 对所有 consts 分组,然后对所有 lets 分组。

为什么?当以后您可能需要根据先前分配的变量之一分配变量时,这很有帮助。

// bad
let i, len, dragonball,
    items = getItems(),
    goSportsTeam = true;

// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;

// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;

13.4 将变量分配到您需要的地方,但将它们放在合理的地方。

为什么?let 和 const 是块范围的,而不是函数范围的。

// bad - unnecessary function call
function checkName(hasName) {
  const name = getName();

  if (hasName === 'test') {
    return false;
  }

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

// good
function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }

  const name = getName();

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

13.5 不要链式变量声明+赋值。ESLint: no-multi-assign

为什么?链接变量赋值会创建隐式全局变量。

// bad
(function example() {
  // JavaScript interprets this as
  // let a = ( b = ( c = 1 ) );
  // The let keyword only applies to variable a; variables b and c become
  // global variables.
  let a = b = c = 1;
}());

console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
  let a = 1;
  let b = a;
  let c = a;
}());

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError

// the same applies for `const`

13.6 避免使用一元递增和递减(++--)。ESLINT  no-plusplus

为什么?根据 eslint 文档,一元递增和递减语句受自动分号插入的影响,并且可能会导致应用程序中值递增或递减的静默错误。使用 num += 1 而不是 num++ 或 num ++ 等语句来改变您的值也更具表现力。不允许一元递增和递减语句还可以防止您无意中预递增/预递减值,这也可能导致程序中出现意外行为。

// bad

const array = [1, 2, 3];
let num = 1;
num++;
--num;

let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) {
  let value = array[i];
  sum += value;
  if (value) {
    truthyCount++;
  }
}

// good

const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;

const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;

13.7 避免在赋值中 = 之前或之后换行。如果您的赋值违反了 max-len,请用括号括起来。eslint  operator-linebreak

为什么?= 周围的换行符可以混淆赋值的值。

// bad
const foo =
  superLongLongLongLongLongLongLongLongFunctionName();

// bad
const foo
  = 'superLongLongLongLongLongLongLongLongString';

// good
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

// good
const foo = 'superLongLongLongLongLongLongLongLongString';

13.8 不允许未使用的变量。eslint: no-unused-vars

为什么?声明但未在代码中任何地方使用的变量很可能是由于重构不完整而导致的错误。此类变量会占用代码中的空间,并可能导致读者混淆。

// bad

const some_unused_var = 42;

// Write-only variables are not considered as used.
let y = 10;
y = 5;

// A read for a modification of itself is not considered as used.
let z = 0;
z = z + 1;

// Unused function arguments.
function getX(x, y) {
    return x;
}

// good

function getXPlusY(x, y) {
  return x + y;
}

const x = 1;
const y = a + 2;

alert(getXPlusY(x, y));

// 'type' is ignored even if unused because it has a rest property sibling.
// This is a form of extracting an object that omits the specified keys.
const { type, ...coords } = data;
// 'coords' is now the 'data' object without its 'type' property.

Hoisting  提升

14.1var 声明被提升到其最近的封闭函数范围的顶部,它们的赋值不会。const 和 let 声明有一个称为时间盲区 (TDZ) 的新概念。重要的是要知道为什么 typeof 不再安全 

// we know this wouldn’t work (assuming there
// is no notDefined global variable)
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting. Note: the assignment
// value of `true` is not hoisted.
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// the interpreter is hoisting the variable
// declaration to the top of the scope,
// which means our example could be rewritten as:
function example() {
  let declaredButNotAssigned;
  console.log(declaredButNotAssigned); // => undefined
  declaredButNotAssigned = true;
}

// using const and let
function example() {
  console.log(declaredButNotAssigned); // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned); // => throws a 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');
  };
}

// the same is true when the function name
// is the same as the variable name.
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');
  }
}

14.5 变量、类和函数在使用之前应先定义它们。eslint: no-use-before-define

为什么?当变量、类或函数在使用后声明时,可能会损害可读性,因为读者不会知道引用的事物是什么。对于读者来说,在遇到事物的使用之前,首先遇到事物的源代码(无论是从另一个模块导入的,还是在文件中定义的)要清楚得多。

// bad

// Variable a is being used before it is being defined.
console.log(a); // this will be undefined, since while the declaration is hoisted, the initialization is not
var a = 10;

// Function fun is being called before being defined.
fun();
function fun() {}

// Class A is being used before being defined.
new A(); // ReferenceError: Cannot access 'A' before initialization
class A {
}

// `let` and `const` are hoisted, but they don't have a default initialization.
// The variables 'a' and 'b' are in a Temporal Dead Zone where JavaScript
// knows they exist (declaration is hoisted) but they are not accessible
// (as they are not yet initialized).

console.log(a); // ReferenceError: Cannot access 'a' before initialization
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let a = 10;
const b = 5;


// good

var a = 10;
console.log(a); // 10

function fun() {}
fun();

class A {
}
new A();

let a = 10;
const b = 5;
console.log(a); // 10
console.log(b); // 5

有关更多信息,请参阅 Ben Cherry 的 JavaScript Scoping & Hoisting

Comparison Operators & Equality 比较运算符和相等

15.1 在 == 和 != 上使用 === 和 !=。ESLint:EQEqEQ 15.2 条件语句(如 if 语句)使用 ToBoolean 抽象方法的强制来评估其表达式,并始终遵循以下简单规则:

  • Objects evaluate to true
  • Undefined evaluates to false
  • Null evaluates to false
  • Booleans evaluate to the value of the boolean
  • Numbers evaluate to false if  +0, -0, or NaN, otherwise true
  • Strings evaluate to false if an empty string '', otherwise true
if ([0] && []) {
  // true
  // an array (even an empty one) is an object, objects will evaluate to true
}

15.3 对布尔值使用快捷键,但对字符串和数字使用显式比较。

// bad
if (isValid === true) {
  // ...
}

// good
if (isValid) {
  // ...
}

// bad
if (name) {
  // ...
}

// good
if (name !== '') {
  // ...
}

// bad
if (collection.length) {
  // ...
}

// good
if (collection.length > 0) {
  // ...
}

15.4 有关更多信息,请参阅 Angus Croll 的 Truth, Equality, and JavaScript

15.5 使用大括号在包含词法声明(例如 letconstfunction 和 class)的 case 和 default 子句中创建块。eslint: no-case-declarations

为什么?词法声明在整个 switch 块中可见,但仅在分配时初始化,这仅在达到其大小写时发生。当多个大小写子句试图定义同一事物时,这会导致问题。

// bad
switch (foo) {
  case 1:
    let x = 1;
    break;
  case 2:
    const y = 2;
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}

// good
switch (foo) {
  case 1: {
    let x = 1;
    break;
  }
  case 2: {
    const y = 2;
    break;
  }
  case 3: {
    function f() {
      // ...
    }
    break;
  }
  case 4:
    bar();
    break;
  default: {
    class C {}
  }
}

15.6 三元不应嵌套,通常是单行表达式。eslint: no-nested-ternary文件

// bad
const foo = maybe1 > maybe2
  ? "bar"
  : value1 > value2 ? "baz" : null;

// split into 2 separated ternary expressions
const maybeNull = value1 > value2 ? 'baz' : null;

// better
const foo = maybe1 > maybe2
  ? 'bar'
  : maybeNull;

// best
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;

15.7 避免不必要的三元语句。eslint: no-unneeded-ternary

// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
const quux = a != null ? a : b;

// good
const foo = a || b;
const bar = !!c;
const baz = !c;
const quux = a ?? b;

15.8 混合运算符时,请将它们括在括号中。唯一的例外是标准算术运算符:+、``- 和 **,因为它们的优先级被广泛理解。我们建议将 / 和 * 括在括号中,因为它们混合时优先级可能不明确。eslint: no-mixed-operators

为什么?这提高了可读性并阐明了开发人员的意图。

// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;

// bad
const bar = a ** b - 5 % d;

// bad
// one may be confused into thinking (a || b) && c
if (a || b && c) {
  return d;
}

// bad
const bar = a + b / c * d;

// good
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);

// good
const bar = a ** b - (5 % d);

// good
if (a || (b && c)) {
  return d;
}

// good
const bar = a + (b / c) * d;

15.9 ?? 是一个逻辑运算符,当其左侧作数为 null 或未定义时,它返回其右侧作数。否则,它返回左侧作数。

为什么?它通过将空/未定义值与其他虚假值区分开来提供精度,从而增强代码的清晰度和可预测性。

// bad
const value = 0 ?? 'default';
// returns 0, not 'default'

// bad
const value = '' ?? 'default';
// returns '', not 'default'

// good
const value = null ?? 'default';
// returns 'default'

// good
const user = {
  name: 'John',
  age: null
};
const age = user.age ?? 18;
// returns 18

Blocks  块

16.1 对所有多行块使用大括号。eslint: nonblock-statement-body-position

// bad
if (test)
  return false;

// good
if (test) return false;

// good
if (test) {
  return false;
}

// bad
function foo() { return false; }

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

16.2 如果您使用带有 if 和 else 的多行块,请将 else 与 if 块的右大括号放在同一行上。eslint: brace-style

// bad
if (test) {
  thing1();
  thing2();
}
else {
  thing3();
}

// good
if (test) {
  thing1();
  thing2();
} else {
  thing3();
}

16.3 如果 if 块始终执行 return 语句,则不需要后续的 else 块。在包含return的 if 块之后的 else if 块中的返回可以分成多个 if 块。eslint: no-else-return

// bad
function foo() {
  if (x) {
    return x;
  } else {
    return y;
  }
}

// bad
function cats() {
  if (x) {
    return x;
  } else if (y) {
    return y;
  }
}

// bad
function dogs() {
  if (x) {
    return x;
  } else {
    if (y) {
      return y;
    }
  }
}

// good
function foo() {
  if (x) {
    return x;
  }

  return y;
}

// good
function cats() {
  if (x) {
    return x;
  }

  if (y) {
    return y;
  }
}

// good
function dogs(x) {
  if (x) {
    if (z) {
      return y;
    }
  } else {
    return z;
  }
}

Control Statements  控制语句

17.1 如果您的控制语句(ifwhile 等)变得太长或超过最大行长,则可以将每个(分组的)条件放入新行中。逻辑运算符应从该行开始。

为什么?在行的开头要求运算符使运算符保持对齐,并遵循类似于方法链接的模式。这也通过使视觉上更容易遵循复杂的逻辑来提高可读性。

// bad
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
  thing1();
}

// bad
if (foo === 123 &&
  bar === 'abc') {
  thing1();
}

// bad
if (foo === 123
  && bar === 'abc') {
  thing1();
}

// bad
if (
  foo === 123 &&
  bar === 'abc'
) {
  thing1();
}

// good
if (
  foo === 123
  && bar === 'abc'
) {
  thing1();
}

// good
if (
  (foo === 123 || bar === 'abc')
  && doesItLookGoodWhenItBecomesThatLong()
  && isThisReallyHappening()
) {
  thing1();
}

// good
if (foo === 123 && bar === 'abc') {
  thing1();
}

17.2 不要使用选择运算符代替控制语句。

// bad
!isRunning && startRunning();

// good
if (!isRunning) {
  startRunning();
}

Comments  注释

18.1 对多行注释使用 /** ... */

// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {

  // ...

  return element;
}

// good
/**
  * make() returns a new element
  * based on the passed-in tag name
  */
function make(tag) {

  // ...

  return element;
}

18.2 用于``单行注释。将单行注释放在注释主题上方的换行符上。在注释前放一个空行,除非它在块的第一行。

// bad
const active = true;  // is current tab

// good
// is current tab
const active = true;

// bad
function getType() {
  console.log('fetching type...');
  // set the default type to 'no type'
  const type = this.type || 'no type';

  return type;
}

// good
function getType() {
  console.log('fetching type...');

  // set the default type to 'no type'
  const type = this.type || 'no type';

  return type;
}

// also good
function getType() {
  // set the default type to 'no type'
  const type = this.type || 'no type';

  return type;
}

18.3 所有注释都以空格开头,以便于阅读。eslint: spaced-comment

// bad
//is current tab
const active = true;

// good
// is current tab
const active = true;

// bad
/**
  *make() returns a new element
  *based on the passed-in tag name
  */
function make(tag) {

  // ...

  return element;
}

// good
/**
  * make() returns a new element
  * based on the passed-in tag name
  */
function make(tag) {

  // ...

  return element;
}

18.4 在注释前加上 FIXME 或 TODO 有助于其他开发人员快速了解您是否指出了需要重新审视的问题,或者您是否正在为需要实现的问题提出解决方案。这些与常规评论不同,因为它们是可作的。这些行动是 FIXME: -- need to figure this out 或 TODO: -- 需要实现

18.5 使用  FIXME: 注释问题。

class Calculator extends Abacus {
  constructor() {
    super();

    // FIXME: shouldn’t use a global here
    total = 0;
  }
}

18.6 使用  TODO: 注释问题的解决方案。

class Calculator extends Abacus {
  constructor() {
    super();

    // TODO: total should be configurable by an options param
    this.total = 0;
  }
}

Whitespace  空白

19.1 使用设置为 2 个空格的软制表符(空格字符)。eslint: indent

// bad
function foo() {
∙∙∙∙let name;
}

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

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

19.2 在前大括号前放置 1 个空格。eslint: space-before-blocks

// bad
function test(){
  console.log('test');
}

// good
function test() {
  console.log('test');
}

// bad
dog.set('attr',{
  age: '1 year',
  breed: 'Bernese Mountain Dog',
});

// good
dog.set('attr', {
  age: '1 year',
  breed: 'Bernese Mountain Dog',
});

19.3 在控制语句(ifwhile 等)的左括号前放置 1 个空格。在函数调用和声明中,参数列表和函数名称之间不要放置空格。eslint: keyword-spacing

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

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

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

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

19.4 用空格来衬托运算符。eslint: space-infix-ops

// bad
const x=y+5;

// good
const x = y + 5;

19.5 使用单个换行符结束文件。ESLint:eol-last

// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;↵
↵
// good
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;↵

19.6 对于很长的链式调用(超过 2 个方法链)时使用缩进。使用前导点,强调该行是方法调用,而不是新语句。eslint:[ no-whitespace-before-property ]

// bad 超过2个方法的调用
$('#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);
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 在块之后和下一个语句之前留一个空行。

// bad
if (foo) {
  return bar;
}
return baz;

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

return baz;

// bad
const obj = {
  foo() {
  },
  bar() {
  },
};
return obj;

// good
const obj = {
  foo() {
  },

  bar() {
  },
};

return obj;

// bad
const arr = [
  function foo() {
  },
  function bar() {
  },
];
return arr;

// good
const arr = [
  function foo() {
  },

  function bar() {
  },
];

return arr;

19.8 在块里不要多余的空行。eslint: padded-blocks

// bad
function bar() {

  console.log(foo);

}

// bad
if (baz) {

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

}

// bad
class Foo {

  constructor(bar) {
    this.bar = bar;
  }
}

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

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

19.9 不要使用多个空行来填充代码。eslint: no-multiple-empty-lines

// bad
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) {
    // ..
  }
}

// good
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

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

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

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

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

19.11 不要在括号内添加空格。eslint: array-bracket-spacing

// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);

// good
const foo = [1, 2, 3];
console.log(foo[0]);

19.12 在大括号内添加空格。eslint: object-curly-spacing

// bad
const foo = {clark: 'kent'};

// good
const foo = { clark: 'kent' };

19.13 避免代码行数超过 100 个字符(包括空格)。注意:根据上述规定,长字符串不受此规则的约束,不应被分解。ESLint:MAX-LEN

为什么?这确保了可读性和可维护性。

// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;

// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));

// good
const foo = jsonData
  && jsonData.foo
  && jsonData.foo.bar
  && jsonData.foo.bar.baz
  && jsonData.foo.bar.baz.quux
  && jsonData.foo.bar.baz.quux.xyzzy;

// better
const foo = jsonData
  ?.foo
  ?.bar
  ?.baz
  ?.quux
  ?.xyzzy;

// good
$.ajax({
  method: 'POST',
  url: 'https://airbnb.com/',
  data: { name: 'John' },
})
  .done(() => console.log('Congratulations!'))
  .fail(() => console.log('You have failed this city.'));

19.14 要求打开区块标记和同一行上的下一个标记内的间距一致。此规则还在同一行上的关闭区块标记和前一个标记内强制执行一致的间距。eslint: block-spacing

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

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

19.15 逗号前不用空格,逗号后使用空格。eslint: comma-spacing

// bad 
const arr = [1 , 2];
var foo = 1,bar = 2;

// good 
const arr = [1, 2];
var foo = 1, bar = 2;

19.16 在计算的属性括号内强制使用间距。eslint: computed-property-spacing

// bad
obj[foo ]
obj[ 'foo']
const x = {[ b ]: a}
obj[foo[ bar ]]

// good
obj[foo]
obj['foo']
const x = { [b]: a }
obj[foo[bar]]

19.17 避免函数及其调用之间有空格。eslint:func-call-spacing

// bad
func ();

func
();

// good
func();

19.18 字面量对象属性的:后面要有空格 。eslint: key-spacing

// bad
const obj = { foo : 42 };
const obj2 = { foo:42 };

// good
const obj = { foo: 42 };

19.19 避免在行尾出现尾随空格。eslint: no-trailing-spaces

// bad - multiple empty lines
const x = 1;. // .代表空格

// good
const x = 1;

19.20 避免多行空行,文件末尾只允许一个换行符,避免文件开头换行符。eslint: no-multiple-empty-lines

// bad - multiple empty lines
const x = 1;


const y = 2;

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


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

const x = 1;
const y = 2;

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

Commas  逗号

20.1 数组/对象/属性的逗号都应该在后面 eslint: comma-style

// bad
const story = [
    once
  , upon
  , aTime
];

// good
const story = [
  once,
  upon,
  aTime,
];

// bad
const hero = {
    firstName: 'Ada'
  , lastName: 'Lovelace'
  , birthYear: 1815
  , superPower: 'computers'
};

// good
const hero = {
  firstName: 'Ada',
  lastName: 'Lovelace',
  birthYear: 1815,
  superPower: 'computers',
};

20.2 对象/数组/属性最后一个元素都多加一个逗号 eslint: comma-dangle

为什么?这导致了更干净的 git 差异。此外,像 Babel 这样的转译器将删除转译代码中额外的尾随逗号,这意味着您不必担心传统浏览器中的尾随逗号问题 

// bad - git diff without trailing comma
const hero = {
      firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - git diff with trailing comma
const hero = {
      firstName: 'Florence',
      lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};
// bad
const hero = {
  firstName: 'Dana',
  lastName: 'Scully'
};

const heroes = [
  'Batman',
  'Superman'
];

// good
const hero = {
  firstName: 'Dana',
  lastName: 'Scully',
};

const heroes = [
  'Batman',
  'Superman',
];

// bad
function createHero(
  firstName,
  lastName,
  inventorOf
) {
  // does nothing
}

// good
function createHero(
  firstName,
  lastName,
  inventorOf,
) {
  // does nothing
}

// good (note that a comma must not appear after a "rest" element)
function createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
) {
  // does nothing
}

// bad
createHero(
  firstName,
  lastName,
  inventorOf
);

// good
createHero(
  firstName,
  lastName,
  inventorOf,
);

// good (note that a comma must not appear after a "rest" element)
createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
);

Semicolons  分号

21.1 所有代码都加分号 eslint: semi

为什么?当 JavaScript 遇到没有分号的换行符时,它会使用一组称为自动分号插入的规则来确定是否应该将该换行符视为语句的结尾,并且(顾名思义)在代码中放置一个分号,如果它认为是这样。但是,ASI 包含一些古怪的行为,如果 JavaScript 误解了换行符,您的代码就会中断。随着新功能成为 JavaScript 的一部分,这些规则将变得更加复杂。显式终止语句并配置 linter 以捕获丢失的分号将有助于防止遇到问题。

// bad - raises exception
const luke = {}
const leia = {}
[luke, leia].forEach((jedi) => jedi.father = 'vader')

// bad - raises exception
const 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!
function foo() {
  return
    'search your feelings, you know it to be foo'
}

// good
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
  jedi.father = 'vader';
});

// good
const reaction = 'No! That’s impossible!';
(async function meanwhileOnTheFalcon() {
  // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
  // ...
}());

// good
function foo() {
  return 'search your feelings, you know it to be foo';
}

 阅读更多 

Type Casting & Coercion  类型铸造和强制

22.1 在语句开头执行类型强制。

22.2 字符串:eslint:no-new-wrappers

// => this.reviewScore = 9;

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

// bad
const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()

// bad
const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string

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

22.3 Numbers:使用 Number 进行类型转换,并始终使用 parseInt 和基数来解析字符串。ESLint: no-new-wrappers

为什么?parseInt 函数生成一个整数值,该整数值由根据指定的基数解释字符串参数的内容决定。字符串中的前导空格将被忽略。如果基数未定义或 0,则假定为 10,除非数字以字符对 0x 或 0X 开头,在这种情况下,假定基数为 16。这与 ECMAScript 3 不同,ECMAScript 3 只是不鼓励(但允许)八进制解释。截至 2013 年,许多实现尚未采用此行为。而且,由于必须支持较旧的浏览器,因此请始终指定基数。

const inputValue = '4';

// bad
const val = new Number(inputValue);

// bad
const val = +inputValue;

// bad
const val = inputValue >> 0;

// bad
const val = parseInt(inputValue);

// good
const val = Number(inputValue);

// good
const val = parseInt(inputValue, 10);

22.4 如果出于某种原因你正在做一些疯狂的事情,而 parseInt 是你的瓶颈,并且出于性能原因需要使用 Bitshift,请发表评论解释原因和你在做什么。

// good
/**
  * parseInt was the reason my code was slow.
  * Bitshifting the String to coerce it to a
  * Number made it a lot faster.
  */
const val = inputValue >> 0;

22.5 注意:  使用位移作时要小心。数字表示为 64 位值 ,但位移作始终返回 32 位整数( 源 )。对于大于 32 位的整数值,位移可能会导致意外行为。 讨论 。最大有符号 32 位 Int 为 2,147,483,647:

2147483647 >> 0; // => 2147483647
2147483648 >> 0; // => -2147483648
2147483649 >> 0; // => -2147483647

22.6 布尔值:eslint:no-new-wrappers

const age = 0;

// bad
const hasAge = new Boolean(age);

// good
const hasAge = Boolean(age);

// best
const hasAge = !!age;

Naming Conventions  命名约定

23.1 避免使用单字母名称。你的命名要具有描述性。eslint:id-length

// bad
function q() {
  // ...
}

// good
function query() {
  // ...
}

23.2 命名对象、函数和实例时使用驼峰命名法。eslint: camelcase

// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}

23.3 仅在命名构造函数或类时使用 PascalCase。ESLint: new-cap

// bad
function user(options) {
  this.name = options.name;
}

const bad = new user({
  name: 'nope',
});

// good
class User {
  constructor(options) {
    this.name = options.name;
  }
}

const good = new User({
  name: 'yup',
});

23.4 不要使用尾随或前导下划线。eslint: no-underscore-dangle

为什么?JavaScript 在属性或方法方面没有隐私的概念。尽管前导下划线是表示“私有”的常见约定,但实际上,这些属性是完全公共的,因此是公共 API 契约的一部分。这种约定可能会导致开发人员错误地认为更改不会被视为破坏性更改,或者不需要测试。tl;DR:如果你想让某件事成为“私密的”,它一定不能明显存在。

// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';

// good
this.firstName = 'Panda';

// good, in environments where WeakMaps are available
// see https://compat-table.github.io/compat-table/es6/#test-WeakMap
const firstNames = new WeakMap();
firstNames.set(this, 'Panda');

23.5 不要保存对this引用。使用箭头函数或 Function#bind代替。

// bad
function foo() {
  const self = this;
  return function () {
    console.log(self);
  };
}

// bad
function foo() {
  const that = this;
  return function () {
    console.log(that);
  };
}

// good
function foo() {
  return () => {
    console.log(this);
  };
}

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() {}

// in some other file
// bad
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

// bad
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

// good
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;

// 文件名 makeStyleGuide.js

23.8 导出构造函数/类/单例/函数库/裸对象时使用 PascalCase。

// 构造函数
export default function MyConstructor(name) {
  this.name = name;
}

// 类 
export default class UserProfile {} 

// 单例 
export default  Logger = { log(msg) { console.log(msg); } }; 

// 裸对象
export default const AppConfig = {
  API_URL: 'https://api.example.com',
  ENV: 'production',
}; 
 

23.9 首字母缩略词和首字母缩略词应始终全部大写或全部小写。

为什么?名称是为了可读性,而不是为了安抚计算机算法。

// bad
import SmsContainer from './containers/SmsContainer';

// bad
const HttpRequests = [
  // ...
];

// good
import SMSContainer from './containers/SMSContainer';

// good
const HTTPRequests = [
  // ...
];

// also good
const httpRequests = [
  // ...
];

// best
import TextMessageContainer from './containers/TextMessageContainer';

// best
const requests = [
  // ...
];

23.10 只有当常量被导出,您才可以选择将其大写。

为什么?这是一个额外的工具,可以在程序员不确定变量是否会发生变化的情况下提供帮助。UPPERCASE_VARIABLES 让程序员知道他们可以信任变量(及其属性)不会改变。

  • 那么所有的 const 变量呢?- 这是不必要的,因此不应将大写用于文件中的常量。但是,它应该用于导出的常量。
  • 导出的对象呢?- 在导出的顶层(例如 EXPORTED_OBJECT.key)大写,并保持所有嵌套属性不会更改。
// bad
const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file';

// bad
export const THING_TO_BE_CHANGED = 'should obviously not be uppercased';

// bad
export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables';

// ---

// allowed but does not supply semantic value
export const apiKey = 'SOMEKEY';

// better in most cases
export const API_KEY = 'SOMEKEY';

// ---

// bad - unnecessarily uppercases key while adding no semantic value
export const MAPPING = {
  KEY: 'value'
};

// good
export const MAPPING = {
  key: 'value',
};

Accessors  访问

24.1 不需要属性的访问器函数。

24.2 不要使用 JavaScript getter/setter,因为它们会导致意外的副作用,并且更难测试、维护和推理。相反,如果您确实创建了访问器函数,请使用 getVal() 和 setVal('hello')。

// bad
class Dragon {
  get age() {
    // ...
  }

  set age(value) {
    // ...
  }
}

// good
class Dragon {
  getAge() {
    // ...
  }

  setAge(value) {
    // ...
  }
}

24.3 如果属性/方法是布尔值 ,请使用 isVal() 或 hasVal()。

// bad
if (!dragon.age()) {
  return false;
}

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

24.4 创建 get() 和 set() 函数是可以的,但要保持一致。

class Jedi {
  constructor(options = {}) {
    const lightsaber = options.lightsaber || 'blue';
    this.set('lightsaber', lightsaber);
  }

  set(key, val) {
    this[key] = val;
  }

  get(key) {
    return this[key];
  }
}

Events  事件

25.1 将数据有效负载附加到事件(无论是 DOM 事件还是更专有的东西,如 Backbone 事件)时,传递对象文字(也称为“哈希”)而不是原始值。这允许后续参与者向事件有效负载添加更多数据,而无需查找和更新事件的每个处理程序。例如,而不是:

// bad
$(this).trigger('listingUpdated', listing.id);

// ...

$(this).on('listingUpdated', (e, listingID) => {
  // do something with listingID
});

prefer:  喜欢:

// good
$(this).trigger('listingUpdated', { listingID: listing.id });

// ...

$(this).on('listingUpdated', (e, data) => {
  // do something with data.listingID
});

jQuery  j 查询

26.1 在 jQuery 对象变量前加上 $.

// bad
const sidebar = $('.sidebar');

// good
const $sidebar = $('.sidebar');

// good
const $sidebarBtn = $('.sidebar-btn');

26.2 缓存 jQuery 查找。

// bad
function setSidebar() {
  $('.sidebar').hide();

  // ...

  $('.sidebar').css({
    'background-color': 'pink',
  });
}

// good
function setSidebar() {
  const $sidebar = $('.sidebar');
  $sidebar.hide();

  // ...

  $sidebar.css({
    'background-color': 'pink',
  });
}

26.3 对于 DOM 查询,使用级联 $('.sidebar ul') 或父 > 子 $('.sidebar > ul')。jsPerf 26.4 将 find 与作用域 jQuery 对象查询一起使用。

// bad
$('ul', '.sidebar').hide();

// bad
$('.sidebar').find('ul').hide();

// good
$('.sidebar ul').hide();

// good
$('.sidebar > ul').hide();

// good
$sidebar.find('ul').hide();

ECMAScript 5 Compatibility ECMAScript 5 兼容性

27.1 参考 Kangax 的 ES5 兼容性表 

ECMAScript 6+ (ES 2015+) Styles ECMAScript 6+ (ES 2015+) 样式

28.1 这是指向各种 ES6+ 功能的链接的集合。

  1. Arrow Functions  箭头函数
  2. Classes  类
  3. Object Shorthand  对象速记
  4. Object Concise  对象简明
  5. Object Computed Properties
    对象计算属性
  6. Template Strings  模板字符串
  7. Destructuring  解构
  8. Default Parameters  默认参数
  9. Rest  休息
  10. Array Spreads  数组价差
  11. Let and Const  let 和 const
  12. Exponentiation Operator  幂运算符
  13. Iterators and Generators
    迭代器和生成器
  14. Modules  模块

28.2 不要使用尚未达到第 3 阶段的 TC39 提案 

为什么? 它们尚未最终确定 ,可能会发生变化或完全撤销。我们想使用 JavaScript,而提案还不是 JavaScript。

Standard Library  标准库

标准库 包含功能损坏但由于遗留原因而保留的实用程序。

29.1 使用 Number.isNaN 而不是全局 isNaN。eslint: no-restricted-globals

为什么?全局 isNaN 将非数字强制转换为数字,对于强制为 NaN 的任何内容返回 true。如果需要此行为,请明确表示。

// bad
isNaN('1.2'); // false
isNaN('1.2.3'); // true

// good
Number.isNaN('1.2.3'); // false
Number.isNaN(Number('1.2.3')); // true

29.2 使用 Number.isFinite 而不是全局 isFinite。eslint: no-restricted-globals

为什么?全局 isFinite 将非数字强制为数字,对于强制为有限数的任何内容返回 true。如果需要此行为,请明确表示。

// bad
isFinite('2e3'); // true

// good
Number.isFinite('2e3'); // false
Number.isFinite(parseInt('2e3', 10)); // true