[译] Airbnb 的 JavaScript 样式指南

727 阅读3分钟

近日想为我司定一份代码规范,便翻译了 Airbnb 最新的规范。
省略了一些对我没帮助。还有代码格式这些没有进行翻译,建议直接使用 Prettier

引用

  • 对你所有的引用使用 const; 避免使用 var
// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;
  • 如果你必须重新分配引用,使用 let 代替 var

    为什么? let 是块级作用域而不是函数作用域 var

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

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

对象

  • 使用的字面语法创建对象
// bad
const item = new Object();

// good
const item = {};
  • 当创建对象时有动态属性时使用计算属性

    为什么? 使你定义一个对象的所有的属性都在同一个地方

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
};
  • 使用简写的对象方法
// bad
const atom = {
  value: 1,

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

// good
const atom = {
  value: 1,

  addValue(value) {
    return atom.value + value;
  }
};
  • 使用简写属性值
const lukeSkywalker = "Luke Skywalker";

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

// good
const obj = {
  lukeSkywalker
};
  • 组织你简写的属性值在你对象声明开始的位置

    为什么? 很容易地告诉你哪些属性是使用简写的

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
};
  • 不要直接调用 Object.prototype 上的方法, 例如 hasOwnPropertypropertyIsEnumerableisPrototypeOf

    为什么? 因为这样可能会跟踪到对象上的属性 - 考虑到 { hasOwnProperty: false } - 或者,这个对象可能是个空对象(Object.create(null)

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

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

// best
const has = Object.prototype.hasOwnProperty; // 缓存这次查询,在模块范围内
console.log(has.call(object, key));
/* or */
import has from "has"; // https://www.npmjs.com/package/has
console.log(has(object, key));
  • 使用 spreads ... 操作符来替代 Object.assign 去浅拷贝。当你删除某些属性时使用 rest ... 操作符去获取一个新对象
// 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 }

数组

  • 使用的字面语法创建对象
// bad
const items = new Array();

// good
const items = [];
  • 使用 Array#push 而不是直接赋值将项目添加到数组
const someStack = [];

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

// good
someStack.push("abracadabra");
  • 使用 spreads ... 来复制数组
// bad
const len = items.length;
const itemsCopy = [];
let i;

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

// good
const itemsCopy = [...items];
  • 将一个 iterable 对象转换成一个数组,使用 spreads ... 而不是 Array.from
const foo = document.querySelectorAll(".foo");

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

// best
const nodes = [...foo];
  • 使用 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);
  • Array.from 代替 spreads ... 映射 iterable ,因为它避免了创建一个中间数组。
// bad
const baz = [...foo].map(mapFn);

// good
const baz = Array.from(foo, mapFn);
  • 使用回调函数返回语句数组方法。可以省略返回的函数体由一个语句返回一个表达式没有副作用
// good
[1, 2, 3].map(x => {
  const y = x + 1;
  return x * y;
});

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

// bad - 没有返回值意味着acc的第一次迭代后变成了未定义的
[
  [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;
});

解构

  • 使用多个对象的属性,使用对象解构来访问

    为什么? 解构节省您创建临时引用这些属性。

// 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}`;
}
  • 使用数组解构
const arr = [1, 2, 3, 4];

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

// good
const [first, second] = arr;
  • 使用对象解构为多个返回值,而不是数组解构

    为什么? 您可以添加新的属性随时间变化或变化的东西而不破坏网站。

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

// 调用者需要考虑的顺序返回数据
const [left, __, top] = processInput(input);

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

// 调用者只选择他们所需要的数据
const { left, top } = processInput(input);

字符串

  • 当通过编程建立字符串,使用模板字符串而不是连接。

    为什么?模板给你一个可读的字符串,简洁的语法与适当的换行和字符串插值特性。

// 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}?`;
}
  • 从不使用 eval() 在一个字符串,它打开太多的漏洞

函数

  • 使用命名函数表达式,而不是函数声明

    为什么? 函数声明是提升的,这意味着在文件中定义函数之前就很容易(太容易了)对其进行引用。 这会损害可读性和可维护性。 如果您发现某个函数的定义足够大或过于复杂,以至于妨碍了对文件其余部分的理解,那么也许是时候将其提取到其自己的模块中了! 无论是否从包含变量中推断出名称,都不要忘记为表达式明确命名(在现代浏览器中或使用 Babel 等编译器时,通常是这种情况)。 这消除了有关错误的调用堆栈的任何假设。

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

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

