cbq926 / trick

JavaScript 代码风格指南()

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JavaScript 代码风格指南() {

如何用最合理的方式写你的 JavaScript 代码

BY 张聪(dancerphil@github)

这是一篇在原文基础上演绎的译文,与原文的表达会有出入

原文在不断的更新,本文基于 2016-08-03 的版本,last commit [f94f6e]

除非另行注明,页面上所有内容采用MIT协议共享

Downloads Downloads ![Gitter](https://badges.gitter.im/Join Chat.svg)

其他的代码指南(Style Guides)

目录

  1. 类型 Types
  2. 引用 References
  3. 对象 Objects
  4. 数组 Arrays
  5. 解构 Destructuring (注:MDN 解构赋值)
  6. 字符串 Strings
  7. 函数 Functions
  8. 箭头函数 Arrow Functions
  9. 类,构造函数 Classes & Constructors
  10. 模块 Modules
  11. 迭代器,生成器 Iterators and Generators
  12. 属性 Properties
  13. 变量 Variables
  14. 提升 Hoisting
  15. 比较运算符,等号 Comparison Operators & Equality
  16. 块 Blocks
  17. 注释 Comments
  18. 空格 Whitespace
  19. 逗号 Commas
  20. 分号 Semicolons
  21. 类型,强制类型转换 Type Casting & Coercion
  22. 命名规则 Naming Conventions
  23. 存取器 Accessors (getter,setter)
  24. 事件 Events
  25. jQuery
  26. ECMAScript 5 兼容
  27. ES6 代码风格
  28. 测试 Testing
  29. 性能 Performance
  30. 学习资源 Resources
  31. 使用人群
  32. 其他翻译
  33. 指南的指南
  34. 和我们一起讨论JS
  35. 贡献者
  36. 许可 License

类型

  • 1.1 基本类型: 你直接访问到基本类型的值

    • string
    • number
    • boolean
    • null
    • undefined
    const foo = 1;
    let bar = foo;
    
    bar = 9;
    
    console.log(foo, bar); // => 1, 9

  • 1.2 复杂类型: 你访问到复杂类型的引用,通过引用得到值

    • object
    • array
    • function
    const foo = [1, 2];
    const bar = foo;
    
    bar[0] = 9;
    
    console.log(foo[0], bar[0]); // => 9, 9

↑ 回到最上方

引用

  • 2.1 你的所有引用都应该使用 const ; 避免使用 var 。 eslint: prefer-const, no-const-assign

    为什么?这就确保了你不会对引用(reference)重新赋值,否则会出现难以理解的 bug

    // 差评
    var a = 1;
    var b = 2;
    
    // 好评
    const a = 1;
    const b = 2;

  • 2.2 如果你一定要修改一个引用,那也不要用 var ,你可以用 let 来代替。 eslint: no-var jscs: disallowVar

    为什么? let 是块级作用域变量,而 var 是函数作用域变量,那你还不快用 let

    // 差评
    var count = 1;
    if (true) {
      count += 1;
    }
    
    // 好评,使用 let
    let count = 1;
    if (true) {
      count += 1;
    }

    BY dancerphil:有一个经典的例子

    // 差评,而且失败
    var foos = []
    for(var i = 0; i < 4; i++){
      foos[i] = function() {
        console.log(i)
      }
    }
    foos[1]() // 4
    
    // 好评
    var foos = []
    for(let i = 0; i < 4; i++){ // 使用 let
      foos[i] = function() {
        console.log(i)
      }
    }
    foos[1]() // 1

  • 2.3 提醒你一下: letconst 都是块级作用域

    // const 和 let 只在它们定义的'代码块'里才存在
    {
      let a = 1;
      const b = 1;
    }
    console.log(a); // 出现 ReferenceError
    console.log(b); // 出现 ReferenceError

↑ 回到最上方

对象

  • 3.1 使用能让人看懂的,字面的,自解释的语法来创建对象。 eslint: no-new-object

    // 差评
    const item = new Object();
    
    // 好评
    const item = {};

  • 3.2 如果你的代码在浏览器环境下执行,别使用 保留字 reserved words 作为键值。来个 IE8 ,它就爆炸了。 更多信息。而在ES6模块中使用或者在服务器端使用时,什么事都没有。 jscs: disallowIdentifierNames

    // 差评
    var superman = {
      default: { clark: 'kent' },
      private: true,
    };
    
    // 好评
    var superman = {
      defaults: { clark: 'kent' },
      hidden: true,
    };

  • 3.3 用一些可读性强的同义词来代替你想要使用的保留字。 jscs: disallowIdentifierNames

    // 差评
    var superman = {
      class: 'alien',
    };
    
    // 差评
    var superman = {
      klass: 'alien',
    };
    
    // 好评
    var superman = {
      type: 'alien',
    };

  • 3.4 创建有动态属性名的对象时,使用特性:可被计算属性名称

    你说的什么意思?这样的话,你可以在一个地方定义所有的对象属性

    注 BY dancerphil`a key named ${k}` 是 ES6 模板字符串,其中可以包含 ${expression} 占位符,(见:MDN 模板字符串)

    function getKey(k) {
      return `a key named ${k}`;
    }
    
    // 差评
    const obj = {
      id: 5,
      name: 'San Francisco',
    };
    obj[getKey('enabled')] = true;
    
    // 好评
    const obj = {
      id: 5,
      name: 'San Francisco',
      [getKey('enabled')]: true, // 可被计算属性名称
    };

  • 3.5 使用对象方法的简写写法。 eslint: object-shorthand jscs: requireEnhancedObjectLiterals

    // 差评
    const atom = {
      value: 1,
    
      addValue: function (value) {
        return atom.value + value;
      },
    };
    
    // 好评
    const atom = {
      value: 1,
    
      addValue(value) {
        return atom.value + value;
      },
    };

  • 3.6 使用对象属性值的简写写法。 eslint: object-shorthand jscs: requireEnhancedObjectLiterals

    为什么?这样写更短更有力!

    const lukeSkywalker = 'Luke Skywalker';
    
    // 差评
    const obj = {
      lukeSkywalker: lukeSkywalker,
    };
    
    // 好评
    const obj = {
      lukeSkywalker,
    };

  • 3.7 把简写的属性放在一起,然后写在对象的开头处

    为什么?这样能清楚地分辨哪些属性使用了简写

    const anakinSkywalker = 'Anakin Skywalker';
    const lukeSkywalker = 'Luke Skywalker';
    
    // 差评
    const obj = {
      episodeOne: 1,
      twoJediWalkIntoACantina: 2,
      lukeSkywalker,
      episodeThree: 3,
      mayTheFourth: 4,
      anakinSkywalker,
    };
    
    // 好评
    const obj = {
      lukeSkywalker,
      anakinSkywalker,
      episodeOne: 1,
      twoJediWalkIntoACantina: 2,
      episodeThree: 3,
      mayTheFourth: 4,
    };

为什么?一般的,我们认为这样更易读,这种做法可以改善 IDE 语法高亮,同时帮助很多JS引擎进行优化

注 BY dancerphil:这一点还是存在一些争议的,eslint 中对 属性名引用风格 (Quoting Style for Property Names)有四种不同的规则,此文采用的是 as-needed 规则,只在需要引用的情况下才引用

而需要引用的情况有两种:

  1. 你使用了 ES3 环境,比如坑爹的 IE8 以下,并且你使用了 keyword 比如 if ,是必须引号的,这个规则在 ES5 被移除了

  2. 你想要使用一个非标识符 non-identifier 作为属性名,比如一个带空格的字符串 "one two"

eslint源码在此

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

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

  • 3.9 不要直接调用 Object.prototype 方法,比如 hasOwnPropertypropertyIsEnumerableisPrototypeOf.

为什么?这些方法可能会被问题对象的属性所覆盖(shadowed) - 考虑 { hasOwnProperty: false } - 或者干脆,这是一个空对象 (Object.create(null)).

// 差评
console.log(object.hasOwnProperty(key));

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

// 最佳实现
const has = Object.prototype.hasOwnProperty; // 在模块域内缓存一个查找(lookup)
/* or */
const has = require('has');

console.log(has.call(object, key));

↑ 回到最上方

数组

  • 4.1 用大家都看得懂的字面语法创建数组。 eslint: no-array-constructor

    // 差评
    const items = new Array();
    
    // 好评
    const items = [];

  • 4.2 不要用下标方式直接在数组中加上一个项,使用 Array#push 来代替

    const someStack = [];
    
    // 差评
    someStack[someStack.length] = 'abracadabra';
    
    // 好评
    someStack.push('abracadabra');

  • 4.3 使用数组的拓展运算符 ... 来复制数组,谁用谁知道

    // 差评
    const len = items.length;
    const itemsCopy = [];
    let i;
    
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }
    
    // 好评
    const itemsCopy = [...items];

  • 4.5 在数组函数的回调中,使用 return 声明。如果一个函数体只需要一句简单的声明,那么你可以省略 return 。参见 8.2。 eslint: array-callback-return

    // 好评
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });
    
    // 好评
    [1, 2, 3].map(x => x + 1);
    
    // 差评
    const flat = {};
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item);
      flat[index] = flatten;
    });
    
    // 好评
    const flat = {};
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item);
      flat[index] = flatten;
      return flatten;
    });
    
    // 差评
    inbox.filter((msg) => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      } else {
        return false;
      }
    });
    
    // 好评
    inbox.filter((msg) => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      }
    
      return false;
    });

