Featured image of post Airbnb JavaScript Style Guide

Airbnb JavaScript Style Guide

Airbnb JavaScript Style Guide

Photo by Pankaj Patel on Unsplash

Airbnb JavaScript Style Guide() {

一份彙整了在 JavasScript 中被普遍使用的风格指南。

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

// good
[1, 2, 3].map(number => (
    `A long string with the ${number}. It’s so long that we’ve 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. 预设参数
  9. 剩馀参数(Rest)
  10. 阵列扩展
  11. Let 及 Const
  12. 迭代器及产生器
  13. 模组

标准程式库

标准程式库(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 设计