// good
// 词汇名称与变量引用的调用区分开
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};
  • 将立即调用的函数表达式包装在括号中。
// 立即调用的函数表达式(IIFE)
(function() {
  console.log("Welcome to the Internet. Please follow me.");
})();
  • 切勿在非功能块(ifwhile等)中声明功能。 而是将函数分配给变量。 浏览器将允许您执行此操作,但是它们都以不同的方式解释它,这是个坏消息。

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

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

// good
let test;
if (currentUser) {
  test = () => {
    console.log("Yup.");
  };
}
  • 切勿命名参数 arguments。 这将优先于分配给每个函数作用域的 arguments 对象。
// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}
  • 切勿使用 arguments,而是选择使用 rest 语法 ... 代替

    为什么? ... 明确说明您要提取的参数。 另外,rest arg 是一个实际的数组,而不仅仅是类似数组的 arguments

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

// good
function concatenateAll(...args) {
  return args.join("");
}
  • 使用默认参数语法,而不是对函数参数进行突变。
// really bad
function handleThings(opts) {
  // 不要! 我们不应该对函数参数进行突变。
  // 双重错误:如果 opts 虚假,它将被设置为 {}
  // 变成您想要的值,但它可能会引入一些细微的错误。
  opts = opts || {};
  // ...
}

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

// good
function handleThings(opts = {}) {
  // ...
}
  • 避免使用默认参数的副作用。

    为什么? 他们感到困惑。

var b = 1;
// bad
function count(a = b++) {
  console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
  • 始终将默认参数放在最后。
// bad
function handleThings(opts = {}, name) {
  // ...
}

// good
function handleThings(name, opts = {}) {
  // ...
}
  • 始终将默认参数放在最后。

    为什么? 以这种方式创建函数将评估字符串,类似于 eval(),这将打开漏洞。

// bad
var add = new Function("a", "b", "return a + b");

// still bad
var subtract = Function("a", "b", "return a - b");
  • 永远不要更改参数

    为什么? 操纵作为参数传入的对象可能会在原始调用方中引起不必要的变量副作用。

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

// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, "key") ? obj.key : 1;
}
  • 切勿重新分配参数。

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

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

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

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

function f4(a = 1) {
  // ...
}
  • 最好使用 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]);

箭头函数

  • 当您必须使用匿名函数时(如传递内联回调时),请使用箭头函数符号。

    为什么? 它创建在此上下文中执行的函数版本,这通常是您想要的,并且语法更简洁。 为什么不? 如果您有一个相当复杂的函数,则可以将该逻辑移到它自己的命名函数表达式中。

// 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;
});
  • 如果函数主体由返回无副作用的表达式的单个语句组成,请省略花括号并使用隐式返回。 否则,请保留括号并使用 return 语句。

    为什么? 语法糖。 将多个函数链接在一起时,它读起来很好。

// 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
}));

// 没有隐式返回的副作用
function foo(callback) {
  const val = callback();
  if (val === true) {
    // 如果回调返回true,则执行某些操作
  }
}

let bool = false;

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

// good
foo(() => {
  bool = true;
});
  • 如果表达式跨越多行,请将其括在括号中以提高可读性。

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

类与构造函数

  • 始终使用 class。 避免直接操作 prototype

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

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

// good
class Queue {
  constructor(contents = []) {
    this.queue = [...contents];
  }
  pop() {
    const value = this.queue[0];
    this.queue.splice(0, 1);
    return value;
  }
}
  • 使用 extends 继承。

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

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

// good
class PeekableQueue extends Queue {
  peek() {
    return this.queue[0];
  }
}
  • 方法可以返回 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);
  • 可以编写一个自定义的 toString() 方法,只要确保它能成功运行并且没有副作用即可。
class Jedi {
  constructor(options = {}) {
    this.name = options.name || "no name";
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}
  • 如果未指定,则类具有默认构造函数。 不需要空的构造函数或仅委托给父类的构造函数
// 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";
  }
}
  • 避免重复的类成员。

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

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

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

