0%

Airbnb JavaScript Style Guide (3)

A mostly reasonable approach to JavaScript
一个最讲道理的JavaScript方法论

Commas 逗号

  • [20.1] Leading commas: Nope. eslint: comma-style jscs: requireCommaBeforeLineBreak
    一定不要前导逗号。

    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
    27
    28
    29
    // 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] Additional trailing comma: Yup. eslint: comma-dangle jscs: requireTrailingComma
    额外的尾拖逗号,是的。

    Why? This leads to cleaner git diffs. Also, transpilers like Babel will remove the additional trailing comma in the transpiled code which means you don’t have to worry about the trailing comma problem in legacy browsers.
    为什么?这可以让 git diffs 更加清楚。另外,编译器如 Babel 会移除多于的尾拖逗号,你不用担心古老浏览器的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // bad - git diff without trailing comma
    const hero = {
    firstName: 'Florence',
    - lastName: 'Nightingale'
    + lastName: 'Nightingale',
    + inventorOf: ['coxcomb chart', 'modern nursing']
    };

    // good - git diff with trailing comma
    const hero = {
    firstName: 'Florence',
    lastName: 'Nightingale',
    + inventorOf: ['coxcomb chart', 'modern nursing'],
    };
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    // bad
    const hero = {
    firstName: 'Dana',
    lastName: 'Scully'
    };

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

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

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

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

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

    // good (note that a comma must not appear after a "rest" element)
    // 注意逗号不应该出现在剩余参数里。
    function createHero(
    firstName,
    lastName,
    inventorOf,
    ...heroArgs
    ) {
    // does nothing
    }

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

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

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

Semicolons 分号

  • [21.1] Yup. eslint: semi jscs: requireSemicolons
    需要。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // bad
    (function () {
    const name = 'Skywalker'
    return name
    })()

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

    // good, but legacy (guards against the function becoming an argument when two files with IIFEs are concatenated)
    // 好,但是是遗产,保证函数不会变成一个参数,当两个文件立即调用函数的时候。
    ;((() => {
    const name = 'Skywalker';
    return name;
    })());

    Read more.

Type Casting & Coercion 类型适配&强制分配

  • [22.1] Perform type coercion at the beginning of the statement.
    执行强制类型在句子一开始的地方

  • [22.2] Strings:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // => this.reviewScore = 9;

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

    // bad
    const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string 不保证返回字符串

    // good
    const totalScore = String(this.reviewScore);
  • [22.3] Numbers: Use Number for type casting and parseInt always with a radix for parsing strings. eslint: radix
    数字:使用Number来做类型适配,使用parseInt带上进制来做字符串的转换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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] If for whatever reason you are doing something wild and parseInt is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you’re doing.
    无论什么原因,parseInt 成为你的瓶颈,出于性能考虑你需要用位运算,记得留一段注释解释你 TM 干了啥。

    1
    2
    3
    4
    5
    6
    7
    // 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 Note: Be careful when using bitshift operations. Numbers are represented as 64-bit values, but bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:
    使用位运算的时候需要十分小心。数字表示成64bit 的值,但是位运算总是返回一个32bit 的整数,大于32bit 的数字位运算可能导致预期之外的行为。最大的32进制整数是 2,147,483,647:

    1
    2
    3
    2147483647 >> 0; // => 2147483647
    2147483648 >> 0; // => -2147483648
    2147483649 >> 0; // => -2147483647
  • [22.6] Booleans:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const age = 0;

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

    // good
    const hasAge = Boolean(age);

    // best
    const hasAge = !!age;

Naming Conventions 命名约定

  • [23.1] Avoid single letter names. Be descriptive with your naming. eslint: id-length
    避免单个字母的名字。名字应该有描述性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    function q() {
    // ...
    }

    // good
    function query() {
    // ...
    }
  • [23.2] Use camelCase when naming objects, functions, and instances. eslint: camelcase jscs: requireCamelCaseOrUpperCaseIdentifiers
    使用小驼峰来命名对象、函数、实例。

    1
    2
    3
    4
    5
    6
    7
    8
    // bad
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    function c() {}

    // good
    const thisIsMyObject = {};
    function thisIsMyFunction() {}
  • [23.3] Use PascalCase only when naming constructors or classes. eslint: new-cap jscs: requireCapitalizedConstructors
    只有命名类和构造器的时候使用大驼峰。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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] Do not use trailing or leading underscores. eslint: no-underscore-dangle jscs: disallowDanglingUnderscores
    不要用前导下划线。

    Why? JavaScript does not have the concept of privacy in terms of properties or methods. Although a leading underscore is a common convention to mean “private”, in fact, these properties are fully public, and as such, are part of your public API contract. This convention might lead developers to wrongly think that a change won’t count as breaking, or that tests aren’t needed. tl;dr: if you want something to be “private”, it must not be observably present.
    为什么?JavaScript 没有私有属性或者方法的概念。尽管前导下划线是一个常用来表达私有的约定,事实上,这些属性全是公开的,是你的 api 协议的一部分。这个约定可能导致开发者错误认为改变一下没关系,或者不需要测试。如果你需要什么真的私有的东西,它必须不显著的出现。

    1
    2
    3
    4
    5
    6
    7
    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    this._firstName = 'Panda';

    // good
    this.firstName = 'Panda';
  • [23.5] Don’t save references to this. Use arrow functions or Function#bind. jscs: disallowNodeTypes
    不要保存指向 this 的引用。使用箭头函数或者 Function.bind

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 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] A base filename should exactly match the name of its default export.
    一个级别的文件名应该精确的和默认导入类同名。

    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
    27
    28
    29
    30
    // file 1 contents
    class CheckBox {
    // ...
    }
    export default CheckBox;

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

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

    // in some other file
    // bad
    import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
    import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
    import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export

    // bad
    import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
    import forty_two from './forty_two'; // snake_case import/filename, camelCase export
    import inside_directory from './inside_directory'; // snake_case import, camelCase export
    import index from './inside_directory/index'; // requiring the index file explicitly
    import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly

    // good
    import CheckBox from './CheckBox'; // PascalCase export/import/filename
    import fortyTwo from './fortyTwo'; // camelCase export/import/filename
    import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
    // ^ supports both insideDirectory.js and insideDirectory/index.js
  • [23.7] Use camelCase when you export-default a function. Your filename should be identical to your function’s name.
    需要导出默认函数的时候使用小驼峰。你的名字应该作为你函数的标识名。

    1
    2
    3
    4
    5
    function makeStyleGuide() {
    // ...
    }

    export default makeStyleGuide;
  • [23.8] Use PascalCase when you export a constructor / class / singleton / function library / bare object.
    导出构造器、类、单例、函数库、空对象的时候,使用大驼峰。

    1
    2
    3
    4
    5
    6
    const AirbnbStyleGuide = {
    es6: {
    },
    };

    export default AirbnbStyleGuide;
  • [23.9] Acronyms and initialisms should always be all capitalized, or all lowercased.
    首字母缩写需要全部大写或者全部小写。

    Why? Names are for readability, not to appease a computer algorithm.
    为什么?命名是为了可读性,而不是为了满足算法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // bad
    import SmsContainer from './containers/SmsContainer';

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

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

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

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

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

