您的位置:首页 » 分类: JavaScript » 文章: Airbnb JavaScript 编码规范(涵盖 ECMAScript 6+)

Airbnb JavaScript 编码规范(涵盖 ECMAScript 6+)

小编推荐:掘金是一个高质量的技术社区,从 ECMAScript 6 到 Vue.js,性能优化到开源类库,让你不错过前端开发的每一个技术干货。各大应用市场搜索「掘金」即可下载APP,技术干货尽在掌握..

一份最合理的 JavasScript 编码规范。 翻译自:https://github.com/airbnb/javascript

注意:本指南假定您使用的是Babel,并要求您使用 babel-preset-airbnb 或相当的预设插件。它还假定您正在您的应用程序中安装 shims/polyfills,实用airbnb-browser-shims或相当的插件。

目录

  1. 类型
  2. 引用
  3. 对象
  4. 数组
  5. 解构
  6. 字符串
  7. 函数
  8. 箭头函数
  9. 类 和 构造函数
  10. 模块
  11. 迭代器 和 生成器
  12. 属性
  13. 变量
  14. 提升
  15. 比较运算符 和 等号
  16. 代码块
  17. 控制语句
  18. 注释
  19. 空白
  20. 逗号
  21. 分号
  22. 类型转换
  23. 命名规则
  24. 存取器
  25. 事件
  26. jQuery
  27. ECMAScript 5 兼容性
  28. ECMAScript 6+ (ES 2015+) 编码风格
  29. 标准库
  30. 测试
  31. 性能
  32. 相关资源

类型


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

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

bar = 9;

console.log(foo, bar); // => 1, 9
  • Symbols 不能被完全 polyfill, 所以不应该在目标浏览器/环境不支持它们的情况下使用它们。


1.2 复合类型: 当你访问一个复合类型时,你需要引用它的值。

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

bar[0] = 9;

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

⬆ 返回目录

引用 References


2.1 对所有的引用使用 const ;不要使用 var。eslint: prefer-const, no-const-assign

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

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

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


2.2 如果你一定需要可变动的引用,使用 let 代替 var。eslint: no-var jscs: disallowVar

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

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

// good, 使用 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,引用错误

⬆ 返回目录

对象 Objects


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

// bad
const item = new Object();

// good
const item = {};


3.2 当创建带有动态属性名称的对象时使用计算的属性名称。

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

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

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

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


3.3 使用对象方法速记语法。 eslint: object-shorthand jscs: requireEnhancedObjectLiterals

// bad
const atom = {
    value: 1,

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

// good
const atom = {
    value: 1,

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


3.4 使用对象属性速记语法。eslint: object-shorthand jscs: requireEnhancedObjectLiterals

为什么?编写代码和描述更加简短。

const lukeSkywalker = 'Luke Skywalker';

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

// good
const obj = {
    lukeSkywalker,
};


3.5 将速记属性分组写在对象声明的开始处。

为什么?更容易看出哪些属性在使用速记语法。

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

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

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


3.6 只用引号引无效标识符的属性。 eslint: quote-props jscs: disallowQuotedKeysInObjects

为什么?一般来说,我们认为比较容易阅读。它改进了语法高亮显示,并且更容易被许多JS引擎优化。

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

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


3.7 不要直接调用 Object.prototype 的方法,比如 hasOwnProperty, propertyIsEnumerable, 和 isPrototypeOf.

为什么?这些方法可能会被对象的属性所覆盖 – 比如 { hasOwnProperty: false } – 或者,对象可能是空( null )对象(Object.create(null))。

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

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

// best
const has = Object.prototype.hasOwnProperty; // 在模块作用域内,缓存查找一次。
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));


3.8 用对象展开操作符浅复制对象,优先于Object.assign 。使用对象剩余操作符来获得一个省略某些属性的新对象。

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

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

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

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

⬆ 返回目录

数组 Arrays


4.1 使用字面量创建数组。 eslint: no-array-constructor

// bad
const items = new Array();

// good
const items = [];


4.2 在向数组添加元素时使用 Array#push 替代直接赋值。

const someStack = [];

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

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


4.3 使用数组展开操作符 ... 复制数组。

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

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

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