// good
class Foo {
  bar() {
    return 2;
  }
}
  • 除非外部库或框架要求使用特定的非静态方法,否则类方法应使用 this 或将其制成静态方法。 作为实例方法,应表明其行为根据接收者的属性而有所不同。
// bad
class Foo {
  bar() {
    console.log("bar");
  }
}

// good - this 有被使用到
class Foo {
  bar() {
    console.log(this.bar);
  }
}

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

// good - 静态方法不应使用 this
class Foo {
  static bar() {
    console.log("bar");
  }
}
  • 始终在非标准模块系统上使用模块(导入/导出)。 您始终可以转换到首选的模块系统。

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

// 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;
  • 不要使用通配符导入。

    为什么? 这样可以确保您具有单个默认导出。

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

// good
import AirbnbStyleGuide from "./AirbnbStyleGuide";
  • 不要导出可变绑定。

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

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

// good
const foo = 3;
export { foo };
  • 在具有单个导出的模块中,首选默认导出而不是命名导出。

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

// bad
export function foo() {}

// good
export default function foo() {}
  • 将所有 import 置于非 import 声明上方

    为什么? 由于 import 是提升的,因此将它们全部放在顶部可以防止出现意外情况。

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

import bar from "bar";

// good
import foo from "foo";
import bar from "bar";

foo.init();

迭代器

  • 不要使用迭代器。 首选 JavaScript 的高阶函数,而不要使用 for-in 或 for-of 等循环

    为什么? 这执行了我们不变的规则。 处理返回值的纯函数比副作用更容易推论。 使用 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);

属性

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

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

// good
const isJedi = luke.jedi;
  • 计算指数时,请使用指数运算符 **
// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;

变量

  • 始终使用 const 或 let 来声明变量。 不这样做将导致全局变量。 我们要避免污染全局名称空间。
// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();
  • 使用一个 const 或 let 对每个变量声明或赋值。

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

// 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";
  • 对所有 const 进行分组,然后对所有 let 进行分组。

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

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

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

// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
  • 在需要它们的地方分配变量,但是将它们放在合理的位置。

    为什么? let 和 const 是块作用域的,而不是函数作用域的。

// bad - 不必要的函数调用
function checkName(hasName) {
  const name = getName();

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

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

  return name;
}

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

  const name = getName();

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

  return name;
}
  • 不要链接变量分配。

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

// bad
(function example() {
  // JavaScript将其解释为
  // let a = ( b = ( c = 1 ) );
  // let关键字仅适用于变量a; 变量b和c变为全局变量
  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

// 同样的适用于 `const`
  • 避免使用一元增减(++--)。

    为什么? 根据 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;
  • 禁止使用未使用的变量。

    为什么? 由于重构不完全,在代码中任何地方声明并没有使用的变量很可能是错误。 这样的变量占用了代码中的空间,并可能引起读者的困惑。

// bad

var some_unused_var = 42;

// 只写变量不被视为已使用。
var y = 10;
y = 5;

// 对其本身进行修改的读取不视为已使用。
var z = 0;
z = z + 1;

// 未使用的函数参数。
function getX(x, y) {
  return x;
}

// good

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

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

alert(getXPlusY(x, y));

// 即使未使用,也会忽略 `type` 因为它具有 `rest` 属性的同级。
// 这是提取忽略指定键的对象的一种形式。
var { type, ...coords } = data;
// 现在,'coords' 是不具有 'type' 属性的 'data' 对象。

提升

  • var 声明被提升到最接近的封闭函数作用域的顶部,而它们的赋值则没有。 const 和 let 声明很幸运,有了一个称为“临时死区(TDZ)”的新概念。 重要的是要知道为什么 typeof 不再安全。
// 我们知道这行不通(假设没有notDefined全局变量)
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// 引用变量后创建变量声明将由于变量提升而起作用。 注意:未提升 `true` 的赋值。
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// 解释器将变量声明提升到范围的顶部,这意味着我们的示例可以重写为:
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;
}
  • 匿名函数表达式将使用其变量名,而不是函数分配。
function example() {
  console.log(anonymous); // => undefined

  anonymous(); // => TypeError anonymous is not a function

  var anonymous = function() {
    console.log("anonymous function expression");
  };
}
  • 命名函数表达式将提升变量名,而不是函数名或函数体。
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");
  };
  console.log(named, superPower);
}