↑ 回到最上方

解构

  • 5.1 使用对象解构(object destructuring)访问和使用多属性对象。 jscs: requireObjectDestructuring

    为什么?解构把你从创建临时引用的地狱中拯救了出来,感谢

    // 差评
    function getFullName(user) {
      const firstName = user.firstName;
      const lastName = user.lastName;
    
      return `${firstName} ${lastName}`;
    }
    
    // 好评
    function getFullName(user) {
      const { firstName, lastName } = user;
      return `${firstName} ${lastName}`;
    }
    
    // 好评如潮
    function getFullName({ firstName, lastName }) {
      return `${firstName} ${lastName}`;
    }

  • 5.2 解构数组。 jscs: requireArrayDestructuring

    const arr = [1, 2, 3, 4];
    
    // 差评
    const first = arr[0];
    const second = arr[1];
    
    // 好评
    const [first, second] = arr;

  • 5.3 要返回多个值时,使用对象解构,而不是数组解构。 jscs: disallowArrayDestructuringReturn

    为什么?你可以轻松的增减返回值或更改它们的顺序,而不需要改变调用者的代码

    // 差评
    function processInput(input) {
      // 一些奇怪的事情发生了(then a miracle occurs)
      return [left, right, top, bottom];
    }
    
    // 调用者需要思考返回值的顺序问题
    const [left, __, top] = processInput(input);
    
    // 好评
    function processInput(input) {
      // 一些奇怪的事情发生了
      return { left, right, top, bottom };
    }
    
    // 调用者只拿取它们需要的数据
    const { left, top } = processInput(input);

↑ 回到最上方

字符串

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

    // 差评
    const name = "Capt. Janeway";
    
    // 好评
    const name = 'Capt. Janeway';

  • 6.2 字符串超过 100 个字节时,应当使用字符串连接号换行

  • 6.3 注意:过度使用字串连接符号可能会对性能造成影响。 jsPerf相关讨论.

    // 差评
    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.';
    
    // 差评
    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.';
    
    // 好评
    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.4 用程序生成字符串时,使用模板字符串代替字符串连接。 eslint: prefer-template template-curly-spacing jscs: requireTemplateStrings

    为什么呢?模板字符串更为简洁,更具可读性,其恰当的换行和字符串插值非常好用

    // 差评
    function sayHi(name) {
      return 'How are you, ' + name + '?';
    }
    
    // 差评
    function sayHi(name) {
      return ['How are you, ', name, '?'].join();
    }
    
    // 差评
    function sayHi(name) {
      return `How are you, ${ name }?`;
    }
    
    // 好评
    function sayHi(name) {
      return `How are you, ${name}?`;
    }

  • 6.5 永远不要在字符串上使用 eval() ,这会造成很多漏洞

  • 6.6 不要在字符串里使用无谓的转义符号。 eslint: no-useless-escape

    为什么?你看得清吗?所以只有必要的时候才转义

    // 差评
    const foo = '\'this\' \i\s \"quoted\"';
    
    // 好评
    const foo = '\'this\' is "quoted"';
    const foo = `'this' is "quoted"`;