4.4 使用展开操作符 ... 代替 Array.from,来将一个类数组(array-like) 对象转换成数组。

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

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

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


4.5 实用 Array.from 代替展开操作符 ... 来映射迭代,因为它避免了创建媒介数组。

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

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


4.6 在数组方法回调中使用 return 语句。如果函数体由一个返回无副作用的表达式的单个语句组成,那么可以省略返回值,查看8.2 说明。 eslint: array-callback-return

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

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

// bad - 没有返回值意味着  `memo` 在第一次迭代后变成 undefined
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
    const flatten = memo.concat(item);
    memo[index] = flatten;
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
    const flatten = memo.concat(item);
    memo[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;
});


4.7 如果数组有多行,请在打开和关闭数组括号之前使用换行符。

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

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

const numberInArray = [
1, 2,
];

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

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

const numberInArray = [
1,
2,
];

⬆ 返回目录

解构 Destructuring


5.1 当访问和使用对象的多个属性时,请使用对象解构。eslint: prefer-destructuring jscs: requireObjectDestructuring

为什么?解构可以在你建这些属性的临时引用时,为你节省时间。

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

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

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

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


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

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

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

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


5.3 使用对象解构来实现多个返回值,而不是数组解构。jscs: disallowArrayDestructuringReturn

为什么? 您可以随着时间的推移添加新的属性或更改排序,而不会改变调用时的位置。

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

⬆ 返回目录

字符串 Strings


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

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

// bad - 模板字面量应该包含插值或换行符
const name = `Capt. Janeway`;

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


6.2 超过100个字符,导致换行的字符串不应使用字符串连接符写成多行。

为什么? 连接字符串是痛苦的工作,而且使代码不易于搜索。

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

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

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


6.3 以编程方式构建字符串时,请使用模板字符串而不是字符串连接。eslint: prefer-template template-curly-spacing jscs: requireTemplateStrings

为什么? 模板字符串为你提供了更好的可读性,简洁的语法,正确的换行符和字符串插值功能。

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

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

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

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


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


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

为什么?反斜杠会破坏可读性,因此只有在必要时才转义。

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

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

⬆ 返回目录

函数 Functions


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

为什么? 函数声明很容易被提升(Hoisting),你可以在函数被定义之前引用该函数。这对可读性和可维护性来说都是不利的。如果你发现一个函数的定义很大或很复杂,以至于妨碍了解文件的其他部分,那么也许是时候把它提取到自己的模块中去!不要忘记显式地命名表达式,不管该名称是否从包含变量中推断出来的(在现代浏览器中,或在使用编译器如Babel 时经常出现这种情况)。这消除了关于Error的调用堆栈的任何假设。(讨论

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

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

// good 
// 用明显区别于变量引用调用的词汇命名
const short = function longUniqueMoreDescriptiveLexicalFoo() {
    // ...
};


7.2 用圆括号包裹立即调用函数表达式 (IIFE)。 eslint: wrap-iife jscs: requireParenthesesAroundIIFE

为什么?一个立即调用函数表达式是一个单独的单元 – 将函数表达式包裹在括号中,后面再跟一个调用括号,这看上去很赶紧。请注意,在模块的世界中,你几乎不需要 IIFE。

// 立即调用函数表达式 (IIFE)
(function () {
    console.log('Welcome to the Internet. Please follow me.');
}());


7.3 永远不要在一个非函数代码块(ifwhile 等)中声明一个函数,把那个函数赋给一个变量代替。浏览器允许你这么做,但是它们都以不同的方式解析。 eslint: no-loop-func


7.4 注意: ECMA-262 把 block 定义为一组语句。函数声明不是语句。

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

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


7.5 永远不要把参数命名为 arguments。这将会覆盖原来函数作用域内的 arguments 对象。

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

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


7.6 不要使用 arguments。可以选择 rest 语法 ... 替代。eslint: prefer-rest-params

为什么?使用 ... 能明确你要传入的参数。另外 rest(剩余)参数是一个真正的数组,而 arguments 是一个类数组(Array-like)。

// 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) {
    // 不!我们不应该改变函数参数。
    // 更加糟糕: 如果参数 opts 是 falsy(假值) 的话,它将被设置为一个对象,
    // 这可能是你想要的,但它可以引起一些小的错误。
    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 切勿使用 Function 构造函数来创建新函数。 eslint: no-new-func

为什么? 以这种方式创建一个函数,与 eval() 类似,会对字符串求值,这会打开漏洞。

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

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


7.11 隔开函数签名,括号两边用空格隔开。 eslint: space-before-function-paren space-before-blocks

为什么?这样做有益代码的一致性,添加或删除函数名时不需要添加或删除空格。

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

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


7.12 不要改变参数。 eslint: no-param-reassign

为什么?操作作为参数传入的对象,可能会在调用原始对象时造成不必要的变量副作用。(愚人码头注:对象是引用类型)

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

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


7.13 参数不要重新赋值。 eslint: no-param-reassign

为什么? 重新分配参数可能会导致意外的行为,特别是在访问 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) {
    // ...
}


