Skip to content

l-world/airbnb-javascript-style-guide-cn

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Airbnb JavaScript编码规范指南(ES6)中文版

个人翻译,一些个人见解会用"注:"的形式标出

原版:Airbnb JavaScript Style Guide

最合理的方法书写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. jQuery
  26. ECMAScript 5 兼容性
  27. ECMAScript 6+ (ES 2015+) 风格
  28. 标准库
  29. 测试
  30. 性能
  31. 资源
  32. 谁在使用?
  33. 翻译
  34. JavaScript编码规范指南
  35. 和我们聊聊JavaScript
  36. 贡献者
  37. 许可
  38. 修改

类型

  • 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

⬆ back to top

引用

  • 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, use the let.
    let count = 1;
    if (true) {
      count += 1;
    }

  • 2.3 注意letconst都是块级作用域.

    // const and let only exist in the blocks they are defined in.
    {
      let a = 1;
      const b = 1;
    }
    console.log(a); // ReferenceError
    console.log(b); // ReferenceError

⬆ back to top

对象

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

    // bad
    const item = new Object();
    
    // good
    const item = {};

  • 3.2 创建动态属性名字的对象时使用计算后的属性名.

    Why? 原因:允许你在一个地方定义一个对象的全部属性.

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

原因:通常我们主观上认为更易读.有利于语法高亮,更容易被许多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, propertyIsEnumerableisPrototypeOf方法. eslint: no-prototype-builtins