↑ 回到最上方

函数

  • 7.1 使用函数声明,不要使用函数表达式。 jscs: requireFunctionDeclarations

    为什么?函数声明是可命名的,他们在调用栈中更容易被识别。另外,函数声明会把整个函数提升(hoisted),而函数表达式只会把函数的引用变量名提升。所以箭头函数可以完全取代函数表达式

    // 差评
    const foo = function () {
    };
    
    // 好评
    function foo() {
    }

  • 7.2 在立即调用的函数表达式的两侧用括号包裹。 eslint: wrap-iife jscs: requireParenthesesAroundIIFE

    为什么?立即调用的函数表达式(immediately invoked function expression)是一个单独的单元,你需要强调这一点,所以把表达式和它的调用括号包裹起来。还需要提醒一下的是:在写模块的时候,你永远不会需要一个 IIFE

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

  • 7.3 永远不要再一个非函数代码块(if, while 等等)定义一个函数。请把你想要的那个函数赋给一个变量。浏览器会允许你这么做,但是它们的解析表现不一致。 eslint: no-loop-func

  • 7.4 注意: ECMA-262 把 block 定义为一组语句。而函数声明并不是语句。 阅读 ECMA-262 相关说明

    // 差评
    if (currentUser) {
      function test() {
        console.log('Nope.');
      }
    }
    
    // 好评
    let test;
    if (currentUser) {
      test = () => {
        console.log('Yup.');
      };
    }

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

    // 差评
    function nope(name, options, arguments) {
      // ...一些代码...
    }
    
    // 好评
    function yup(name, options, args) {
      // ...一些代码...
    }

  • 7.6 与此同时,不要使用 arguments 。可以选择 rest 语法 ... 替代。 eslint: prefer-rest-params

    为什么? ... 可以确切的指定你想要获取的变量,而且 rest 参数是一个真正的数组,而 argument 是一个类数组对象

    // 差评
    function concatenateAll() {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    }
    
    // 好评
    function concatenateAll(...args) {
      return args.join('');
    }

  • 7.7 使用预设参数的语法,而不是改变函数参数

    // 实在差评
    function handleThings(opts) {
      // 不!我们不该改变函数参数
      // 错上加错!如果 opts 被计算为 false ,它会成为一个对象
      // 或许你想这么做,但是这样会引起 bug
      opts = opts || {};
      // ...
    }
    
    // 依旧差评
    function handleThings(opts) {
      if (opts === void 0) {
        opts = {};
      }
      // ...
    }
    
    // 好评
    function handleThings(opts = {}) {
      // ...
    }

  • 7.8 避免预设参数的语法的副作用

    为什么?这样做完之后,代码变得没有逻辑

    var b = 1;
    // 差评
    function count(a = b++) {
      console.log(a);
    }
    count();  // 1
    count();  // 2
    count(3); // 3
    count();  // 3

  • 7.9 总是把预设参数写在参数的最后

    // 差评
    function handleThings(opts = {}, name) {
      // ...
    }
    
    // 好评
    function handleThings(name, opts = {}) {
      // ...
    }

  • 7.10 永远不要使用函数构造器来构造一个新函数

    为什么?通过这种方式来构造函数与 eval() 相类似。会造成很多漏洞

    // 差评
    var add = new Function('a', 'b', 'return a + b');
    
    // 依旧差评
    var subtract = Function('a', 'b', 'return a - b');

  • 7.11 在函数的 signature 后放置空格。eslint: space-before-function-paren space-before-blocks

    为什么?一致性是必须考虑的。在你新增和删除名称时,你不需要改变空格

    // 差评
    const f = function(){};
    const g = function (){};
    const h = function() {};
    
    // 好评
    const x = function () {};
    const y = function a() {};

  • 7.12 不要改变对象的参数。 eslint: no-param-reassign

    为什么?将传入对象的参数重新赋值,可能会引起一些不期望的副作用

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

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

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

    // 差评
    function f1(a) {
      a = 1;
    }
    
    function f2(a) {
      if (!a) { a = 1; }
    }
    
    // 好评
    function f3(a) {
      const b = a || 1;
    }
    
    function f4(a = 1) {
    }

↑ 回到最上方

箭头函数

  • 8.1 当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数符号。 eslint: prefer-arrow-callback, arrow-spacing jscs: requireArrowFunctions

    为什么?箭头函数创造了一个新的 this 上下文,通常符合你的期望,而且箭头函数非常简洁。(参考 BY yucheMDNtoddmotto

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

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

  • 8.2 如果一个函数体只有很简单的一行,那就把花括号、圆括号和 return 都省略掉。如果不是,那就保留括号和 return 。 eslint: arrow-parens, arrow-body-style jscs: disallowParenthesesAroundArrowParam, requireShorthandArrowFunctions

    为什么?通过语法修饰,在函数的链式调用中可读性很高

    // 差评
    [1, 2, 3].map(number => {
      const nextNumber = number + 1;
      `A string containing the ${nextNumber}.`;
    });
    
    // 好评
    [1, 2, 3].map(number => `A string containing the ${number}.`);
    
    // 好评
    [1, 2, 3].map((number) => {
      const nextNumber = number + 1;
      return `A string containing the ${nextNumber}.`;
    });
    
    // 好评
    [1, 2, 3].map((number, index) => ({
      index: number
    }));

  • 8.3 如果你们表达式跨行了,请使用括号把它们包裹起来,增加可读性

    为什么?这样做是的表达式的开始和结束的位置更加清楚

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

  • 8.4 如果你的函数只有一个参数,而且不包含大括号,那么省略括号吧,否则,你应当使用括号包住参数。 eslint: arrow-parens jscs: disallowParenthesesAroundArrowParam

    为什么?减少视觉噪声

    // 差评
    [1, 2, 3].map((x) => x * x);
    
    // 好评
    [1, 2, 3].map(x => x * x);
    
    // 好评
    [1, 2, 3].map(number => (
      `A long string with the ${number}. It’s so long that we’ve broken it ` +
      'over multiple lines!'
    ));
    
    // 差评
    [1, 2, 3].map(x => {
      const y = x + 1;
      return x * y;
    });
    
    // 好评
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });

  • 8.5 避免把箭头函数语法 (=>) 和比较运算符 (<=, >=) 相混淆。不是你自己看得清就可以的,而是你写的时候要让别人看清。 eslint: no-confusing-arrow

    // 差评
    const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
    
    // 差评
    const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
    
    // 好评
    const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
    
    // 好评
    const itemHeight = (item) => {
      const { height, largeSize, smallSize } = item;
      return height > 256 ? largeSize : smallSize;
    };