7.14 优先使用展开运算符 ... 来调用可变参数函数。 eslint: prefer-spread

为什么? 它更清洁,你不需要提供一个上下文,而且你不能轻易地实用 applynew

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

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

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

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


7.15 具有多行签名或调用的函数,应该像本指南中的其他多行列表一样缩进:每一项都独占一行,最后一项上有一个尾逗号。

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

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

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

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

⬆ 返回目录

箭头函数 Arrow Functions


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

为什么? 它创建了一个在 this 上下文中执行的函数的版本,这通常是你想要的,而且这样的写法更为简洁。(愚人码头注:参考 Arrow functions – JavaScript | MDNES6 arrow functions, syntax and lexical scoping

为什么不? 如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个声明函数上。

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

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


8.2 如果函数体由一个返回无副作用(side effect)的expression(表达式)的单行语句组成,那么可以省略大括号并使用隐式返回。否则,保留大括号并使用 return 语句。eslint: arrow-parens, arrow-body-style jscs: disallowParenthesesAroundArrowParam, requireShorthandArrowFunctions

愚人码头注,什么是副作用(side effect)?一段代码,即在不需要的情况下,创建一个变量并在整个作用域内可用。

为什么? 语法糖。 当多个函数链式调用时,可读性更高。

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

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

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

let bool = false;

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

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


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

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

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

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


8.4 如果你的函数只有一个参数并且不使用大括号,则可以省略参数括号。否则,为了清晰和一致性,总是给参数加上括号。
注意:总是使用圆括号也是可以被lint工具接受的,在这种情况下 使用 eslint 的 “always” 选项,或者 jscs 中不要包含 disallowParenthesesAroundArrowParam 选项。 eslint: arrow-parens jscs: disallowParenthesesAroundArrowParam

为什么? 不造成视觉上的混乱。

// 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 don’t want it to take up space on the .map line!`
));

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

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


8.5 避免使用比较运算符(&lt; =, &gt;=)时,混淆箭头函数语法(=&gt;)。 eslint: no-confusing-arrow

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

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

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

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

⬆ 返回目录

类 Classes & 构造函数 Constructors


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 继承。

为什么?因为 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 如果没有指定,类有一个默认的构造函数。一个空的构造函数或者只是委托给父类则不是必须的。 eslint: no-useless-constructor

// bad
class Jedi {
    constructor() {}

    getName() {
    return this.name;
    }
}

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

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


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

为什么? 重复类成员声明将默认使用最后一个 – 重复类成员几乎肯定是一个错误。

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

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

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

⬆ 返回目录

模块 Modules


10.1 总是使用模块 (import/export) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。

为什么?模块就是未来,让我们开始迈向未来吧。

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

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

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


10.2 不要使用通配符 import(导入)。

为什么?这样能确保你只有一个默认 export(导出)。

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

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


10.3 不要从 import(导入) 中直接 export(导出)。

为什么?虽然一行代码简洁明了,但有一个明确的 import(导入) 方法和一个明确的 export(导出) 方法,使事情能保持一致。

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

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


10.4 一个地方只在一个路径中 import(导入) 。
eslint: no-duplicate-imports

为什么? 从同一路径 import(导入) 多个模块分散在多行代码中,可能会使代码难以维护。

// bad
import foo from 'foo';
// … 其他一些 imports … //
import { named1, named2 } from 'foo';

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

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