原因:这些方法可能会被对象自身的同名属性覆盖 - 比如{ 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; // cache the lookup once, in module scope.
/* or */
import has from 'has'; // https://www.npmjs.com/package/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 }); // this mutates `original` ಠ_ಠ
    delete copy.a; // so does this
    
    // bad
    const original = { a: 1, b: 2 };
    const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
    
    // good
    const original = { a: 1, b: 2 };
    const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
    
    const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

⬆ back to top

数组

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

  • 4.4 将可迭代对象转换成数组使用扩展运算符...而不是Array.from.

    const foo = document.querySelectorAll('.foo');
    
    // good
     const nodes = Array.from(foo);
    
    // best
    const nodes = [...foo];

  • 4.5 使用 Array.from 方法将类数组对象转化成数组.

    const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
    
    // bad
    const arr = Array.prototype.slice.call(arrLike);
    
    // good
    const arr = Array.from(arrLike);

  • 4.6 遍历迭代器进行映射时使用 Array.from 代替扩展运算符 ..., 因为这可以避免创建中间数组.

    // bad
    const baz = [...foo].map(bar);
    
    // good
    const baz = Array.from(foo, bar);

  • 4.7 在数组方法的回调函数中使用return.如果函数体只有一条返回没有副作用的声明则可以省略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 - no returned value means `acc` becomes undefined after the first iteration
    [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
      const flatten = acc.concat(item);
      acc[index] = flatten;
    });
    
    // good
    [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
      const flatten = acc.concat(item);
      acc[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.8 如果一个数组有多行则要在数组的开括号后和闭括号前使用新行

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

⬆ back to top

解构

  • 5.1 访问对象的多个属性时使用对象解构. eslint: prefer-destructuring

    原因:解构可以避免创建属性的临时引用.

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

    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) {
      // then a miracle occurs
      return [left, right, top, bottom];
    }
    
    // the caller needs to think about the order of return data
    const [left, __, top] = processInput(input);
    
    // good
    function processInput(input) {
      // then a miracle occurs
      return { left, right, top, bottom };
    }
    
    // the caller selects only the data they need
    const { left, top } = processInput(input);

⬆ back to top

字符串

  • 6.1 字符串使用单引号 '' . eslint: quotes

    // bad
    const name = "Capt. Janeway";
    
    // bad - template literals should contain interpolation or newlines
    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

    原因:模板字符串有可读性强,语法明确,换行合理和字符串插值的特点.

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

⬆ back to top

函数

  • 7.1 使用具名函数表达式而非函数声明. eslint: func-style

    原因:函数声明被提升了,意味着很容易在文件中定义此函数之前引用它。不利于可读性和可维护性.如果你发现函数定义既庞大又复杂以至于不能理解文件的其他部分,或许你应该将它拆分成模块。别忘记要显式命名表达式,不用管名字是否是从包含的变量中推断的(通常出现在使用Babel编译器的现代浏览器中).这样会消除Error调用栈中的猜想. (讨论)

    // bad
    function foo() {
      // ...
    }
    
    // bad
    const foo = function () {
      // ...
    };
    
    // good
    // lexical name distinguished from the variable-referenced invocation(s)
    const short = function longUniqueMoreDescriptiveLexicalFoo() {
      // ...
    };

  • 7.2 用圆括号包裹自执行匿名函数. eslint: wrap-iife

    原因:一个立即执行匿名函数表达式是一个单一的单元-括号清楚地表达了它包含函数自身和调用括号的意思.注意,在到处都是模块的世界中几乎不需要IIFE.

    // immediately-invoked function expression (IIFE)
    (function () {
      console.log('Welcome to the Internet. Please follow me.');
    }());

  • 7.3 不要在非函数块(if , while 等)中声明函数.要将函数赋给变量.浏览器允许你这么做,但是处理方式不同,这不是好消息. eslint: no-loop-func

  • 7.4 注意: ECMA-262定义了block作为一组语句的块. 函数声明不是语句. Read ECMA-262's note on this issue.

    // 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, 使用其余运算符 ... . eslint: prefer-rest-params

    原因:...是你想获得的参数列表,因此,扩展语法的参数是真正的数组,而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) {
      // No! We shouldn't mutate function arguments.
      // Double bad: if opts is falsy it'll be set to an object which may
      // be what you want but it can introduce subtle bugs.
      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

    原因:显然你无需使用上下文,很难结合newapply.

    // 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 多行签名或调用的函数应该像此指南中其他多行列表一样被缩进:每一个独占一行,最后一个加一个逗号. eslint: function-paren-newline

    // bad
    function foo(bar,
                 baz,
                 quux) {
      // ...
    }
    
    // good
    function foo(
      bar,
      baz,
      quux,
    ) {
      // ...
    }
    
    // bad
    console.log(foo,
      bar,
      baz);
    
    // good
    console.log(
      foo,
      bar,
      baz,
    );

⬆ back to top

箭头函数

  • 8.1 当你必须使用函数表达式(传递匿名函数)时,使用箭头函数标记. eslint: prefer-arrow-callback, arrow-spacing jscs: requireArrowFunctions

    原因:这会创建一个能在其中使用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;
    });

  • 8.2 如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的return. 否则保留花括号并使用return语句. eslint: arrow-parens, arrow-body-style

    原因:语法糖.当进行多个函数的链式操作时可读性好.

    // 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 函数如果只接收一个参数并且没使用用花括号,则省略圆括号. 否则为了清晰明确总是使用圆括号包裹参数.注意:总是使用圆括号也是可以接受的,eslint中的"always"选项.eslint: arrow-parens

    原因:看上去不混乱.

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

  • 8.5 避免把箭头函数语法 (=>) 和比较运算符 (<=, >=)弄混. 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;
    };

  • 8.6 注意有隐式返回函数体的箭头函数的位置. eslint: implicit-arrow-linebreak

    // bad
    (foo) =>
      bar;
    
    (foo) =>
      (bar);
    
    // good
    (foo) => bar;
    (foo) => (bar);
    (foo) => (
       bar
    )

⬆ back to top

类&构造函数

  • 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 如果未声明构造函数,则类会有一个默认的构造函数. 没必要用空的构造函数或者将其委托给父类. 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

    原因:重复的类成员声明会默认使用最后声明的-有重复值基本就是bug.

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

⬆ back to top

模块

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

    原因:这会确保你只有一个默认的export.

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

  • 10.3 不要直接export从import中导入的模块.

    原因:很显然有一个明确的方法来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';
    // … some other 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

    原因:通常来说可变应该被避免,尤其是在export绑定值的时候.通常来说,这个技术只在常量引用被导出的时候使用.

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

  • 10.6 在单一export的模块中,建议export default而不是具名export. eslint: import/prefer-default-export

    原因:建议更多的文件只有一个export出口.有利于维护和阅读.

    // bad
    export function foo() {}
    
    // good
    export default function foo() {}

  • 10.7 将所有的import放在非import语句前. eslint: import/imports-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 多行import应该像多行数组和对象字面量一样被缩进.

    原因:花括号遵循此规范中其他花括号块同样的缩进规则,同样要加尾逗号.

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