// 当函数名称与变量名称相同时,也是如此。
function example() {
  console.log(named); // => undefined

  named(); // => TypeError named is not a function

  var named = function named() {
    console.log("named");
  };
}
  • 函数声明会提升其名称和函数体。
function example() {
  superPower(); // => Flying

  function superPower() {
    console.log("Flying");
  }
}

比较运算符和平等

  • 使用 ===!== 替代 ==!=

  • 条件语句(如 if 语句)使用 ToBoolean 抽象方法强制使用其表达式来评估其表达式,并始终遵循以下简单规则:

    • Objects 评估为 true
    • Undefined 评估为 false
    • Null 评估为 false
    • Booleans 评估为 boolean
    • Numbers 评估为 false 如果为 +0, -0, 或者 NaN, 否则为 true
    • Strings 评估为 false 如果是一个空字符串 '', 否则为 true
if ([0] && []) {
  // true
  // 一个数组(甚至是一个空数组)是一个对象,对象的计算结果为true
}
  • 将快捷方式用于布尔值,而对字符串和数字进行显式比较。
// bad
if (isValid === true) {
  // ...
}

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

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

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

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

// good
if (collection.length > 0) {
  // ...
}
  • 使用大括号创建包含 case 和 default 子句的块级作用域(例如 let,const,function 和 class)

    为什么? 词法声明在整个 switch 块中都是可见的,但是只有在分配时才进行初始化,只有在达到大小写时才会发生。 当多个 case 子句尝试定义同一事物时,这会导致问题。

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

// good
switch (foo) {
  case 1: {
    let x = 1;
    break;
  }
  case 2: {
    const y = 2;
    break;
  }
  case 3: {
    function f() {
      // ...
    }
    break;
  }
  case 4:
    bar();
    break;
  default: {
    class C {}
  }
}
  • 三元数不应嵌套,并且通常是单行表达式。
// bad
const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null;

// 分为2个独立的三元表达式
const maybeNull = value1 > value2 ? "baz" : null;

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

// best
const foo = maybe1 > maybe2 ? "bar" : maybeNull;
  • 避免不必要的三元语句。
// 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;
  • 混合运算符时,请将其括在括号中。 唯一的例外是标准算术运算符:+-**,因为它们的优先顺序已广为人知。 我们建议将/和*括在括号中,因为当它们混合使用时,它们的优先级可能会模棱两可。

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

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

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

// bad
// 一个人可能会混淆思考(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;

  • 如果 if 块始终执行 return 语句,则随后的 else 块是不必要的。 包含返回值的 if 块后面的 else if 块中的返回值可以分为多个 if 块。
// 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;
  }
}

注释

  • 使用 /** ... */ 表示多行注释。
// bad
// make() 返回一个新的 element
// 根据传入的 tag 名称
//
// @param {String} tag
// @return {Element} element
function make(tag) {
  // ...

  return element;
}

// good
/**
 * make()  返回一个新的 element
 * 根据传入的 tag 名称
 */
function make(tag) {
  // ...

  return element;
}
  • // 用于单行注释。 将单行注释放在注释主题上方的换行符上。 除非注释所在的第一行,否则在注释之前放置一个空行。
// 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;
}
  • 以空格开始所有注释,以使其更易于阅读
// 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;
}
  • 使用 FIXMETODO 为您的评论加上前缀可帮助其他开发人员快速了解您是否指出需要重新解决的问题,或者是否建议解决需要解决的问题。 这些与常规评论不同,因为它们是可行的。 这些动作是 FIXME:-需要弄清楚这一点TODO:-需要实施

  • 使用 // FIXME: 注释问题

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

    // FIXME: shouldn’t use a global here
    total = 0;
  }
}
  • 使用 // TODO: 注释问题的解决方案