10.5 不要 export(导出) 可变绑定。eslint: import/no-mutable-exports

为什么? 一般应该避免可变性,特别是在导出可变绑定时。虽然一些特殊情况下,可能需要这种技术,但是一般而言,只应该导出常量引用。

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

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


10.6 在只有单个导出的模块中,默认 export(导出) 优于命名 export(导出)。eslint: import/prefer-default-export

为什么?为了鼓励更多的文件只有一个 export(导出),这有利于模块的可读性和可维护性。

// bad
export function foo() {}

// good
export default function foo() {}


10.7 将所有 import 导入放在非导入语句的上面。eslint: import/first

由于 import 被提升,保持他们在顶部,防止意外的行为。

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

import bar from 'bar';

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

foo.init();


10.8 多行导入应该像多行数组和对象字面量一样进行缩进。

为什么? 花括号应遵循与编码风格指南中的每个其他花括号相同的缩进规则,末尾的逗号也一样。

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

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


10.9 禁止在模块 import(导入) 语句中使用 Webpack 加载器语法。
eslint: import/no-webpack-loader-syntax

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

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

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

⬆ 返回目录

迭代器 Iterators 和 生成器 Generators


11.1 不要使用 iterators(迭代器) 。请使用高阶函数,例如 map()reduce() 等,而不是像 for-infor-of 这样的循环。 eslint: no-iterator no-restricted-syntax

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

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

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

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

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

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

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

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


11.2 现在不要使用 generators (生成器)。

为什么? 因为目前没有很好地办法将他们转译成 ES5 。


11.3 如果您必须使用 generators (生成器),或者如果漠视我们的建议,请确保它们的函数签名恰当的间隔。 eslint: generator-star-spacing

为什么? function* 都是同一概念关键字的组成部分 – * 不是 function 的修饰符,function* 是一个独特的构造,与function不同。

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

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

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

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

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

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

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

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

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

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

⬆ 返回目录

属性 Properties


12.1 使用 点语法(.) 来访问对象的属性。 eslint: dot-notation jscs: requireDotNotation

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

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

// good
const isJedi = luke.jedi;


12.2 当通过变量访问属性时使用中括号 []

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

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

const isJedi = getProp('jedi');


12.3 求幂时使用求幂运算符 ** 。eslint: no-restricted-properties.

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

// good
const binary = 2 ** 10;

⬆ 返回目录

变量 Variables


13.1 总是使用 constlet 来声明变量。 不这样做会导致产生全局变量。 我们希望避免污染全局命名空间。 eslint: no-undef prefer-const

// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();


13.2 使用 constlet声明每个变量。 eslint: one-var jscs: disallowMultipleVarDecl

为什么? 以这种方式添加新的变量声明更容易,你永远不必担心是否需要将 , 换成 ;,或引入标点符号差异。您也可以在调试器中遍历每个声明,而不是一次跳过所有的变量。

// 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 - 不必要的函数调用
function checkName(hasName) {
    const name = getName();

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

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

    return name;
}

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

    const name = getName();

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

    return name;
}


13.5 变量不要链式赋值。eslint: no-multi-assign

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

// bad
(function example() {
    // JavaScript 将其解析为
    // let a = ( b = ( c = 1 ) );
    // let关键字只适用于变量a;
    // 变量b和c变成了全局变量。
    let a = b = c = 1;
}());

console.log(a); // 抛出 ReferenceError(引用错误)
console.log(b); // 1
console.log(c); // 1

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

console.log(a); // 抛出 ReferenceError(引用错误)
console.log(b); // 抛出 ReferenceError(引用错误)
console.log(c); // 抛出 ReferenceError(引用错误)

// 同样适用于 `const`


13.6 避免使用一元递增和递减运算符(++, --)。 eslint no-plusplus

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

// bad

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

let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) { let value = array[i]; sum += value; if (value) { truthyCount++; } } // good const array = [1, 2, 3]; let num = 1; num += 1; num -= 1; const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;

⬆ 返回目录

Hoisting


14.1 var 声明会被提升至他们作用域的顶部,但它们赋值不会提升。letconst 声明被赋予了一种称为「暂时性死区(Temporal Dead Zones, TDZ)」的概念。这对于了解为什么 type of 不再安全相当重要。