↑ 回到最上方

类,构造函数

  • 9.1 总是使用 class。避免直接操作 prototype ,会 prototype 不一定要用,对吧

    为什么? class 语法更简洁易读

    // 差评
    function Queue(contents = []) {
      this.queue = [...contents];
    }
    Queue.prototype.pop = function () {
      const value = this.queue[0];
      this.queue.splice(0, 1);
      return value;
    };
    
    
    // 好评
    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

    // 差评
    const inherits = require('inherits');
    function PeekableQueue(contents) {
      Queue.apply(this, contents);
    }
    inherits(PeekableQueue, Queue);
    PeekableQueue.prototype.peek = function () {
      return this._queue[0];
    }
    
    // 好评
    class PeekableQueue extends Queue {
      peek() {
        return this._queue[0];
      }
    }

  • 9.3 方法返回 this 可以帮助链式调用,这真的棒

    // 差评
    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
    
    // 好评
    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

    // 差评
    class Jedi {
      constructor() {}
    
      getName() {
        return this.name;
      }
    }
    
    // 差评
    class Rey extends Jedi {
      constructor(...args) {
        super(...args);
      }
    }
    
    // 好评
    class Rey extends Jedi {
      constructor(...args) {
        super(...args);
        this.name = 'Rey';
      }
    }

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

    为什么?重复的类成员中,只有最后一个声明会被静静的选中,重复类成员几乎必然会引起 bug

    // 差评
    class Foo {
      bar() { return 1; }
      bar() { return 2; }
    }
    
    // 好评
    class Foo {
      bar() { return 1; }
    }
    
    // 好评
    class Foo {
      bar() { return 2; }
    }

↑ 回到最上方

模块

  • 10.1 总是使用模组 (import/export) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。参考:@颜海镜@segmentgault

    为什么?模块是未来,让我们开始使用“未来”吧!

    // 差评
    const AirbnbStyleGuide = require('./AirbnbStyleGuide');
    module.exports = AirbnbStyleGuide.es6;
    
    // 还可以
    import AirbnbStyleGuide from './AirbnbStyleGuide';
    export default AirbnbStyleGuide.es6;
    
    // 好评如潮
    import { es6 } from './AirbnbStyleGuide';
    export default es6;

  • 10.2 不要使用通配符 * 引入

    为什么?这样能确保你只有一个默认的 export

    // 差评
    import * as AirbnbStyleGuide from './AirbnbStyleGuide';
    
    // 好评
    import AirbnbStyleGuide from './AirbnbStyleGuide';

  • 10.3 不要从 import 中直接 export

    为什么?虽然写在一行看起来很棒,但是它们毕竟一个是引入,一个是导出,分开写会让所有事都井井有条

    // 差评
    // filename es6.js
    export { es6 as default } from './airbnbStyleGuide';
    
    // 好评
    // filename es6.js
    import { es6 } from './AirbnbStyleGuide';
    export default es6;

  • 10.4 来自相同路径的 import ,就把他们写在同一个地方。 eslint: no-duplicate-imports

    为什么?写在不同行的话,程序的维护和更新变得非常困难

    // 差评
    import foo from 'foo';
    // … 一些其他的 imports … //
    import { named1, named2 } from 'foo';
    
    // 好评
    import foo, { named1, named2 } from 'foo';
    
    // 好评
    import foo, {
      named1,
      named2,
    } from 'foo';

  • 10.5 不要 export 可变的绑定。 eslint: import/no-mutable-exports

    为什么?在特定的情况下也许会导出可变绑定,一般来说我们都要避免异常(mutation)的发生,只使用常数引用。

    // 差评
    let foo = 3;
    export { foo }
    
    // 好评
    const foo = 3;
    export { foo }

  • 10.6 如果模块只有一个简单的 export,使用 default 模块而不是带名称模块。 eslint: import/prefer-default-export

    // 差评
    export function foo() {}
    
    // 好评
    export default function foo() {}

  • 10.7 把所有的 import 放在其他的声明之前。 eslint: import/imports-first

    为什么?因为 import 是提升的(hoisted),把它们放在最上方可以避免许多令人惊讶的代码行为

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

↑ 回到最上方

迭代器,生成器

  • 11.1 不要使用迭代器(iterators)。选择 JS 高阶函数比如 map()reduce() ,不要使用循环,比如 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];
    
    // 差评
    let sum = 0;
    for (let num of numbers) {
      sum += num;
    }
    
    sum === 15;
    
    // 好评
    let sum = 0;
    numbers.forEach(num => sum += num);
    sum === 15;
    
    // 好评如潮 (use the functional force)
    const sum = numbers.reduce((total, num) => total + num, 0);
    sum === 15;

  • 11.2 暂时不要使用生成器(generators)

    为什么?他们不能很好的被编译到 ES5

    注 BY dancerphil:不过截至目前(2016.5),V8 和 Nodejs 已经很好的支持了生成器

↑ 回到最上方

  • 11.3 如果你一意孤行,不听劝告,要使用生成器,请先确保他们的函数签名(function signature)是正确的。 eslint: generator-star-spacing

    为什么? function* 是同一个概念关键字的两个部分 - * 不是 function的一个修饰符, function*function 不同,它是一个独有的关键字

    // 差评
    function * foo() {
    }
    
    const bar = function * () {
    }
    
    const baz = function *() {
    }
    
    const quux = function*() {
    }
    
    function*foo() {
    }
    
    function *foo() {
    }
    
    // 奇差无比
    function
    *
    foo() {
    }
    
    const wat = function
    *
    () {
    }
    
    // 好评
    function* foo() {
    }
    
    const foo = function* () {
    }

属性

  • 12.1 使用 . 符号来访问对象属性。 eslint: dot-notation jscs: requireDotNotation

    const luke = {
      jedi: true,
      age: 28,
    };
    
    // 差评
    const isJedi = luke['jedi'];
    
    // 好评
    const isJedi = luke.jedi;

  • 12.2 如果要使用一个变量来访问属性,那么使用 []

    const luke = {
      jedi: true,
      age: 28,
    };
    
    function getProp(prop) {
      return luke[prop];
    }
    
    const isJedi = getProp('jedi');

