Featured image of post Airbnb JavaScript Style Guide

Airbnb JavaScript Style Guide

Airbnb JavaScript Style Guide

Photo by Pankaj Patel on Unsplash

https://github.com/jigsawye/javascript

Airbnb JavaScript Style Guide()

一份汇集了在 JavaScript 中被普遍使用的风格指南。

Downloads Gitter

其他风格指南

翻译自 Airbnb JavaScript Style Guide

数据类型

  • 1.1 基本:你可以直接访问基本数据类型。
  • 字符串
  • 数字
  • 布尔
  • null
  • undefined
const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9
  • 1.2 复杂:你需要通过引用的方式访问复杂数据类型。
  • 对象
  • 数组
  • 函数
const foo = [1, 2];
const bar = foo;

bar[0] = 9;

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

引用

为什么?因为这能确保你无法对引用重新赋值,也不会让你的代码有错误或难以理解。

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

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

为什么?因为 let 的作用域是在块级内,而不像 var 是在函数内。

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

// good, use the let.
let count = 1;
if (true) {
    count += 1;
}
  • 2.3 请注意,letconst 的作用域都只在块级内。
// const 及 let 只存在于他们被定义的块级内。
{
    let a = 1;
    const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError

对象

// bad
const item = new Object();

// good
const item = {};
// bad
const superman = {
    default: { clark: 'kent' },
    private: true,
};

// good
const superman = {
    defaults: { clark: 'kent' },
    hidden: true,
};
// bad
const superman = {
    class: 'alien',
};

// bad
const superman = {
    klass: 'alien',
};

// good
const superman = {
    type: 'alien',
};

  • 3.4 创建具有动态属性名称的对象时请使用可被计算的属性名称。

为什么?因为这样能够让你在同一个地方定义所有的对象属性。


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,
};
  • 3.7 请在对象声明的开头将简写的属性分组。

为什么?因为这样能够很简单地看出哪些属性是使用简写。

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,
};

为什么?整体来说,我们认为这在主观上更容易阅读。它会改善语法高亮,也能让多数的 JS 引擎更容易优化。

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

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

数组

// 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++) {
    itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];
  • 4.4 如果要转换一个像数组的对象至数组,可以使用 Array#from。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
  • 4.5 在数组方法的回调使用 return 声明。若函数本身是如 8.2 的单一语法,那么省略 return 是可以的。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
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
    const flatten = memo.concat(item);
    flat[index] = memo.concat(item);
});

// good
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
    const flatten = memo.concat(item);
    flat[index] = flatten;
    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;
  • 5.3 需要返回多个值时请使用对象解构赋值,而不是数组解构赋值。

为什么?因为你可以增加新的属性或改变排序且不须更动调用的位置。

// 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, right } = processInput(input);

字符串

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

// good
const name = 'Capt. Janeway';
  • 6.2 如果字符串超过 100 个字符,请使用字符串连接符号换行。
  • 6.3 注意:过度的长字符串连接可能会影响性能。jsPerf讨论串
// 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.';

为什么?因为模板字符串更有可读性,正确的换行符号及字符串插值功能让语法更简洁。

// 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.5 千万不要在字符串中使用 eval(),会造成许多的漏洞。

函数

为什么?因为函数声明是可命名的,所以他们在调用栈中更容易被识别。此外,函数声明自身都会被提升,而函数表达式则只有引用会被提升。这个规则使得箭头函数可以完全取代函数表达式。

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

// good
function foo() {
}

为什么?一个立即执行函数是个独立的单元-将函数及调用函数的括号包起来明确表示这一点。注意在模块世界的任何地方,你都不需要使用立即执行函数。

// 立即执行函数(IIFE)
(function () {
    console.log('Welcome to the Internet. Please follow me.');
}());
  • 7.3 绝对不要在非函数的块级(if、while 等等)声明函数。你可以将函数赋予至变量解决这个问题。浏览器会允许你这么做,但不同浏览器产生的结果可能会不同。eslint: no-loop-func

  • 7.4 **注意:**ECMA-262 将块级定义为语句。函数声明则不是语句。阅读 ECMA-262 关于这个问题的问题说明

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