// 我们知道这样运行不了
// (假设没有 notDefined 全局变量)
function example() {
    console.log(notDefined); // => 抛出一个 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); // => 抛出一个 ReferenceError(引用错误)
    console.log(typeof declaredButNotAssigned); // => 抛出一个 ReferenceError(引用错误)
    const declaredButNotAssigned = true;
}


14.2 匿名函数表达式的变量名会被提升,但函数分配不会。

function example() {
    console.log(anonymous); // => undefined

    anonymous(); // => TypeError anonymous is not a function 输入错误,anonymous 不是一个函数

    var anonymous = function () {
    console.log('anonymous function expression');
    };
}


14.3 命名的函数表达式的变量名会被提升,但函数名和函数体并不会。

function example() {
    console.log(named); // => undefined

    named(); // => TypeError named is not a function,输入错误,named 不是一个函数

    superPower(); // => ReferenceError superPower is not defined, ReferenceError(引用错误)superPower 未定义

    var named = function superPower() {
    console.log('Flying');
    };
}

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

    named(); // => TypeError named is not a function,输入错误,named 不是一个函数

    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 抽象方法来强制求值它们的表达式,并始终遵循以下简单规则:

  • Objects 求值为 true
  • Undefined 求值为 false
  • Null 求值为 false
  • Booleans 求值为 布尔值
  • Numbers 如果是 +0、-0、或 NaN 求值为 false , 否则为 true
  • Strings 如果是空字符串 '' 求值为 false , 否则为 true
if ([0] && []) {
    // true
    // 一个数组 (即使是一个空数组) 是一个 object, objects 被求值为 true
}


15.3 对于布尔值使用简写,但对于字符串和数字使用显式比较。

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

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

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

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

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

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


15.4 想了解更多信息,参考 Angus Croll 的 Truth Equality and JavaScript


15.5casedefault 子句中,使用大括号来创建包含词法声明的语句块(例如 let, const, function, 和 class). eslint: no-case-declarations

为什么? 词法声明在整个 switch 语句块中都是可见的,但是只有在分配时才被初始化,这只有当它到达 case 时才会发生。这在多个 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 {}
    }
}


15.6 三元表达式不应该嵌套,通常写成单行表达式。 eslint: no-nested-ternary

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


15.7 避免不必要的三元表达式语句。 eslint: 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;


15.8 当运算符混合在一个语句中时,请将其放在括号内。混合算术运算符时,不要将 **%+-*/ 混合在一起。eslint: no-mixed-operators

为什么? 这可以提高可读性,并清晰展现开发者的意图。

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

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

// bad
if (a || b && c) {
    return 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;

⬆ 返回目录

代码块 Blocks


16.1 使用大括号包裹所有的多行代码块。 eslint: nonblock-statement-body-position

// bad
if (test)
    return false;

// good
if (test) return false;

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

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

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


16.2 如果通过 ifelse 使用多行代码块,把 else 放在 if 代码块闭合括号的同一行。eslint: brace-style jscs: disallowNewlineBeforeBlockStatements

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

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


16.3 如果一个 if 块总是执行一个 return 语句,后面的 else 块是不必要的。在 else if 块中的 return,可以分成多个 if 块来 return 。eslint: no-else-return

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

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

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

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

    return y;
}

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

    if (y) {
    return y;
    }
}

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

⬆ 返回目录

控制语句 Control Statements

  • 17.1 如果您的控制语句(if, while 的)太长或超过最大行长度,那么每个(分组)条件可以放单独一行。逻辑运算符应该放在每行起始处。

为什么? 在每行起始处要求运算符可以使运算符保持一致,并遵循与方法链式调用类似的模式。这样可以使复杂逻辑更易于查看,以提高可读性。

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

⬆ 返回目录

注释 Comments


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

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

    // ...

    return element;
}

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

    // ...

    return element;
}


18.2 单行注释使用 // 。将单行注释放在续注释的语句上方。在注释之前放置一个空行,除非它位于代码块的第一行。

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

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

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

    return type;
}

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

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

    return type;
}

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

    return type;
}


18.3 所有注释符和注释内容用一个空格隔开,让它更容易阅读。 eslint: spaced-comment

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

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

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

    // ...

    return element;
}

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

    // ...

    return element;
}