⬆ back to top

迭代器和生成器

  • 11.1 不要使用迭代器.建议使用JS更高优先级的函数代替for-infor-of循环. eslint: no-iterator no-restricted-syntax

    原因:这会强化不可变的规则.使用带返回值的纯函数很容易消除副作用.

    使用 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;
    
    // 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 现在不要使用生成器.

    原因:不能很好地翻译为ES5.

  • 11.3 如果你非得使用生成器或者无视 建议, 请确保函数签名中的空格位置正确. 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* () {
      // ...
    };

⬆ back to top

属性

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

⬆ back to top

变量

  • 13.1 总是使用 const 或者 let 声明变量. 不这么做会导致全局变量问题. 要避免污染全局命名空间. Captain Plannet警告过我们.eslint: no-undef prefer-const

    // bad
    superPower = new SuperPower();
    
    // good
    const superPower = new SuperPower();

  • 13.2 每个变量的声明或赋值都要使用const或者let. eslint: one-var

    原因:这种方式更易添加变量声明,不用考虑将;变成,.用debugger逐个调试也很方便,而不是一次跳过所有变量.

    // bad
    const items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
    
    // bad
    // (compare to above, and try to spot the mistake)
    const items = getItems(),
        goSportsTeam = true;
        dragonball = 'z';
    
    // good
    const items = getItems();
    const goSportsTeam = true;
    const dragonball = 'z';

  • 13.3constlet 分别放到一起.

    原因:当需要给依赖前一个已经赋值的变量的变量赋值时很有用.

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

  • 13.5 变量不要进行链式赋值.

    原因:变量链式赋值会创建隐藏的全局变量.

    // bad
    (function example() {
      // JavaScript interprets this as
      // let a = ( b = ( c = 1 ) );
      // The let keyword only applies to variable a; variables b and c become
      // global variables.
      let a = b = c = 1;
    }());
    
    console.log(a); // throws ReferenceError
    console.log(b); // 1
    console.log(c); // 1
    
    // good
    (function example() {
      let a = 1;
      let b = a;
      let c = a;
    }());
    
    console.log(a); // throws ReferenceError
    console.log(b); // throws ReferenceError
    console.log(c); // throws ReferenceError
    
    // the same applies for `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;

  • 13.7 避免在赋值中的=前后换行. 如果要换行的话使用圆括号包裹 max-len. eslint operator-linebreak.

    原因? =周围的换行会混淆赋值.

    // bad
    const foo =
      superLongLongLongLongLongLongLongLongFunctionName();
    
    // bad
    const foo
      = 'superLongLongLongLongLongLongLongLongString';
    
    // good
    const foo = (
      superLongLongLongLongLongLongLongLongFunctionName()
    );
    
    // good
    const foo = 'superLongLongLongLongLongLongLongLongString';

  • 13.8 不允许出现未被使用的变量. eslint: no-unused-vars

    原因:声明但未被使用的变量通常是不完全重构犯下的错误.这种变量在代码里浪费空间并会给读者造成困扰.

    // bad
    
    var some_unused_var = 42;
    
    // Write-only variables are not considered as used.
    var y = 10;
    y = 5;
    
    // A read for a modification of itself is not considered as used.
    var z = 0;
    z = z + 1;
    
    // Unused function arguments.
    function getX(x, y) {
        return x;
    }
    
    // good
    
    function getXPlusY(x, y) {
      return x + y;
    }
    
    var x = 1;
    var y = a + 2;
    
    alert(getXPlusY(x, y));
    
    // 'type' is ignored even if unused because it has a rest property sibling.
    // This is a form of extracting an object that omits the specified keys.
    var { type, ...coords } = data;
    // 'coords' is now the 'data' object without its 'type' property.

⬆ back to top