// good
let test;
if (currentUser) {
    test = () => {
    console.log('Yup.');
    };
}
  • 7.5 请勿将参数命名为 arguments,这样会将覆盖掉函数作用域传来的 arguments
// bad
function nope(name, options, arguments) {
    // ...stuff...
}

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

为什么?使用 ... 能够明确指出你要将参数传入哪个变量。再加上 rest 参数是一个真正的数组,而不像 arguments 似数组而非数组。

// 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) {
    // 不!我们不该变动函数的参数。
    // Double bad: 如果 opt 是 false ,那们它就会被设定为一个对象,
    // 或許你想要这么做,但是这样可能会造成一些 Bug。
    opts = opts || {};
    // ...
}

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

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

为什么?因为这样会让思绪混淆。

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

// good
function handleThings(name, opts = {}) {
    // ...
}
  • 7.10 千万别使用构造函数去创建一个新的函数。

为什么?通过这种方式创建一个函数来计算字符串类似于 eval(),会造成许多的漏洞。

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

// still bad
var subtract = Function('a', 'b', 'return a - b');
  • 7.11 在函数的标识后放置空格。

为什么?一致性较好,而且你不应该在新增或删除名称时增加或减少空格。

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

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

为什么?操作作为参数传入的对象可能导致变量产生原调用者不期望的副作用。

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

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

为什么?将参数重新赋值可能导致意外的行为,尤其在访问 arguments 对象时。它可能会引起优化的问题,尤其在 V8。

```javascript
// 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) {
}
```

箭头函数

为什么?它会在有 this 的内部建立了一个新版本的函数,通常功能都是你所想象的,而且语法更为简洁。

为什么不?如果你已经有一个相当复杂的函数时,或许你该将逻辑都移到一个函数声明上。

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

为什么?因为语法修饰。这样能够在多个函数链结在一起的时候更易读。

为什么不?如果你打算返回一个对象。

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

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

// good
[1, 2, 3].map((number) => {
    const nextNumber = number + 1;
    return `A string containing the ${nextNumber}.`;
});
  • 8.3 如果表达式跨了多行,请将它们包在括号中增加可读性。

为什么?这么做更清楚的表达函数的开始与结束的位置。

// bad
[1, 2, 3].map(number => 'As time went by, the string containing the ' +
    `${number} became much longer. So we needed to break it over multiple ` +
    'lines.'
);

// good
[1, 2, 3].map(number => (
    `As time went by, the string containing the ${number} became much ` +
    'longer. So we needed to break it over multiple lines.'
));

为什么?减少视觉上的混乱。

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