18.4 给注释增加 FIXMETODO 的前缀,可以帮助其他开发者快速了解这个是否是一个需要重新复查的问题,或是你正在为需要解决的问题提出解决方案。这将有别于常规注释,因为它们是可操作的。使用 FIXME -- need to figure this out 或者 TODO -- need to implement


18.5 使用 // FIXME: 来标识需要修正的问题。愚人码头注:如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明。

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

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


18.6 使用 // TODO: 来标识需要实现的问题。愚人码头注:如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会简略说明。

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

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

愚人码头注:还有 // XXX: 注释,如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明。部分 IDE 有这些注释的收集视图,例如任务(task)视图,TODO视图等,在项目发布前,检查一下任务视图是一个很好的习惯。

⬆ 返回目录

空白 Whitespace


19.1 使用 2 个空格作为缩进。 eslint: indent jscs: validateIndentation

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

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

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


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

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

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

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

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


19.3 在控制语句(ifwhile 等)的小括号前放一个空格。在函数调用及声明中,不在函数的参数列表前加空格。 eslint: keyword-spacing jscs: requireSpaceAfterKeywords

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

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

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

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


19.4 使用空格把运算符隔开。 eslint: space-infix-ops jscs: requireSpaceBeforeBinaryOperators, requireSpaceAfterBinaryOperators

// bad
const x=y+5;

// good
const x = y + 5;


19.5 在文件末尾插入一个空行。 eslint: eol-last

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


19.6 长方法链式调用时使用缩进(2个以上的方法链式调用)。使用一个点 . 开头,强调该行是一个方法调用,不是一个新的声明。eslint: newline-per-chained-call no-whitespace-before-property

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


19.7 在语句块后和下条语句前留一个空行。jscs: requirePaddingNewLinesAfterBlocks

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

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

return baz;

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

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

    bar() {
    },
};

return obj;

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

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

    function bar() {
    },
];

return arr;


19.8 不要用空行来填充块。 eslint: padded-blocks jscs: disallowPaddingNewlinesInBlocks

// bad
function bar() {

    console.log(foo);

}

// bad
if (baz) {

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

}