变量提升

  • 14.1 var声明会被提升到最近的闭合函数作用域的顶部, 但是赋值不会. constlet 声明有一个新的概念叫 临时死区 (TDZ). 了解 typeof不再安全很重要.

    // we know this wouldn't work (assuming there
    // is no notDefined global variable)
    function example() {
      console.log(notDefined); // => throws a ReferenceError
    }
    
    // creating a variable declaration after you
    // reference the variable will work due to
    // variable hoisting. Note: the assignment
    // value of `true` is not hoisted.
    function example() {
      console.log(declaredButNotAssigned); // => undefined
      var declaredButNotAssigned = true;
    }
    
    // the interpreter is hoisting the variable
    // declaration to the top of the scope,
    // which means our example could be rewritten as:
    function example() {
      let declaredButNotAssigned;
      console.log(declaredButNotAssigned); // => undefined
      declaredButNotAssigned = true;
    }
    
    // using const and let
    function example() {
      console.log(declaredButNotAssigned); // => throws a ReferenceError
      console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
      const declaredButNotAssigned = true;
    }

  • 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');
      };
    }
    
    // the same is true when the function name
    // is the same as the variable name.
    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');
      }
    }
  • 更多信息请参考 JavaScript Scoping & Hoisting by Ben Cherry.

⬆ back to top

比较运算符&相等

  • 15.1 使用 ===!==而非 ==!=. eslint: eqeqeq

  • 15.2 条件声明例如 if 会用 ToBoolean 这个抽象方法将表达式转成布尔值并遵循如下规则:

    • Objects 等于 true
    • Undefined 等于 false
    • Null 等于 false
    • Booleans 等于 布尔值
    • Numbers+0, -0, or NaN的情况下等于false, 其他情况是 true
    • Strings''时等于 false , 否则是 true
    if ([0] && []) {
      // true
      // 数组(即使是空数组)也是对象,对象等于true
    }

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

    // bad
    if (isValid === true) {
      // ...
    }
    
    // good
    if (isValid) {
      // ...
    }
    
    // bad
    if (name) {
      // ...
    }
    
    // good
    if (name !== '') {
      // ...stuff...
    }
    
    // bad
    if (collection.length) {
      // ...
    }
    
    // good
    if (collection.length > 0) {
      // ...
    }

  • 15.5 在用 casedefault 语句创建的包含有如下词语(如 let, const, functionclass)的代码块中使用花括号 .

原因:这些词语在整个switch中可见但是仅仅在当执行到case时被赋值的时候初始化.当多条case语句试图定义同一个变量时会导致问题.

eslint rules: no-case-declarations.