// good
[1, 2, 3].map(x => x * x);
```javascript
// good
[1, 2, 3].map(number => (
    `A long string with the ${number}. Its so long that weve broken it ` +
    'over multiple lines!'
));

// 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;
});
// 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 => { return item.height > 256 ? item.largeSize : item.smallSize; }

构造函数

  • 9.1 总是使用 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;
    }
}
  • 9.2 使用 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];
    }
}
  • 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 若类没有指定构造函数,那它会拥有默认的构造函数。一个空的构造函数或只委派给父类是不必要的。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';
    }
}

模块

  • 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;

迭代器及生成器

  • 11.1 不要使用迭代器。更好的做法是使用 JavaScript 的高阶函数,像是 map()reduce(),替代如 for-of 的循环语法。eslint: no-iterator

为什么?这加强了我们不变的规则。处理纯函数的返回值让代码更易读,胜过它所造成的函数副作用。

eslint rules: no-iterator.

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 (使用 javascript 的高阶函数)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
  • 11.2 现在还不要使用生成器。

为什么?因为它现在编译至 ES5 还没有编译得非常好。

属性

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');

变量

  • 13.1 为了避免污染全局的命名空间,请使用 const 来声明变量,如果不这么做将会产生全局变量。Captain Planet warned us of that.
// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();

为什么?因为这样更容易增加新的变量声明,而且你也不用担心替换 ;, 及加入的标点符号不同的问题。

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

// bad
// (比较上述例子找出错误)
const items = getItems(),
    goSportsTeam = true;
    dragonball = 'z';

// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
  • 13.3 将所有的 constlet 分组。

为什么?当你需要根据之前已赋值的变量来赋值给未赋值变量时相当有帮助。

// 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 在你需要的地方赋值给变量,但是请把它们放在合理的位置。

为什么?因为 letconst 是在块级作用域内,而不是函数作用域。

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

提升

// 我们知道这样是行不通的
// (假设没有名为 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;
}

// 使用 const 及 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');
    };
}

// 当函数名称和变量名称相同时也是如此。
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');
    }
}

条件式与等号

Comparison Operators & Equality

  • 15.1 请使用 ===!== ,别使用 ==!= 。eslint: eqeqeq

  • 15.2 像是 if 的条件语法内会使用 ToBoolean 的抽象方法强转类型,并遵循以下规范:

  • 对象 转换为 true
  • Undefined 转换为 false
  • Null 转换为 false
  • 布尔 转换为 该布尔值
  • 数字 如果是 +0, -0, 或 NaN 则转换为 false,其他的皆为 true
  • 字符串 如果是空字符串 '' 则转换为 false,其他的皆为 true
if ([0] && []) {
    // true
    // 数组(即使为空)为一个对象,所以转换为 true
}
  • 15.3 使用简短的方式。
// bad
if (name !== '') {
    // ...stuff...
}

// good
if (name) {
    // ...stuff...
}

// bad
if (collection.length > 0) {
    // ...stuff...
}

// good
if (collection.length) {
    // ...stuff...
}
  • 15.4 想了解更多信息请参考 Angus Croll 的 Truth Equality and JavaScript
  • 15.5 Use braces to create blocks in case and default clauses that contain lexical declarations (e.g. let, const, function, and class).
  • 15.6 casedefault 包含了声明语法(例如:letconstfunctionclass)时使用大括号来创建块级。

Why? Lexical declarations are visible in the entire switch block but only get initialized when assigned, which only happens when its case is reached. This causes problems when multiple case clauses attempt to define the same thing. 为什么?声明语法可以在整个 switch 块级中可见,但是只在进入该 case 时初始化。当多个 case 语法时会导致尝试定义相同事情的问题。

eslint rules: no-case-declarations.

// 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.7 不应该使用嵌套的三元运算符,且通常应该使用单行来表示。

eslint rules: no-nested-ternary.

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

// better
const maybeNull = value1 > value2 ? 'baz' : null;

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

// best
const maybeNull = value1 > value2 ? 'baz' : null;

const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
  • 15.8 避免不必要的三元运算符语法。

eslint rules: no-unneeded-ternary.

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

  • 16.1 多行块请使用大括号刮起来。
// 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;
}
// bad
if (test) {
    thing1();
    thing2();
}
else {
    thing3();
}

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

控制语句

  • 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 不要用选择运算符(selection operators)来取代控制语句。
// bad
!isRunning && startRunning();

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

注释

  • 18.1 多行注释请使用 /** ... */ ,包含描述,指定类型以及参数值还有返回值。
// bad
// make() 根据传入的 tag 名称返回一个新的组件
//
// @param {String} tag
// @return {Element} element
function make(tag) {

    // ...stuff...

    return element;
}

// good
/**
 * make() 根据传入的 tag 名称返回一个新的组件
    *
    * @param {String} tag
    * @return {Element} element
    */
function make(tag) {

    // ...stuff...

    return element;
}
  • 18.2 单行注释请使用 //。在欲注释的上方新增一行进行注释。在注释的上方空一行,除非他在块的第一行。
// bad
const active = true;  // 当目前分页

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

// bad
function getType() {
    console.log('fetching type...');
    // 设定默认的类型为 'no type'
    const type = this._type || 'no type';

    return type;
}

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

    // 设定默认的类型为 '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 在注释前方加上 FIXMETODO 可以帮助其他开发人员快速了解这是一个需要重新讨论的问题,或是一个等待解决的问题。和一般的注释不同,他们是可被执行的。对应的动作为 FIXME -- 重新讨论并解决TODO -- 必须执行

  • 18.4 使用 // FIXME: 标注问题。

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

    // FIXME: 不该在这使用全局变量
    total = 0;
    }
}
  • 18.5 使用 // TODO: 标注问题的解决方式。
class Calculator extends Abacus {
    constructor() {
    super();

    // TODO: total 应该可被传入的参数所修改
    this.total = 0;
    }
}

空格

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

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

// good
function baz() {
∙∙const name;
}
// 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',
});
// bad
if(isJedi) {
    fight ();
}

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

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

// good
function fight() {
    console.log('Swooosh!');
}
// bad
const x=y+5;

// good
const x = y + 5;
  • 19.5 在文件的最尾端加上一行空白行。
// bad
(function (global) {
    // ...stuff...
})(this);
// bad
(function (global) {
    // ...stuff...
})(this);

// good
(function (global) {
    // ...stuff...
})(this);
// 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);
// 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;
// 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);
}
// bad
function bar( foo ) {
    return foo;
}

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

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

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

// good
const foo = [1, 2, 3];
console.log(foo[0]);
// bad
const foo = {clark: 'kent'};

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

为什么?这样确保可读性及维护性。

// bad
const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. Whatever wizard constrains a helpful ally. The counterpart ascends!';

// 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 = 'Whatever national crop flips the window. The cartoon reverts within the screw. ' +
    'Whatever wizard constrains a helpful ally. The counterpart ascends!';

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

逗号

// 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',
};

为什么?这会让 Git 的差异列表更干净。另外,Babel 转译器也会删除结尾多余的逗号,也就是说你完全不需要担心在老旧的浏览器发生多余逗号的问题

// bad - 不含多余逗号的 git 差异列表
const hero = {
        firstName: 'Florence',
    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb graph', 'modern nursing']
};

// good - 包含多余逗号的 git 差异列表
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 () {
    const name = 'Skywalker'
    return name
})()

// good
(() => {
    const name = 'Skywalker';
    return name;
}());

// good(防止当两个文件含有立即执行函数需要合并时,函数被当成参数处理)
;(() => {
    const name = 'Skywalker';
    return name;
}());

了解更多

类型转换

  • 22.1 在开头的声明进行强制类型转换。
  • 22.2 字符串:
// => this.reviewScore = 9;

// bad
const totalScore = this.reviewScore + '';

// good
const totalScore = String(this.reviewScore);
  • 22.3 数字:使用 Number 做类型转换,而 parseInt 则始终以基数解析字符串。eslint: radix
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 是你的瓶颈,所以你对于性能方面的原因而必须使用位移,请留下评论并解释为什么使用,及你做了哪些事情。
// good
/**
 * 使用 parseInt 导致我的程序变慢,改成使用
    * 位右移强制将字符串转为数字加快了他的速度。
    */
const val = inputValue >> 0;
  • 22.5 **注意:**使用位转换时请小心。数字为 64 位数值,但是使用位转换时则会返回一个 32 位的整数(来源),这会导致大于 32 位的数值产生异常 讨论串,32 位的整数最大值为 2,147,483,647:
2147483647 >> 0 //=> 2147483647
2147483648 >> 0 //=> -2147483648
2147483649 >> 0 //=> -2147483647
const age = 0;

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

// good
const hasAge = Boolean(age);

// good
const hasAge = !!age;

命名规范

  • 23.1 避免使用单一字母的名称,让你的名称有解释的含义。
// bad
function q() {
    // ...stuff...
}

// good
function query() {
    // ..stuff..
}
// 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',
});
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';

// good
this._firstName = 'Panda';
// 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 如果你的文件只有输出一个类,你的文件名称必须和你的类名称相同。
// 文件内容
class CheckBox {
    // ...
}
export default CheckBox;

// 在其他的文件
// bad
import CheckBox from './checkBox';

// bad
import CheckBox from './check_box';

// good
import CheckBox from './CheckBox';
  • 23.7 当你导出为默认的函数时请使用驼峰式大小写。文件名称必须与你的函数名称一致。
function makeStyleGuide() {
}

export default makeStyleGuide;
  • 23.8 当你导出为单例 / 库 / 空对象时请使用帕斯卡命名法。
const AirbnbStyleGuide = {
    es6: {
    }
};

export default AirbnbStyleGuide;

访问器

  • 24.1 属性的访问器函数不是必须的。
  • 24.2 别使用 JavaScript 的 getters 或 setters,因为它们会导致意想不到的副作用,而且不易于测试、维护以及进行推测。取而代之,如果你要创建一个访问器函数,请使用 getVal() 及 setVal(‘hello’)。
// bad
dragon.age();

// good
dragon.getAge();

// bad
dragon.age(25);

// good
dragon.setAge(25);
  • 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];
    }
}

事件

  • 25.1 当需要对事件传入数据时(不论是 DOM 事件或是其他私有事件),请传入对象替代单一的数据。这样可以使之后的开发人员直接加入其他的数据到事件里,而不需更新该事件的处理器。例如,比较不好的做法:
// 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
});

jQuery

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

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

// good
const $sidebarBtn = $('.sidebar-btn');
  • 26.2 缓存 jQuery 的查询。
// bad
function setSidebar() {
    $('.sidebar').hide();

    // ...stuff...

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

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

    // ...stuff...

    $sidebar.css({
    'background-color': 'pink'
    });
}
  • 26.3 DOM 的查询请使用层递的 $('.sidebar ul') 或 父元素 > 子元素 $('.sidebar > ul')jsPerf
  • 26.4 对作用域内的 jQuery 对象使用 find 做查询。
// bad
$('ul', '.sidebar').hide();

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

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

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

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

ECMAScript 5 兼容性

ECMAScript 6 风格

  • 28.1 以下是链接到各个 ES6 特性的列表。
  1. 箭头函数
  2. 对象简写
  3. 简洁对象
  4. 可计算的对象属性
  5. 模板字符串
  6. 解构赋值
  7. 默认参数
  8. 剩余参数(Rest)
  9. 数组扩展
  10. Let 及 Const
  11. 迭代器及生成器
  12. 模块

标准库

标准库(Standard Library)基于历史因素,仍保有某些功能有缺陷的函数。

  • 29.1 使用 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
  • 29.2 使用 Number.isFinite 而非 isFinite

为什么?全局函数 isFinite 会先将任何非数值转换为数值,如果转换后之值有限,则函数返回 true。 若真要转换为数值,请表达清楚。

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

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

测试

function foo() {
    return true;
}
  • 30.2 无题,不过很重要
  • 不论你用哪个测试框架,你都应该撰写测试!
  • 力求撰写许多的纯函数,并尽量减少异常发生的机会。
  • 要对 stubs 及 mocks 保持严谨——他们可以让你的测试变得更加脆弱。
  • 我们在 Airbnb 主要使用 mocha。对小型或单独的模块偶尔使用 tape
  • 努力达到 100% 的测试覆盖率是个很好的目标,即使实现这件事是不切实际的。
  • 每当你修复完一个 bug,就撰写回归测试。一个修复完的 bug 若没有回归测试,通常在未来肯定会再次发生损坏。

性能

资源

学习 ES6

请读这个

工具

  • Code Style Linters

其他的风格指南

其他风格

了解更多

书籍

博客

Podcasts

谁在使用

这是正在使用这份风格指南的组织列表。送一个 pull request 后我们会将你增加到列表上。

翻译

This style guide is also available in other languages:

JavaScript 风格指南

与我们讨论 JavaScript

贡献者

License

(The MIT License)

Copyright (c) 2014-2016 Airbnb

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Amendments

We encourage you to fork this guide and change the rules to fit your team’s style guide. Below, you may list some amendments to the style guide. This allows you to periodically update your style guide without having to deal with merge conflicts.

Reference

All rights reserved,未經允許不得隨意轉載
Built with Hugo
主题 StackJimmy 设计