// bad
class Foo {

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

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

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


19.9 不要在圆括号内加空格。 eslint: space-in-parens jscs: disallowSpacesInsideParentheses

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

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

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

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


19.10 不要在中括号内添加空格。 eslint: array-bracket-spacing jscs: disallowSpacesInsideArrayBrackets

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

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


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

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

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


19.12 避免有超过100个字符(包括空格)的代码行。注意:根据上面的规则,长字符串可以免除这个规则,不应该被破坏。eslint: max-len jscs: maximumLineLength

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

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

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

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

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

⬆ 返回目录

逗号 Commas


20.1 行开头处不要实用使用逗号。 eslint: comma-style jscs: requireCommaBeforeLineBreak

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

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

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

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


20.2 添加结尾的逗号。 eslint: comma-dangle jscs: requireTrailingComma

为什么?这会让 git diff(差异比较) 更干净。另外,像Babel这样的转译器会删除转译后代码中的结尾逗号,这意味着您不必担心传统浏览器中的结尾逗号问题

// bad - 没有结尾逗号的 git diff 差异比较
const hero = {
        firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - 有结尾逗号的 git diff 差异比较
const hero = {
        firstName: 'Florence',
        lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};
// bad
const hero = {
    firstName: 'Dana',
    lastName: 'Scully'
};

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

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

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

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

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

// good (请注意,逗号不能出现在 “rest” 元素的后面)
function createHero(
    firstName,
    lastName,
    inventorOf,
    ...heroArgs
) {
    // does nothing
}

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

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

// good (请注意,逗号不能出现在 “rest” 元素的后面)
createHero(
    firstName,
    lastName,
    inventorOf,
    ...heroArgs
);

⬆ 返回目录

分号 Semicolons


21.1 当然要使用封号 eslint: semi jscs: requireSemicolons

为什么? 当 JavaScript 遇到没有分号的换行符时,它使用一组称为自动分号插入的规则来确定是否应该将换行符视为语句的结尾,并且(顾名思义)如果被这样认为的话,在换行符前面自动插入一个分号。ASI(自动分号插入)包含了一些稀奇古怪的的行为,不过,如果 JavaScript 错误地解释了你的换行符,你的代码将会被中断执行。随着新功能成为 JavaScript 的一部分,这些规则将变得更加复杂。明确地结束你的语句并配置你的 linter 来捕获缺少的分号,将有助于防止遇到问题。

// bad - 引发异常
const luke = {}
const leia = {}
[luke, leia].forEach(jedi => jedi.father = 'vader')

// bad - 引发异常
const reaction = "No! That's impossible!"
(async function meanwhileOnTheFalcon(){
    // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
    // ...
}())

// bad - 返回`undefined`,而不是下一行的值 - 当 `return` 独占一行时,自动分号插入总是会发生。
function foo() {
    return
    'search your feelings, you know it to be foo'
}

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

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

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

更多阅读.

⬆ 返回目录

类型转换 Type Casting & Coercion


22.1 在声明语句的开始处就执行强制类型转换.


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

// => this.reviewScore = 9;

// bad
const totalScore = new String(this.reviewScore); // typeof totalScore 是 "object" 而不是 "string"

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

// bad
const totalScore = this.reviewScore.toString(); // 不能保证返回一个字符串

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


22.3 数字: 使用 Number 进行转换,而 parseInt 则始终以基数解析字串。 eslint: radix no-new-wrappers

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 was the reason my code was slow.
    * Bitshifting the String to coerce it to a
    * Number made it a lot faster.
    */
const val = inputValue >> 0;


22.5 注意: 使用位运算请小心。 数字使用 64位值表示, 但是位运算只返回32位整数 (来源)。 小于32位整数的位运算会导致不可预期的行为. 讨论。最大的有符号整数是 2,147,483,647:

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


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

const age = 0;

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

// good
const hasAge = Boolean(age);

// best
const hasAge = !!age;

⬆ 返回目录

命名规则 Naming Conventions


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

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

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


23.2 当命名对象,函数和实例时使用驼峰式命名。 eslint: camelcase jscs: requireCamelCaseOrUpperCaseIdentifiers

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

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


23.3 当命名构造函数或类的时候使用 PascalCase 式命名,(愚人码头注:即单词首字母大写)。 eslint: new-cap jscs: requireCapitalizedConstructors

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

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

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

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


23.4 不要使用下划线开头或结尾。 eslint: no-underscore-dangle jscs: disallowDanglingUnderscores

为什么? JavaScript 对于属性或方法而言并没有私有的概念。虽然下划线开头通常意味着 ‘private’(私有)是通用的惯例,事实上,这些属性是完全公开的,是公开API的一部分。 这个惯例可能会导致开发人员错误地认为这不重要或者测试也不必要。简而言之:如果你想让其 “private”, 必须使其不可见。

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

// good
this.firstName = 'Panda';


23.5 不要存储 this 引用。请实用箭头函数或者 Function#bind。 jscs: disallowNodeTypes

// 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 basename 应与其默认导出的名称正好匹配。(愚人码头注:basename 指的是文件名)

// 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 单词首字母大写命名, filename 下划线命名
import forty_two from './forty_two'; // import/filename 下划线命名, export 驼峰式命名
import inside_directory from './inside_directory'; // import 下划线命名, export 驼峰式命名
import index from './inside_directory/index'; // 明确地 require 索引文件
import insideDirectory from './insideDirectory/index'; //  明确地 require 索引文件

// good
import CheckBox from './CheckBox'; // export/import/filename 单词首字母大写命名
import fortyTwo from './fortyTwo'; // export/import/filename 驼峰式命名
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js


23.7 当 导出(export) 一个默认函数时使用驼峰式命名。你的文件名应该和你的函数的名字一致。

function makeStyleGuide() {
    // ...
}

export default makeStyleGuide;


23.8 当导出一个 构造函数 / 类 / 单例 / 函数库 / 纯对象时使用 PascalCase 式命名,(愚人码头注:即单词首字母大写)。

const AirbnbStyleGuide = {
    es6: {
    },
};

export default AirbnbStyleGuide;


23.9 首字母缩写词应该总是全部大写,或全部小写。

为什么? 名字是更具可读性,而不是为了满足计算机算法。

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

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

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

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

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

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

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

⬆ 返回目录

存取器 Accessors


24.1 属性的存取器函数不是必须的。


24.2 別使用 JavaScript 的 getters/setters,因为它们会导致意想不到的副作用,而且很难测试,维护和理解。相反,如果要使用存取器函数,使用 getVal() 及 setVal(‘hello’)。

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

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

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

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


24.3 如果属性/方法是一个 boolean, 使用 isVal()hasVal() 方法。

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

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


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

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

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

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

⬆ 返回目录

事件 Events


25.1 将绑定数据到事件时 (不论是 DOM 事件还是其他像Backbone一类的事件), 传递 hash 而不是原始值。 这将允许后续的贡献者不用查找和更新事件的每一个处理程序就可以给事件添加更多的数据。例如,不要使用下边的:

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

// ...

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

prefer:

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

// ...

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

⬆ 返回目录

jQuery


26.1 jQuery 对象变量命名以 $ 为前缀。 jscs: requireDollarBeforejQueryAssignment

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

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

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


26.2 缓存 jQuery 选择器的查询结果。

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

    // ...

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

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

    // ...

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


26.3 DOM 查询使用后代选择器 $('.sidebar ul') 或者 父类 > 子类 $('.sidebar &gt; 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 兼容性 Compatibility


27.1 参考 Kangax 的 ES5 compatibility table.

⬆ 返回目录

ECMAScript 6+ (ES 2015+) 编码风格


28.1 这是一个各种 ES6+ 新特性的链接集合。

  1. Arrow Functions
  2. Classes
  3. Object Shorthand
  4. Object Concise
  5. Object Computed Properties
  6. Template Strings
  7. Destructuring
  8. Default Parameters
  9. Rest
  10. Array Spreads
  11. Let and Const
  12. Exponentiation Operator
  13. Iterators and Generators
  14. Modules


28.2 不要使用 TC39 proposals 还未实现的 stage3 的功能。

为什么?他们没有最终确定,他们可能会改变或完全撤回。我们想要使用JavaScript,而且建议性的提案还不是 JavaScript 。

⬆ 返回目录

标准库 Standard Library

标准库包含有问题,但由于历史遗留问题而保留下来的功能。


29.1 使用 Number.isNaN 代替全局 isNaN 。eslint: no-restricted-globals

为什么?全局的 isNaN 方法会将非数字转换为数字, 任何被转换为 NaN 的东西都会返回 true 。
如果需要这种行为,请明确使用。

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

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


29.2 使用 Number.isFinite 代替全局 isFinite 。eslint: no-restricted-globals

为什么?全局的 isFinite 方法会将非数字转换为数字, 任何被转换为有限大的数字都会返回 true 。
如果需要这种行为,请明确使用。

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

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

⬆ 返回目录

测试


30.1 是的。

function foo() {
    return true;
}


30.2 不要这么做,很严重:
– 不论你用哪一个测试框架,都应该写测试用例!
– 尽力写一些简单的纯函数, 并尽量减可变性发生的地方。
– 谨慎的使用 stubs 和 mocks – 它们会使测试变得脆弱.
– 我们主要在 Airbnb 中主要使用 mochatape 也偶尔会用于小型独立模块。
– 100%的测试覆盖率是很好的追求目标,即使它并不总是实际可行的。
– 每当你修复一个 bug ,写一个回归测试。 未经回归测试的bug修复几乎会在将在再次出现.

⬆ 返回目录

性能 Performance

⬆ 返回目录

资源 Resources

学习 ES6+

阅读这个

工具

其他编码风格指南

其他风格

了解更多

书籍

博客

播客

⬆ 返回目录

正文完。下面还有一个推广让最好的人才遇见更好的机会!

互联网行业的年轻人,他们面对着怎样的职业瓶颈、困惑与未来选择?过去,这鲜有人关心。资深的职场人,也多半优先选择熟人去推荐机会。

100offer致力于改变现状,帮互联网行业最好的人才发现更好的机会。使用 100offer.com 或 100offer App ,可以一周内获得中国、美国等数千家优质企业的工作机会。

马上去遇见更好的机会
推广结束

发表评论

电子邮件地址不会被公开。 必填项已用*标注