```javascript
// 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;
    
    // 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.7 避免不必要的三元运算符.

    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;

  • 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
    // one may be confused into thinking (a || b) && c
    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;

⬆ back to top

  • 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

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

  • 16.3 如果 if 块总是执行 return 声明, 随后的 else 块是不必要的. 跟随if块的else if块中包含的return可以被拆成多个if块. 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;
      }
    }

⬆ back to top

控制语句

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

  • 17.2 不要使用短路运算符代替控制语句.

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

⬆ back to top

注释

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

    // bad
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param {String} tag
    // @return {Element} element
    function make(tag) {
    
      // ...stuff...
    
      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 如果想表明这个问题以后会再次被查看或者给出了这个问题的解决方案,在注释前加上前缀FIXME或者TODO会有利于其他开发者很快理解问题. 这些不同于一般的注释因为它们是可操作的. 操作是 FIXME: -- need to figure this outTODO: -- 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;
      }
    }

⬆ back to top

空格

  • 19.1 将软tab设置为两个空格. eslint: indent

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

  • 19.2 在起始花括号前加一个空格. eslint: space-before-blocks

    // 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 在控制语句的圆括号前加一个空格 (if, while 等). 在函数调用和声明中在参数列表和函数名间不要放置空格. eslint: keyword-spacing

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

    // 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 进行长的函数链式调用时使用缩进(超过两个方法的链式调用).为了强调该行是方法调用而不是新的语句声明要用.开头. 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 在代码块结束和下一个声明前留一个空行.

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

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

  • 19.9 圆括号中不要添加空格. eslint: space-in-parens

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

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

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

  • 19.12 避免一行代码超过100个字符(包括空格). 注意: above,不包括整体不应该被分割的长字符串. eslint: max-len

    原因:确保可读性和可维护性.

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

  • 19.13 在同一行中开放代码块的内部开始和结束的部分空格数保持一致. 此规则亦适用于单行的闭合代码块. eslint: block-spacing

    // bad
    function foo() {return true;}
    if (foo) { bar = 0;}
    
    // good
    function foo() { return true; }
    if (foo) { bar = 0; }

  • 19.14 逗号前不要加空格,逗号后要加空格. eslint: comma-spacing

    // bad
    var foo = 1,bar = 2;
    var arr = [1 , 2];
    
    // good
    var foo = 1, bar = 2;
    var arr = [1, 2];

  • 19.15 计算属性内不要使用空格. eslint: computed-property-spacing

    // bad
    obj[foo ]
    obj[ 'foo']
    var x = {[ b ]: a}
    obj[foo[ bar ]]
    
    // good
    obj[foo]
    obj['foo']
    var x = { [b]: a }
    obj[foo[bar]]

  • 19.16 在函数名和调用间不要加空格. eslint: func-call-spacing

    // bad
    func ();
    
    func
    ();
    
    // good
    func();

  • 19.17 对象字面量属性的键和值之间要强制使用空格. eslint: key-spacing

    // bad
    var obj = { "foo" : 42 };
    var obj2 = { "foo":42 };
    
    // good
    var obj = { "foo": 42 };

  • 19.19 避免多个空行,只允许文件末尾出现一个新行. eslint: no-multiple-empty-lines

    // bad
    var x = 1;
    
    
    
    var y = 2;
    
    // good
    var x = 1;
    
    var y = 2;

⬆ back to top

逗号

  • 20.1 不要用,作为行的开始 eslint: comma-style

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

    原因:会让git diff更干净. 你不必担心老式浏览器的问题 结尾逗号问题因为Babel转译器会在转译后的代码中移除额外的逗号.

    // 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'],
    };
    
    // 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)
    function createHero(
      firstName,
      lastName,
      inventorOf,
      ...heroArgs
    );

⬆ back to top

分号

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

    原因: 当JavaScript不使用分号遇到分行符时,会使用一系列称作分号自动插入(ASI) 的规则来决定其是否应该将分行符视作声明的结尾, (正如名字所暗示的) 如果必要的话会在分行符前插入一个分号. ASI拥有几个古怪的行为, 如果JavaScript错误地解释了换行符你的代码会收到破坏. 这些规则会随着新的特性成为JavaScript的一部分而变得越来越复杂. 显式地结束声明语句并且配置好校验器来捕获缺失的分号会帮你避免遇到这些问题.

    // bad - raises exception
    const luke = {}
    const leia = {}
    [luke, leia].forEach(jedi => jedi.father = 'vader')
    
    // bad - raises exception
    const reaction = "No! That's impossible!"
    (async function meanwhileOnTheFalcon(){
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }())
    
    // bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI!
    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';
    }

    更多.

⬆ back to top

类型转换

  • 22.1 在声明的开始部分进行类型强转.

  • 22.2 字符串: no-new-wrappers

    // => this.reviewScore = 9;
    
    // bad
    const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"
    
    // bad
    const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()
    
    // bad
    const totalScore = this.reviewScore.toString(); // isn't guaranteed to return a string
    
    // 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 布尔值:

    const age = 0;
    
    // bad
    const hasAge = new Boolean(age);
    
    // good
    const hasAge = Boolean(age);
    
    // best
    const hasAge = !!age;

⬆ back to top

命名规则

  • 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 仅当命名构造函数或类的时候使用帕斯卡风格. eslint: new-cap

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

    原因:JavaScript对于属性和方法并没有隐私的概念.尽管下划线开头通常意味着'private', 事实上这些属性是完全公开的,是公开API的一部分. 这种风格可能导致开发者错误地认为这不重要或者测试也不必要. 也就是说: 如果你想让其 “private”, 必须使其不可见.

    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    this._firstName = 'Panda';
    
    // good
    this.firstName = 'Panda';
    
    // good, in environments where WeakMaps are available
    // see https://kangax.github.io/compat-table/es6/#test-WeakMap
    const firstNames = new WeakMap();
    firstNames.set(this, 'Panda');

  • 23.5 不要保存指向 this的引用. 使用箭头函数或 函数的#bind.

    // bad
    function foo() {
      const self = this;
      return function () {
        console.log(self);
      };
    }
    
    // bad
    function foo() {
      const that = this;
      return function () {
        console.log(that);
      };
    }
    
    // good
    function foo() {
      return () => {
        console.log(this);
      };
    }

  • 23.6 基本的文件名应该和默认export的名字刚好匹配.

    // 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 当默认导出一个函数时使用驼峰风格. 文件名要和函数名相同.

    function makeStyleGuide() {
    }
    
    export default makeStyleGuide;

  • 23.8 当导出 构造函数 / 类 / 单例 / 函数库 / 对象时使用帕斯卡风格.

    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 = [
      // ...
    ];
    
    // best
    import TextMessageContainer from './containers/TextMessageContainer';
    
    // best
    const Requests = [
      // ...
    ];

  • 23.10 可以在以下情况把一个常量全部大写(1) 被导出, (2) const声明的 (不能被重新赋值), (3) 开发者r认为该变量(内部嵌套的属性)不会改变.

    原因? 这对于开发者不确定变量是否会改变的情形下的额外的辅助工具. UPPERCASE_VARIABLES 能让开发者明确该变量(和它的属性)无法改变.

    • 所有用const 声明的变量呢? - 没必要,所以一个文件内的常量没必要用大写. 然而在导出常量的时候应该使用大写.
    • 导出的对象呢? - 只对导出对象的顶层使用大写(如 EXPORTED_OBJECT.key) 保证嵌套的属性都不会改变.
    // bad
    const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file';
    
    // bad
    export const THING_TO_BE_CHANGED = 'should obviously not be uppercased';
    
    // bad
    export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables';
    
    // ---
    
    // allowed but does not supply semantic value
    export const apiKey = 'SOMEKEY';
    
    // better in most cases
    export const API_KEY = 'SOMEKEY';
    
    // ---
    
    // bad - unnecessarily uppercases key while adding no semantic value
    export const MAPPING = {
      KEY: 'value'
    };
    
    // good
    export const MAPPING = {
      key: 'value'
    };

⬆ back to top

存取器

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

⬆ back to top

事件

  • 25.1 当将数据和事件绑定时 (不论是 DOM 事件还是其他像Backbone一类的事件), 传递对象字面量(也叫摘要值)而不是原始值. 这会允许接下来的修改者不用查找和更新事件的每一个处理器就可以给事件添加更多的数据,不要使用下边的:

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

    建议:

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

⬆ back to top

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

⬆ back to top

ECMAScript5兼容性

⬆ back to top

ECMAScript6+(ES2015+)风格

  • 28.1 这是不同ES6特性的链接集合.
  1. 箭头函数
  2. 对象简写
  3. Object Concise
  4. 对象计算属性
  5. 模板字符串
  6. 解构
  7. 默认参数
  8. 剩余运算符
  9. 数组展开
  10. Let 和 Const
  11. 求幂运算符
  12. 迭代器和生成器
  13. 模块

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

    原因:未最终确定, and they are subject to change or to be withdrawn entirely会随着改变及整个废弃而被影响. 我们想要使用JavaScript,而草案还不是JavaScript.

⬆ back to top

标准库

标准库包含功能有问题但由于遗留原因保留下来的功能.

  • 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

⬆ back to top

测试

  • 30.1 当然需要

    function foo() {
      return true;
    }

  • 30.2 不要这么做,很严重:
  • 不论你用哪一个测试框架,都应该写测试用例!
  • 尽力写一些简洁的函数, 使改动数据的部分最小化.
  • 小心使用 stubs 和 mocks - 它们会使测试变得脆弱.
  • 我们在Airbnb中主要使用 mocha. tape 有时也用于小的独立模块.
  • 100%的测试覆盖率是需要努力达成的目标,即使这是不切实际的.
  • 无论何时修复bug, 写回归测试. 没有回归测试的bug修复几乎会在将在再次出现.

⬆ back to top

性能

⬆ back to top

资源

学习 ES6

阅读

工具

其他编码规范指南

其他风格

更深的阅读

书籍

博客

播客

⬆ back to top

谁在使用

这是一份使用这份编码规范的组织列表.给我们提PR我们会把你加到这份列表里.

⬆ back to top

翻译

其他语言的版本:

JavaScript编码规范指南

和我们聊聊JavaScript

贡献者

许可

(The MIT License)

Copyright (c) 2012 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. ⬆ back to top

修改

我们鼓励你fork这份指南并改变规则来适应你团队的风格.你还可以列出一些修改补充.这可以让你不用处理合并冲突就可以定期升级编码规范指南

About

Airbnb JavaScript编码规范翻译

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published