↑ 回到最上方

变量

  • 13.1 你应当使用 const 来声明变量。不这么做会导致全局变量。我们想要尽量避免污染全局命名空间。地球队长(Captain Planet)已经警告过我们了。(解释 BY dancerphil:Captain Planet 保卫着 global namespace,好冷)

    // 差评
    superPower = new SuperPower();
    
    // 好评
    const superPower = new SuperPower();

  • 13.2 每个变量都对应一个 const 。 eslint: one-var jscs: disallowMultipleVarDecl

    为什么?增加新的变量会更容易,而且你永远不用再担心把 ,; 换来换去的事情,你肯定觉得 git diff 里包含太多标点符号的变换是很蠢的事情。而且,调试时你可以步进,而不是一次性跳过了所有的声明

    BY dancerphil:不写分号星人有另一种可供参考的的写法

    // 差评
    const items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
    
    // 差评(与上面比较一下,再说说哪里写错了)
    const items = getItems(),
        goSportsTeam = true;
        dragonball = 'z';
    
    // 好评
    const items = getItems();
    const goSportsTeam = true;
    const dragonball = 'z';
    
    // BY dancerphil
    const items = getItems()
      , goSportsTeam = true
      , dragonball = 'z'

  • 13.3 把你所有的 const 放在一起,再把所有的 let 放在一起

    为什么?等到你想要把已赋值的变量赋给一个新变量的时候,这很有帮助

    // 差评
    let i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
    
    // 差评
    let i;
    const items = getItems();
    let dragonball;
    const goSportsTeam = true;
    let len;
    
    // 好评
    const goSportsTeam = true;
    const items = getItems();
    let dragonball;
    let i;
    let length;

  • 13.4 在你需要的地方赋值变量,把他们放到合理的位置

    为什么? letconst 是块级作用域的,不是函数级作用域的

    // 差评 - 没有必要的函数调用
    function checkName(hasName) {
      const name = getName();
    
      if (hasName === 'test') {
        return false;
      }
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }
    
    // 好评
    function checkName(hasName) {
      if (hasName === 'test') {
        return false;
      }
    
      const name = getName();
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }

↑ 回到最上方

提升

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

    // 这样是运行不了的
    // (如果 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
    
      var anonymous = function () {
        console.log('anonymous function expression');
      };
    }

  • 14.3 命名函数表达式的变量名会被提升,但函数名和函数函数内容不会

    function example() {
      console.log(named); // => undefined
    
      named(); // => TypeError named is not a function
    
      superPower(); // => ReferenceError superPower is not defined
    
      var named = function superPower() {
        console.log('Flying');
      };
    }
    
    // 当函数名和变量名相同时,结论也同样成立
    function example() {
      console.log(named); // => undefined
    
      named(); // => TypeError named is not a function
    
      var named = function named() {
        console.log('named');
      }
    }

  • 14.4 函数声明的名称和函数体都会被提升,所以你知道该怎么写

    function example() {
      superPower(); // => Flying
    
      function superPower() {
        console.log('Flying');
      }
    }
  • 想了解更多信息,参考 Scoping & Hoisting 来自 Ben Cherry

BY dancerphil:在我看来可以这样总结,没有特殊需要的时候,把定义放在使用之前就可以了

↑ 回到最上方

比较运算符,等号

  • 15.1 尽量使用 ===!== ,而不是 ==!= 。 eslint: eqeqeq

  • 15.2 Conditional statements such as the if statement evaluate their expression using coercion with the ToBoolean abstract method and always follow these simple rules:

    • Objects 被视为 true
    • Undefined 被视为 false
    • Null 被视为 false
    • Booleans 被视为 boolean的值
    • 如果 Numbers+0, -0, or NaN ,将被视为 false ,否则被视为 true
    • 如果 Strings 是一个空字符串 '' ,将被视为 false ,否则被视为 true
    if ([0] && []) {
      // true
      // 数组(即使它是空的)是一个对象,而对象被视为 true
    }

  • 15.3 使用简写(shortcuts)

    // 差评
    if (name !== '') {
      // ...一些代码...
    }
    
    // 好评
    if (name) {
      // ...一些代码...
    }
    
    // 差评
    if (collection.length > 0) {
      // ...一些代码...
    }
    
    // 好评
    if (collection.length) {
      // ...一些代码...
    }

  • 15.4 想了解更多信息,可以参考 Angus Croll 的 Truth Equality and JavaScript(你点进去后会发现你的思维并没有你想像的那么清晰)

    // BY dancerphil
    console.log([] == true); // false
    console.log(![] == true); // false
    console.log(!![] == true); // true
    if([]) {
      console.log('why?'); // why?
    }

  • 15.5 如果在 casedefault 中包含了宣告语法(lexical declarations),比如 let, const, function, 和 class。那么用大括号来建立代码块

eslint rules: no-case-declarations.

为什么?宣告语法在整个 switch 块都可见,但是只在我们定义的地方才得到了初始值,也就是当进入其所在的 case 时才触发。如果不加大括号,可能引起多个 case 尝试定义同一事物的情况

```javascript
// 差评
switch (foo) {
  case 1:
    let x = 1;
    break;
  case 2:
    const y = 2;
    break;
  case 3:
    function f() {}
    break;
  default:
    class C {}
}

// 好评
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 三元运算符通常应该是简单的单行表达式,而不是嵌套的(nested)

eslint rules: no-nested-ternary.

```javascript
// 差评
const foo = maybe1 > maybe2
  ? "bar"
  : value1 > value2 ? "baz" : null;

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

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

// 好评如潮
const maybeNull = value1 > value2 ? 'baz' : null;

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

  • 15.7 避免不必要的三元运算符

eslint rules: no-unneeded-ternary.

```javascript
// 差评
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;

// 好评
const foo = a || b;
const bar = !!c;
const baz = !c;
```

↑ 回到最上方

  • 16.1 用大括号包裹所有的多行代码块

    // 差评
    if (test)
      return false;
    
    // 好评
    if (test) return false;
    
    // 好评
    if (test) {
      return false;
    }
    
    // 差评
    function foo() { return false; }
    
    // 好评
    function bar() {
      return false;
    }

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

    // 差评
    if (test) {
      thing1();
      thing2();
    }
    else {
      thing3();
    }
    
    // 好评
    if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }

↑ 回到最上方

注释

  • 17.1 使用 /** ... */ 作为多行注释。包含描述,并指定参数和返回值的类型和值

    // 差评
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param {String} tag
    // @return {Element} element
    function make(tag) {
    
      // ...一些代码...
    
      return element;
    }
    
    // 好评
    /**
     * make() returns a new element
     * based on the passed in tag name
     *
     * @param {String} tag
     * @return {Element} element
     */
    function make(tag) {
    
      // ...一些代码...
    
      return element;
    }

  • 17.2 使用 // 作为单行注释。在需要注释的代码上方另起一行写注释。在注释前空一行,除非注释在块的开头

    // 差评
    const active = true;  // is current tab
    
    // 好评
    // is current tab
    const active = true;
    
    // 差评
    function getType() {
      console.log('fetching type...');
      // set the default type to 'no type'
      const type = this._type || 'no type';
    
      return type;
    }
    
    // 好评
    function getType() {
      console.log('fetching type...');
    
      // set the default type to 'no type'
      const type = this._type || 'no type';
    
      return type;
    }
    
    // 同样好评
    function getType() {
      // set the default type to 'no type'
      const type = this._type || 'no type';
    
      return type;
    }

  • 17.3 在注释前加上 FIXMETODO 可以帮助其他开发者快速的了解写这段注释的原因。也许你正在指出一个需要复查的问题,或者你正在给需要实现的功能提供一个解决方式。和一般的注解不用,它们是可被执行的,对应的动作为 FIXME: -- 需要搞清楚这一点 或者 TODO: -- 需要实现

  • 17.4 使用 // FIXME: 标注问题

    class Calculator extends Abacus {
      constructor() {
        super();
    
        // FIXME: 这里不应该存在全局变量
        total = 0;
      }
    }

  • 17.5 使用 // TODO: 标注问题的解决方式

    class Calculator extends Abacus {
      constructor() {
        super();
    
        // TODO: total 应该是个可被传入的可选变量
        this.total = 0;
      }
    }

↑ 回到最上方

空格

BY dancerphil:曾经我坚持用 4 空格缩进,现在我认为 2 空格缩进也许更好,这样可以保证与 html, css 和 jsx 之间的一致性。这已经不是基于回调地狱的讨论,而是另外的原因。

```javascript
// 差评
function foo() {
∙∙∙∙const name;
}

// 差评
function bar() {
∙const name;
}

// 好评
function baz() {
∙∙const name;
}
```

  • 18.2 在起始的大括号 { 的左侧放上一个空格。 eslint: space-before-blocks jscs: requireSpaceBeforeBlockStatements

    // 差评
    function test(){
      console.log('test');
    }
    
    // 好评
    function test() {
      console.log('test');
    }
    
    // 差评
    dog.set('attr',{
      age: '1 year',
      breed: 'Bernese Mountain Dog',
    });
    
    // 好评
    dog.set('attr', {
      age: '1 year',
      breed: 'Bernese Mountain Dog',
    });

if 的行为与函数不同,我承认应该要加,虽然这带来了一些成本

```javascript
// 差评
if(isJedi) {
  fight ();
}

// 好评
if (isJedi) {
  fight();
}

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

// 好评
function fight() {
  console.log('Swooosh!');
}
```

我认为这一条只针对二元及以上运算符,对于一元运算符,请不要用空格分开它们,比如 let dog = isHuman && !hasGirl()

另:原文没有提到逗号和分号的空格风格,不过注意永远不要在逗号 , 分号 ; 的左侧加空格

```javascript
// 差评
const x=y+5;

// 好评
const x = y + 5;
```

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

    // 差评
    (function (global) {
      // ...一些代码...
    })(this);
    // 差评
    (function (global) {
      // ...一些代码...
    })(this);
    
    // 好评
    (function (global) {
      // ...一些代码...
    })(this);

  • 18.6 链式调用时(超过两个方法的链),使用符合调用逻辑的缩进,并且在方法的左侧放置点 . ,别给我放到后面,你需要强调这是方法调用而不是新语句。 eslint: newline-per-chained-call no-whitespace-before-property

    // 差评
    $('#items').find('.selected').highlight().end().find('.open').updateCount();
    
    // 差评
    $('#items').
      find('.selected').
        highlight().
        end().
      find('.open').
        updateCount();
    
    // 好评
    $('#items')
      .find('.selected')
        .highlight()
        .end()
      .find('.open')
        .updateCount();
    
    // 差评
    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);
    
    // 好评
    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);
    
    // 好评
    const leds = stage.selectAll('.led').data(data);

  • 18.7 在块结束后、新语句开始前,插入一个空行。 jscs: requirePaddingNewLinesAfterBlocks

    // 差评
    if (foo) {
      return bar;
    }
    return baz;
    
    // 好评
    if (foo) {
      return bar;
    }
    
    return baz;
    
    // 差评
    const obj = {
      foo() {
      },
      bar() {
      },
    };
    return obj;
    
    // 好评
    const obj = {
      foo() {
      },
    
      bar() {
      },
    };
    
    return obj;
    
    // 差评
    const arr = [
      function foo() {
      },
      function bar() {
      },
    ];
    return arr;
    
    // 好评
    const arr = [
      function foo() {
      },
    
      function bar() {
      },
    ];
    
    return arr;

  • 18.8 不要使用空白行来“装饰”你的代码块。 eslint: padded-blocks jscs: disallowPaddingNewlinesInBlocks

    // 差评
    function bar() {
    
      console.log(foo);
    
    }
    
    // also bad
    if (baz) {
    
      console.log(qux);
    } else {
      console.log(foo);
    
    }
    
    // 好评
    function bar() {
      console.log(foo);
    }
    
    // 好评
    if (baz) {
      console.log(qux);
    } else {
      console.log(foo);
    }

  • 18.9 不要在括号里加空格,当然,逗号后面有空格。 eslint: space-in-parens jscs: disallowSpacesInsideParentheses

    // 差评
    function bar( foo ) {
      return foo;
    }
    
    // 好评
    function bar(foo) {
      return foo;
    }
    
    // 差评
    if ( foo ) {
      console.log(foo);
    }
    
    // 好评
    if (foo) {
      console.log(foo);
    }

  • 18.12 避免你的某一行代码超过了 100 字符,当然空格也算在里面。 eslint: max-len jscs: maximumLineLength

    为毛?这种做法保证了可读性和可维护性

    // 差评
    const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. Whatever wizard constrains a helpful ally. The counterpart ascends!';
    
    // 差评
    $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
    
    // 好评
    const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. ' +
      'Whatever wizard constrains a helpful ally. The counterpart ascends!';
    
    // 好评
    $.ajax({
      method: 'POST',
      url: 'https://airbnb.com/',
      data: { name: 'John' },
    })
      .done(() => console.log('Congratulations!'))
      .fail(() => console.log('You have failed this city.'));