class Calculator extends Abacus {
  constructor() {
    super();

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

命名约定

  • 避免使用单个字母名称。 用您的命名来描述。
// bad
function q() {
  // ...
}

// good
function query() {
  // ...
}
  • 在命名对象,函数和实例时使用驼峰命名
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
  • 仅在命名构造函数或类时使用帕斯卡命名。
// 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"
});
  • 请勿使用结尾或下划线。

    为什么? 就属性或方法而言,JavaScript 没有私有的概念。 尽管下划线通常是指“私有”的约定,但实际上,这些属性是完全公开的,因此是您的公共 API 合同的一部分。 该约定可能导致开发人员错误地认为更改不会被视为失败或不需要测试。 tl; dr:如果您希望某些东西是“私有的”,则一定不能将其明显地存在。

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

// good
this.firstName = "Panda";

// good, 在可以使用WeakMaps的环境中
const firstNames = new WeakMap();
firstNames.set(this, "Panda");
  • 不要保存 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);
  };
}
  • 基本文件名应与默认导出文件名完全匹配。
// file 1 contents
class CheckBox {
  // ...
}
export default CheckBox;

// file 2 contents
export default function fortyTwo() {
  return 42;
}

// file 3 contents
export default function insideDirectory() {}

// in some other file
// bad
import CheckBox from "./checkBox"; // 帕斯卡命名 import/export, 驼峰命名 filename
import FortyTwo from "./FortyTwo"; // 帕斯卡命名 import/filename, 驼峰命名 export
import InsideDirectory from "./InsideDirectory"; // 帕斯卡命名 import/filename, 驼峰命名 export

// bad
import CheckBox from "./check_box"; // 帕斯卡命名 import/export, snake_case filename
import forty_two from "./forty_two"; // snake_case import/filename, 驼峰命名 export
import inside_directory from "./inside_directory"; // snake_case import, 驼峰命名 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"; // 帕斯卡命名 export/import/filename
import fortyTwo from "./fortyTwo"; // 驼峰命名 export/import/filename
import insideDirectory from "./insideDirectory"; // 驼峰命名 export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js
  • 导出默认功能时,请使用驼峰命名。文件名应与函数名称相同。
function makeStyleGuide() {
  // ...
}

export default makeStyleGuide;
  • 导出构造函数/类/单例/函数库/裸对象时,请使用帕斯卡命名。
const AirbnbStyleGuide = {
  es6: {}
};

export default AirbnbStyleGuide;
  • 首字母缩写词和首字母缩写应始终全部大写或全部小写。

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

// 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 = [
  // ...
];
  • 您可以选择仅在以下情况下将常量大写:(1)已导出,(2)是 const(无法重新分配),并且(3)程序员可以相信它(及其嵌套属性)永不更改。

    为什么? 这是一个辅助工具,可在程序员不确定变量是否会更改的情况下提供帮助。 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";

// ---

// 允许,但不提供语义值
export const apiKey = "SOMEKEY";

// 在大多数情况下更好
export const API_KEY = "SOMEKEY";

// ---

// bad - 不必要的大写键,但不添加语义值
export const MAPPING = {
  KEY: "value"
};

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

存取器

  • 请勿使用 JavaScript getter / setter,因为它们会导致意外的副作用,并且更难于测试,维护和推理。 相反,如果您确实制作了访问器函数,请使用 getVal()setVal('hello')
// bad
class Dragon {
  get age() {
    // ...
  }

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

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

  setAge(value) {
    // ...
  }
}
  • 如果属性/方法是布尔值,请使用 isVal()hasVal()
// bad
if (!dragon.age()) {
  return false;
}

// good
if (!dragon.hasAge()) {
  return false;
}
  • 可以创建 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];
  }
}

事件

  • 将数据有效负载附加到事件(无论是 DOM 事件还是诸如 Backbone 事件之类的更专有的事件)时,请传递对象文字(也称为“哈希”)而不是原始值。 这允许后续的参与者将更多数据添加到事件有效负载,而无需查找和更新事件的每个处理程序。 例如,代替:
// bad
$(this).trigger("listingUpdated", listing.id);

// ...

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

更好的:

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

// ...

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

标准库

标准库包含实用程序,这些实用程序在功能上已损坏,但由于遗留原因而保留。

  • 使用 Number.isNaN 代替全局 isNaN

    为什么? 全局 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
  • 使用 Number.isFinite 代替全局 isFinite

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

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

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