Accessors 存取器

  • [24.1] Accessor functions for properties are not required.
    属性存取函数没必要。

  • [24.2] Do not use JavaScript getters/setters as they cause unexpected side effects and are harder to test, maintain, and reason about. Instead, if you do make accessor functions, use getVal() and setVal(‘hello’).
    不要用 JavaScript 的getters/setters,它们可能产生难以测试、修理、推断的副作用。用 getVal() 和 setVal(‘hello’) 来做一个存储器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // bad
    class Dragon {
    get age() {
    // ...
    }

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

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

    setAge(value) {
    // ...
    }
    }
  • [24.3] If the property/method is a boolean, use isVal() or hasVal().
    如果属性或者方法是一个布尔值,使用 isVal()hasVal()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    if (!dragon.age()) {
    return false;
    }

    // good
    if (!dragon.hasAge()) {
    return false;
    }
  • [24.4] It’s okay to create get() and set() functions, but be consistent.
    用 get set 也没关系,但是请保持一致性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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] When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event. For example, instead of:
    当荷载带着数据到event之后。传递了一个哈希值而不是原始值。这允许后来的贡献者加上更多的数据到荷载里,同时不需要找到更新每一个处理者。

    1
    2
    3
    4
    5
    6
    7
    8
    // bad
    $(this).trigger('listingUpdated', listing.id);

    // ...

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

    prefer:

    1
    2
    3
    4
    5
    6
    7
    8
    // good
    $(this).trigger('listingUpdated', { listingId: listing.id });

    // ...

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

j Query

  • [26.1] Prefix jQuery object variables with a $. jscs: requireDollarBeforejQueryAssignment
    jQuery对象用美元符号开头更好。

    1
    2
    3
    4
    5
    6
    7
    8
    // bad
    const sidebar = $('.sidebar');

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

    // good
    const $sidebarBtn = $('.sidebar-btn');
  • [26.2] Cache jQuery lookups.
    缓存查找器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 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] For DOM queries use Cascading $('.sidebar ul') or parent > child $('.sidebar > ul'). jsPerf
    dom 查询使用串联之类的选择器。

  • [26.4] Use find with scoped jQuery object queries.
    已经选了对象查询的 jQuery 使用 find 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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 ES5兼容性

ECMAScript 6+ (ES 2015+) Styles

  • 28.2 Do not use TC39 proposals that have not reached stage 3.

    Why? They are not finalized, and they are subject to change or to be withdrawn entirely. We want to use JavaScript, and proposals are not JavaScript yet.

Testing

  • [29.1] Yup.

    1
    2
    3
    function foo() {
    return true;
    }
  • [29.2] No, but seriously:

    • Whichever testing framework you use, you should be writing tests!
    • 不管用什么框架,都应该写测试。
    • Strive to write many small pure functions, and minimize where mutations occur.
    • 努力写很多小的纯函数,最小化变化。
    • Be cautious about stubs and mocks - they can make your tests more brittle.
    • 小心使用存档和原型,让测试十分易碎。
    • We primarily use mocha at Airbnb. tape is also used occasionally for small, separate modules.
    • Airbnb 的框架 mocha
    • 100% test coverage is a good goal to strive for, even if it’s not always practical to reach it.
    • 追求100%的测试率是个好目标,但有时候难以实现。
    • Whenever you fix a bug, write a regression test. A bug fixed without a regression test is almost certainly going to break again in the future.
    • 无论什么时候修了一个 bug,应该加一个回归测试,没有回归测试的 bug fix 有很大概率将来又坏掉。

Performance 性能

Resources 资源

Learning ES6

Read This

Tools

Other Style Guides

Other Styles

Further Reading

Books

Blogs

Podcasts