↑ 回到最上方

逗号

  • 19.1 行首逗号: 请对它说不 。 eslint: comma-style jscs: requireCommaBeforeLineBreak

    // 差评
    const story = [
        once
      , upon
      , aTime
    ];
    
    // 好评
    const story = [
      once,
      upon,
      aTime,
    ];
    
    // 差评
    const hero = {
        firstName: 'Ada'
      , lastName: 'Lovelace'
      , birthYear: 1815
      , superPower: 'computers'
    };
    
    // 好评
    const hero = {
      firstName: 'Ada',
      lastName: 'Lovelace',
      birthYear: 1815,
      superPower: 'computers',
    };

  • 19.2 结尾的多余逗号: 要!它并不多余 。 eslint: comma-dangle jscs: requireTrailingComma

    为什么? 这会让 git diffs 更干净。另外,像 babel 这样的转译器会移除结尾多余的逗号,也就是说你不必担心老旧浏览器的 行尾逗号问题。(老旧浏览器在这里指 IE6/7 和 IE9 的怪异模式)

    你问我非 ES6 环境?看着办吧

    // 差评 - 没有行尾逗号时, git diff 是这样的
    const hero = {
         firstName: 'Florence',
    -    lastName: 'Nightingale'
    +    lastName: 'Nightingale',
    +    inventorOf: ['coxcomb chart', 'modern nursing']
    };
    
    // 好评 - 有行尾逗号时, git diff 是这样的
    const hero = {
         firstName: 'Florence',
         lastName: 'Nightingale',
    +    inventorOf: ['coxcomb chart', 'modern nursing'],
    };
    
    // 差评
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully'
    };
    
    const heroes = [
      'Batman',
      'Superman'
    ];
    
    // 好评
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully',
    };
    
    const heroes = [
      'Batman',
      'Superman',
    ];

↑ 回到最上方

分号

  • 20.1 。 eslint: semi jscs: requireSemicolons

    BY dancerphil:我之前所在百姓网规定是不写的。(知乎的相关讨论)

    对我而言,无所谓,这是一个 eslint 自动 fix 的规则,在 AST 树的层面完全没有区别

    // 差评
    (function () {
      const name = 'Skywalker'
      return name
    })()
    
    // 好评
    (function () {
      const name = 'Skywalker';
      return name;
    }());
    
    // 好评,但是过于传统(这种做法曾被用来预防当两个存在 IIFE 的文件合并时括号被误当作一个函数的参数的情况)
    ;(() => {
      const name = 'Skywalker';
      return name;
    }());

    IIFE - 立即调用的函数表达式

    在 stackoverflow 阅读更多

↑ 回到最上方

类型,强制类型转换

  • 21.1 执行类型转换的位置应当是语句的开头

  • 21.2 对字符串:

    // => this.reviewScore = 9;
    
    // 差评
    const totalScore = this.reviewScore + ''; // 调用 this.reviewScore.valueOf()
    
    // 差评
    const totalScore = this.reviewScore.toString(); // 不能保证返回字符串
    
    // 好评
    const totalScore = String(this.reviewScore);

  • 21.3 数字:使用 Number 来进行类型转换,在使用 parseInt 时,总是带上类型转换的基数。 eslint: radix

    const inputValue = '4';
    
    // 差评
    const val = new Number(inputValue);
    
    // 差评
    const val = +inputValue;
    
    // 差评
    const val = inputValue >> 0;
    
    // 差评
    const val = parseInt(inputValue);
    
    // 好评
    const val = Number(inputValue);
    
    // 好评
    const val = parseInt(inputValue, 10);

  • 21.4 如果因为某些原因, parseInt 成为了性能瓶颈,而你就是任性的要使用 位操作 (Bitshift),那么留个注释解释一下原因吧

    // 好评
    /**
     * 我的程序那么慢,全是 parseInt 的锅
     * 改成使用位操作后,很快很爽很开心
     */
    const val = inputValue >> 0;

  • 21.5 注意 : 小心使用位操作运算符。数字会被当成 64位变量,但是位操作运算符总是返回 32 位的整数(参考)。位操作在处理大于 32 位的整数值时,并不会如你所料。相关讨论。最大的 32 位整数是 2,147,483,647 :

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

  • 21.6 布尔值:

    const age = 0;
    
    // 差评
    const hasAge = new Boolean(age);
    
    // 好评
    const hasAge = Boolean(age);
    
    // 好评如潮
    const hasAge = !!age;

↑ 回到最上方

命名规则

  • 22.1 别用单字母命名。让你的命名具备描述性

    // 差评
    function q() {
      // ...一些代码...
    }
    
    // 好评
    function query() {
      // ..一些代码..
    }

  • 22.2 使用驼峰式命名对象、函数和实例。 eslint: camelcase jscs: requireCamelCaseOrUpperCaseIdentifiers

    // 差评
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    function c() {}
    
    // 好评
    const thisIsMyObject = {};
    function thisIsMyFunction() {}

  • 22.3 使用帕斯卡式命名构造函数或类。它又被称为大驼峰式命名 eslint: new-cap jscs: requireCapitalizedConstructors

    // 差评
    function user(options) {
      this.name = options.name;
    }
    
    const bad = new user({
      name: 'nope',
    });
    
    // 好评
    class User {
      constructor(options) {
        this.name = options.name;
      }
    }
    
    const good = new User({
      name: 'yup',
    });

  • 22.4 不要以下划线作为开头或结尾。 eslint: no-underscore-dangle jscs: disallowDanglingUnderscores

    为什么?事实上,Javascript 没有隐私方面的概念。虽然在一般的约定中,在前面加下划线的意思是 private ,但事实上,这些属性完完全全的就是公开的,也因此,意味着它们也是你公共 API 的一部分。这个一般的约定误导一部分程序员,使他们错误的认为一个小小的变化不会引起什么结果,或者认为测试是可有可无的

    太长不看:如果你真的需要一些“私密”的属性,它不应该被双手奉上

    // 差评
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    this._firstName = 'Panda';
    
    // 好评
    this.firstName = 'Panda';

  • 22.5 别保存 this 的引用。使用箭头函数或 Function#bind。 jscs: disallowNodeTypes

    // 差评
    function foo() {
      const self = this;
      return function () {
        console.log(self);
      };
    }
    
    // 差评
    function foo() {
      const that = this;
      return function () {
        console.log(that);
      };
    }
    
    // 好评
    function foo() {
      return () => {
        console.log(this);
      };
    }

  • 22.6 你的文件名应当和默认的类名保持完全一致

    // file 1 contents
    class CheckBox {
      // ...
    }
    export default CheckBox;
    
    // file 2 contents
    export default function fortyTwo() { return 42; }
    
    // file 3 contents
    export default function insideDirectory() {}
    
    // 然后在其他的一些文件里
    // 差评
    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
    
    // 差评
    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
    
    // 好评
    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

  • 22.7 使用驼峰式命名导出默认函数。你的文件名应当和函数名保持完全一致

    function makeStyleGuide() {
    }
    
    export default makeStyleGuide;

  • 22.8 使用帕斯卡式命名导出构造函数、类、单例(singleton)、函数库、空对象

    const AirbnbStyleGuide = {
      es6: {
      }
    };
    
    export default AirbnbStyleGuide;

↑ 回到最上方

存取器

  • 23.1 并非所有属性的存取器函数都是必须的

  • 23.2 不要使用 JavaScript 的 getters/setters ,因为它们会导致你不期待的副作用,而且使测试、维护和推论。如果你一定要存取器函数,使用 getVal() 和 setVal('hello')

    // 差评
    class Dragon {
      get age() {
        // ...
      }
    
      set age(value) {
        // ...
      }
    }
    
    // 好评
    class Dragon {
      getAge() {
        // ...
      }
    
      setAge(value) {
        // ...
      }
    }

  • 23.3 如果属性或方法是一个 boolean ,使用 isVal()hasVal()

    // 差评
    if (!dragon.age()) {
      return false;
    }
    
    // 好评
    if (!dragon.hasAge()) {
      return false;
    }

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

↑ 回到最上方

事件

  • 24.1 当事件需要一些附加数据时(无论是 DOM 事件还是私有事件),传入一个哈希而不是原始值。这样可以让后面的开发者简单的增加更多数据到事件中,而无需找出并更新事件的每一个处理器。比如不要写:

    // 差评
    $(this).trigger('listingUpdated', listing.id);
    
    ...
    
    $(this).on('listingUpdated', (e, listingId) => {
      // 使用 listingId
    });

    而写:

    // 好评
    $(this).trigger('listingUpdated', { listingId: listing.id });
    
    ...
    
    $(this).on('listingUpdated', (e, data) => {
      // 使用 data.listingId
    });

↑ 回到最上方

jQuery

  • 25.1 jQuery 对象的变量名统一加上前缀 $ 。 jscs: requireDollarBeforejQueryAssignment

    // 差评
    const sidebar = $('.sidebar');
    
    // 好评
    const $sidebar = $('.sidebar');
    
    // 好评
    const $sidebarBtn = $('.sidebar-btn');

  • 25.2 缓存 jQuery 查询,避免无用的查询

    // 差评
    function setSidebar() {
      $('.sidebar').hide();
    
      // ...一些代码...
    
      $('.sidebar').css({
        'background-color': 'pink'
      });
    }
    
    // 好评
    function setSidebar() {
      const $sidebar = $('.sidebar');
      $sidebar.hide();
    
      // ...一些代码...
    
      $sidebar.css({
        'background-color': 'pink'
      });
    }

  • 25.3 查询 DOM 时,使用层叠 $('.sidebar ul') 或 parent > child 的模式 $('.sidebar > ul'). jsPerf

  • 25.4 查询有作用域的 jQuery 对象时,使用 find

    // 差评
    $('ul', '.sidebar').hide();
    
    // 差评
    $('.sidebar').find('ul').hide();
    
    // 好评
    $('.sidebar ul').hide();
    
    // 好评
    $('.sidebar > ul').hide();
    
    // 好评
    $sidebar.find('ul').hide();

↑ 回到最上方

ECMAScript 5 兼容性

↑ 回到最上方

ES6 代码风格

  • 27.1 ES6 代码风格分布在各个篇目中,我们整理了一下,以下是链接到 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. Iterators and Generators
  13. Modules

↑ 回到最上方

测试

  • 28.1 测试这个东西,真的可以有!

    function foo() {
      return true;
    }

  • 28.2 好吧,认真一点:
  • 不论你使用了什么测试框架,你都应该写测试!
  • 尽量写很多小的纯函数(small pure functions),这样你可以在很小的区域里探知异常发生的地点
  • 对 stubs 和 mocks 保持严谨 - 他们使你的测试变得更加脆弱
  • 在 Airbnb 我们主要使用 mocha ,对一些小型或单独的模块会使用 tape
  • 达到 100% 测试覆盖率,虽然实现它是不切实际的,但这是一个非常好的目标
  • 每当你修复一个 bug ,就为这个 bug 写 回归测试 。一个修复的 bug 如果没有回归测试,一般而言都会复发

↑ 回到最上方

性能

↑ 回到最上方

学习资源

学习 ES6

阅读

工具

其他风格指南

其他风格

更多阅读

相关书籍

博客(我很喜欢“部落格”,**会这么说。为了部落!格。)

播客 (Podcasts)

↑ 回到最上方

和我们一起讨论JS

  • 在这里找我们: gitter.

贡献者

许可

(The MIT License)

Copyright (c) 2014-2016 Airbnb

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

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

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

警告 :转载时必须包含此许可证

↑ 回到最上方

修订

非常鼓励你 fork 这份代码风格指南,稍加改动后作为你们团队的指南。欢迎讨论

};

About

JavaScript 代码风格指南()