跳到主要内容

JavaScript 编码规范

0 关于 ES5

表单设计器里面的 JS 代码都是会直接在浏览器运行的,如果不确定浏览器的支持情况,统一用 ES5 的写法是最安全的。

  • 0.1 recommended ES5 中的变量声明。

    使用 var 进行声明:

    // good
    var foo = 'foo';

    需注意,var 声明的变量不是块作用域而是函数作用域:

    // 将打印 2, 2, 2,而非 0, 1, 2
    for (var i = 0; i < 3; ++i) {
    var iteration = i;
    setTimeout(function() { console.log(iteration); }, i * 1000);
    }

    另外,var 声明的变量会被提升到其作用域顶部:

    // 变量声明会被提升到函数顶部,但赋值不会被提升
    function example() {
    console.log(declaredButNotAssigned); // => undefined
    console.log(notDeclared); // => throws a ReferenceError
    var declaredButNotAssigned = true;
    }

    即便如此,我们还是推荐在变量使用前再进行声明,而不是统一在作用域开始处声明,以增强可读性。当然,如果你担心声明提升问题会带来隐患,也可以选择统一在作用域开始处进行声明。

    不要在声明前就使用变量,这样做可能给人带来疑惑和隐患。eslint: no-use-before-define

    // bad
    console.log(foo); // => undefined
    var foo = 'foo';

    // good
    var foo = 'foo';
    console.log(foo); // => foo
  • 0.2 referenced 使用 Array 的 slice 方法进行数组复制和类数组对象转换。

    数组复制:

    var items = [1, 2, 3];

    // bad
    var itemsCopy = [];
    for (var i = 0; i < items.length; i++) {
    itemsCopy[i] = items[i];
    }

    // good
    var itemsCopy = items.slice();

    将类数组对象转换成数组:

    function trigger() {
    var args = Array.prototype.slice.call(arguments);
    // ...
    }
  • 0.3 recommended 不要使用保留字作为对象的属性名。

    不要使用保留字作为对象的属性名,它们在 IE8 中不工作

    // bad
    var superman = {
    class: 'alien',
    default: { clark: 'kent' },
    private: true
    };

    // good
    var superman = {
    type: 'alien',
    defaults: { clark: 'kent' },
    hidden: true
    };

1 编码风格

编码风格系统已经自带格式化规范,写完代码过后右键格式化(Format Document)。

如图:

Format Code

2 语言特性

2.1 变量声明

  • 2.1.1 mandatory 一条声明语句声明一个变量。eslint: one-var

    这样做更易于追加新的声明语句(你不需要总去把最后的 ; 改成 , 了),也更易于进行单步调试。

    // bad
    const foo = 1,
    bar = 2;

    // good
    const foo = 1;
    const bar = 2;
  • 2.1.2 mandatory 声明的变量必须被使用。eslint: no-unused-vars

    声明而未使用的变量、表达式可能带来潜在的问题,也会给维护者造成困扰,应将它们删除。

    // bad - 未使用变量 foo
    const foo = 1;

    // good
    const foo = 1;
    doSomethingWith(foo);

    // bad - 只修改变量不认为是被使用
    let bar = 1;
    bar = 2;
    bar += 1;

    // good
    let bar = 1;
    bar = 2;
    bar += 1;
    doSomethingWith(bar);

    // bad - 未使用参数 y
    function getX(x, y) {
    return x;
    }

    // good
    function getXPlusY(x, y) {
    return x + y;
    }
  • 2.1.3 referenced 哪里使用,哪里声明。

    在变量被使用前再进行声明,而不是统一在块开始处进行声明。

    ES6 提供的 letconst 是块级作用域,不存在类似 var 的声明提升的问题。因此我们可以把声明写在更合理的地方(一般是变量被使用前),而不是统一在块开始处进行声明。

    // bad - 如果权限校验(checkUserPermission)失败,fetchData 是不必要的
    function getData(id) {
    const data = fetchData(id);

    if (!checkUserPermission()) {
    return false;
    }

    if (data.foo === 'bar') {
    // ...
    }

    return data;
    }

    // good
    function getData(id) {
    if (!checkUserPermission()) {
    return false;
    }

    const data = fetchData(id);

    if (data.foo === 'bar') {
    // ...
    }

    return data;
    }
  • 2.1.4 mandatory 变量不要与外层作用域已存在的变量同名。eslint: no-shadow

    如果变量与外层已存在变量同名,会降低可读性,也会导致内层作用域无法读取外层作用域的同名变量。

    // bad
    const foo = 1;
    if (someCondition) {
    const foo = 2;
    console.log(foo); // => 2
    }

    // good
    const foo = 1;
    if (someCondition) {
    const bar = 2;
    console.log(bar); // => 2
    console.log(foo); // => 1
    }
  • 2.1.5 mandatory 不要重复声明变量和函数。eslint: no-redeclare

    在 ES5 中,尽管使用 var 重复声明不会报错,但这样做会令人疑惑,降低程序的可维护性。同理,函数的声明也不要与已存在的变量和函数重名:

    // bad
    var a = 'foo';
    var a = 'bar';
    function a() {}
    console.log(a); // => 'bar'

    // good
    var a = 'foo';
    var b = 'bar';
    function c() {}
    console.log(a); // => 'foo'

    // bad - arg 已作为函数参数声明
    function myFunc(arg) {
    var arg = 'foo';
    console.log(arg);
    }
    myFunc('bar'); // => 'foo'

    // good
    function myFunc(arg) {
    var otherName = 'foo';
    console.log(arg);
    }
    myFunc('bar'); // => 'bar'

    在 ES6 中,使用 constlet 重复声明变量会直接报错:

    // bad
    const a = 'foo';
    function a() {} // => Uncaught SyntaxError: Identifier 'a' has already been declared

    // good
    const a = 'foo';
    function b() {}

    // bad - arg 已作为函数参数声明
    function myFunc(arg) {
    const arg = 'foo';
    console.log(arg);
    }
    myFunc('bar'); // => Uncaught SyntaxError: Identifier 'arg' has already been declared

    // good
    function myFunc(arg) {
    const otherName = 'foo';
    console.log(arg);
    }
    myFunc('bar'); // => 'bar'

2.2 原始类型

JS的数据类型包括 7 种原始类型(primitive type),即 Boolean, Null, Undefined, Number, String, Symbol (ES6 新定义), BigInt(ES11新定义),以及 Object 类型,了解更多。这个章节主要介绍原始类型相关的规约。

  • 2.2.1 mandatory 不要使用 new Number/String/Boolean。eslint: no-new-wrappers

    使用 new Number/String//Boolean 声明不会有任何好处,还会导致变量成为 object 类型,可能引起 bug。

    // bad
    const num = new Number(0);
    const str = new String('foo');
    const bool = new Boolean(false);
    console.log(typeof num, typeof str, typeof bool); // => object, object, object
    if (num) { // true(对象相当于 true)
    }
    if (bool) { // true(对象相当于 true)
    }

    // good
    const num = 0;
    const str = 'foo';
    const bool = false;
    console.log(typeof num, typeof str, typeof bool); // => number, string, boolean
    if (num) { // false(0 相当于 false)
    }
    if (bool) { // false
    }
  • 2.2.2 recommended 类型转换。

    【数字】使用 Number()parseInt()

    const str = '1';

    // bad
    const num = +str;
    const num = str >> 0;
    const num = new Number(str);

    // good
    const num = Number(str);

    // good
    const num = parseInt(str, 10);

    【字符串】使用 String()

    const num = 1;

    // bad
    const str = new String(num); // typeof str is "object" not "string"
    const str = num + ''; // invokes num.valueOf()
    const str = num.toString(); // isn’t guaranteed to return a string

    // good
    const str = String(num);

    【布尔值】使用 !!

    const age = 0;

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

    // good
    const hasAge = !!age;
  • 2.2.3 recommended 使用 parseInt() 方法时总是带上基数。eslint: radix

    parseInt 方法的第一个参数是待转换的字符串,第二个参数是转换基数。当第二个参数省略时,parseInt 会根据第一个参数自动判断基数:

    • 如果以 0x 开头,则使用 16 作基数
    • 如果以 0 开头,则使用 8 作基数。正是这条规则经常导致错误,ES5 规范中直接将这条规则移除,即 ES5 及之后的执行环境以 0 开头也会使用 10 作为基数
    • 其他情况则使用 10 作基数

    虽然从 ES5 开始就移除了自动以 8 作基数的规则,但有时难以保证所有的浏览器和 JS 执行环境都支持了这一特性。了解更多

    因此,推荐始终给 parseInt() 方法加上基数,除非可以保证代码的执行环境不受上述特性的影响。

    // bad
    parseInt("071"); // => ES5 前的执行环境中得到的是 57

    // good
    parseInt("071", 10); // => 71
  • 2.2.4 mandatory 避免不必要的布尔类型转换。eslint: no-extra-boolean-cast

    if 等条件语句中,将表达式的结果强制转换成布尔值是多余的:

    // bad
    if (!!foo) {
    // ...
    }

    while (!!foo) {
    // ...
    }

    const a = !!flag ? b : c;

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

    while (foo) {
    // ...
    }

    const a = flag ? b : c;
  • 2.2.5 字符串

  • 2.2.5.1 mandatory 字符串优先使用单引号。eslint: quotes

    // bad
    const name = "tod";
    const name = `tod`; // 模板字符串中应包含变量或换行,否则需用单引号

    // good
    const name = 'tod';
  • 2.2.5.2 recommended 使用模板字符串替代字符串拼接。eslint: prefer-template

    模板字符串让代码更简洁,可读性更强

    // bad
    function getDisplayName({ nickName, realName }) {
    return nickName + ' (' + realName + ')';
    }

    // good
    function getDisplayName({ nickName, realName }) {
    return `${nickName} (${realName})`;
    }
  • 2.2.5.3 mandatory 禁止不必要的转义字符。no-useless-escape

    转义字符会大大降低代码的可读性,因此尽量不要滥用它们。

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

    // good
    const foo = '\'this\' is "quoted"';
    const foo = `'this' is "quoted"`;

2.3 数组

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

    不要使用 new Array()Array() 创建数组,除非为了构造某一长度的空数组。

    // bad
    const a = new Array(1, 2, 3);
    const b = Array(1, 2, 3);

    // good
    const a = [1, 2, 3];
    const b = new Array(500); // 构造长度为 500 的空数组
  • 2.3.2 mandatory 某些数组方法的回调函数中必须包含 return 语句。eslint: array-callback-return

    以下数组方法:map, filter, from , every, find, findIndex, reduce, reduceRight, some, sort 的回调函数中必须包含 return 语句,否则可能会产生误用或错误。

    一个常见的误用是,本该用 forEach 的场景却用了 map

    // 欲将 ['a', 'b', 'c'] 转换成 {a: 0, b: 1, c: 2}
    const myArray = ['a', 'b', 'c'];
    const myObj = {};

    // bad - map 应用于构建一个新数组,单纯想遍历数组应使用 forEach
    myArray.map((item, index) => {
    myObj[item] = index;
    });

    // good
    myArray.forEach((item, index) => {
    myObj[item] = index;
    });

    某些方法漏掉 return 还可能引起错误:

    // 欲将 ['a', 'b', 'c'] 转换成 {a: 0, b: 1, c: 2}
    const myArray = ['a', 'b', 'c'];

    // bad => Uncaught TypeError: Cannot set property 'b' of undefined
    const myObj = myArray.reduce((memo, item, index) => {
    memo[item] = index;
    }, {});

    // good
    const myObj = myArray.reduce((memo, item, index) => {
    memo[item] = index;
    return memo;
    }, {});

2.4 对象

  • 2.4.1 mandatory 使用字面量创建对象。eslint: no-new-object

    // bad
    const obj = new Object();

    // good
    const obj = {};
  • 2.4.2 mandatory 对象的属性名不要用引号包裹,除非包含特殊字符。eslint: quote-props

    这样更加简洁,也有助于语法高亮和一些 JS 引擎的优化。

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

    // good
    const good = {
    foo: 3,
    bar: 4,
    'data-blah': 5,
    'one two': 12,
    };
  • 2.4.3 mandatory 优先使用 . 访问对象的属性。eslint: dot-notation

    这样可以提高代码可读性。[] 仅应在访问动态属性名或包含特殊字符的属性名时被使用。

    const obj = {
    active: true,
    [getDynamicKey()]: 'foo',
    'data-bar': 'bar',
    };

    // bad
    const isActive = obj['active'];

    // good
    const isActive = obj.active;
    const foo = obj[getDynamicKey()];
    const bar = obj['data-bar'];
  • 2.4.4 mandatory 不要直接在对象上调用 Object.prototypes 上的方法。eslint: no-prototype-builtins

    不要直接在对象上调用 Object.prototypes 上的方法,例如 hasOwnPropertypropertyIsEnumerableisPrototypeOf

    这些方法可能会被对象上的属性覆盖,导致错误:

    const obj = {
    foo: 'foo',
    hasOwnProperty: false,
    };
    const objNull = Object.create(null);

    // bad => Uncaught TypeError: obj.hasOwnProperty is not a function
    console.log(obj.hasOwnProperty('foo'));
    console.log(objNull.hasOwnProperty('foo'));

    // good
    console.log(Object.prototype.hasOwnProperty.call(obj, 'foo'));
    console.log(Object.prototype.hasOwnProperty.call(objNull, 'foo'));

2.5 函数

  • 2.5.1 mandatory 不要在块中使用函数声明。eslint: no-inner-declarations

    在非函数块(如 ifwhile 等)中,不要使用函数声明:

    // bad - 函数声明不是块作用域而是函数作用域,因此在块外也能使用函数,容易引起误解
    if (true) {
    function test() {
    console.log('test');
    }
    }
    test(); // => test

    // good - 函数表达式可以清晰地说明函数能否在块外使用
    // 不能在块外使用
    if (true) {
    const test = function () {
    console.log('test');
    };
    }
    test(); // => Uncaught ReferenceError: test is not defined

    // 能在块外使用
    let test;
    if (true) {
    test = function () {
    console.log('test');
    };
    }
    test(); // => test
  • 2.5.2 recommended 不要修改函数参数。eslint: no-param-reassign

    不要修改引用类型的参数,这可能导致作为入参的原变量发生变化:

    // bad
    const f1 = function f1(obj) {
    obj.key = 1;
    }
    const originalObj = { key: 0 };
    f1(originalObj);
    console.log(originalObj); // => { key: 1 }

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

    更不要给参数重新赋值,这可能导致意外的行为和内核优化问题:

    // bad
    function foo(bar, baz) {
    if (!baz) {
    bar = 1;
    }
    }

    // good
    function foo(bar, baz) {
    let qux = bar;
    if (!baz) {
    qux = 1;
    }
    }
  • 2.5.3 mandatory 将立即执行函数表达式(IIFE)用小括号包裹。eslint: wrap-iife

    IIFE 是一个独立的执行单元,将它用小括号包裹可以更清晰的体现这点。需要提醒的是,由于 ES6 模块语法的引入,你可能不再需要使用 IIFE 了。

    (function () {
    console.log('Welcome to the Internet. Please follow me.');
    }());
  • 2.5.4 referenced 函数的复杂度不应过高。eslint: complexity

    过高的复杂度意味着代码难以维护和测试。我们推荐函数的复杂度不要超过以下阈值:

    • 圈复杂度不超过 10
    • 认知复杂度不超过 15

2.6 操作符

  • 2.6.1 recommended 使用严格相等运算符。eslint: eqeqeq

    非严格相等运算符(==!=)会在比较前将被比较值转换为相同类型,对于不熟悉 JS 语言特性的人来说,这可能造成不小的隐患。了解更多

    因此,一般情况下我们应该使用严格比较运算符( ===!==)进行比较。如果要比较的两个值类型不同,应该显性地将其转换成相同类型再进行严格比较,而不是依赖于 ==!= 的隐式类型转换。

    const id = '83949';

    // bad - 为了兼容 id 可能是字符串的情况,而有意使用 == 与数字比较
    if (id == 83949) {
    // do something
    }

    // good - 如果 id 可能是字符串,应该先将其进行类型转换,再使用 === 进行比较
    if (Number(id) === 83949) {
    // do something
    }
  • 2.6.2 mandatory 不要使用一元自增自减运算符。eslint: no-plusplus

    不要使用一元自增自减运算符(++--),除非在 for 循环条件中。

    ++-- 会带来值是否会提前变化带来的理解成本,也可能因为自动添加分号机制导致一些错误,因此我们推荐使用 num += 1 来代替 num++。但出于习惯,在 for 循环的条件中依然可以使用自增自减运算符。

    let num = 1;

    // bad
    num++;
    --num;

    // good
    num += 1;
    num -= 1;
  • 2.6.3 mandatory 不要使用 void 运算符。eslint: no-void

    在很老版本的 JS 中,undefined 值是可变的,因此使用 void 语句一般是用来得到一个 undefined 值。而在新版本的 JS 中,上面的问题已不复存在。因此出于程序可读性的考虑,禁止使用 void 运算符。

    // bad
    const foo = void 0;

    // good
    const foo = undefined;
  • 2.6.4 mandatory 避免嵌套的三元表达式。eslint: no-nested-ternary

    嵌套的三元表达式会降低代码可读性。

    // bad
    const foo = bar ? baz : qux === quxx ? bing : bam;

    // good
    const qu = qux === quxx ? bing : bam;
    const foo = bar ? baz : qu;
  • 2.6.5 mandatory 避免不必要的三元表达式。eslint: no-unneeded-ternary

    // bad
    const foo = a ? a : b;
    const bar = c ? true : false;
    const baz = c ? false : true;

    // good
    const foo = a || b;
    const bar = !!c;
    const baz = !c;
  • 2.6.6 mandatory 混合使用多种操作符时,用小括号包裹分组。eslint: no-mixed-operators

    这可以更清晰地表达代码意图,提高可读性。四则运算符(+, -, *, /)可以不包裹,因为大多数人熟知它们的优先级。

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

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

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

    // good
    const bar = (a ** b) - (5 % d);

    // bad - 有人可能会误以为执行顺序是 (a || b) && c
    if (a || b && c) {
    return d;
    }

    // good
    if (a || (b && c)) {
    return d;
    }

    // good - 四则运算可以不用小括号包裹
    const bar = a + b / c * d;

2.7 控制语句

  • 2.7.1 mandatory switch 语句中的 case 需要以 break 结尾。eslint: no-fallthrough

    // bad
    switch(foo) {
    case 1:
    doSomething();
    case 2:
    doSomethingElse();
    default:
    doSomething();
    }

    // good
    switch(foo) {
    case 1:
    doSomething();
    break;
    case 2:
    doSomethingElse();
    break;
    default:
    doSomething();
    }
  • 2.7.2 recommended switch 语句需要始终包含 default 分支。eslint: default-case

    在使用 switch 语句时,有时会出现因开发者忘记设置 default 而导致错误,因此建议总是给出 default。如果有意省略 default,请在 switch 语句末尾用 // no default 注释指明:

    // bad
    let foo;
    switch (bar) {
    case 1:
    foo = 2;
    break;
    }

    // good
    let foo;
    switch (bar) {
    case 1:
    foo = 2;
    break;
    default:
    foo = 0;
    }

    // good - 如果有意省略 default,请在 switch 语句末尾用 `// no default` 注释指明
    let foo = 0;
    switch (bar) {
    case 1:
    foo = 2;
    break;
    // no default
    }
  • 2.7.3 referenced switch 语句应包含至少 3 个条件分支。

    switch 语句在有许多条件分支的情况下可以使代码结构更清晰。但对于只有一个或两个条件分支的情况,更适合使用 if 语句,if 语句更易于书写和阅读。

    // bad
    let foo;
    switch (bar) {
    case 1:
    foo = 2;
    break;
    default:
    foo = 0;
    }

    // good
    let foo;
    if (bar === 1) {
    foo = 2;
    } else {
    foo = 0;
    }
  • 2.7.4 referenced 控制语句的嵌套层级不要过深。eslint: max-depth

    控制语句的嵌套层级不要超过 4 级,否则将难以阅读和维护:

    // bad
    if (condition1) {
    // depth = 1
    if (condition2) {
    // depth = 2
    for (let i = 0; i < 10; i++) {
    // depth = 3
    if (condition4) {
    // depth = 4
    if (condition5) {
    // bad - depth = 5
    }
    return;
    }
    }
    }
    }
  • 2.7.5 mandatory for 循环中的计数器应朝着正确方向移动。eslint: for-direction

    for 循环中更新子句的计数器朝着错误的方向移动时,循环的终止条件将永远无法达到,这会导致死循环的出现。这时要么是程序出现了错误,要么应将 for 循环改为 while 循环。

    // bad
    for (let i = 0; i < length; i--) {
    // do something
    }

    // good
    for (let i = 0; i < length; i++) {
    // do something
    }
  • 2.7.6 recommended for-in 循环中需要对 key 进行验证。eslint: guard-for-in

    使用 for-in 循环时需要避免对象从原型链上继承来的属性也被遍历出来,因此保险的做法是对 key 是否是对象自身的属性进行验证:

    // bad
    for (const key in foo) {
    doSomething(key);
    }

    // good
    for (const key in foo) {
    if (Object.prototype.hasOwnProperty.call(foo, key)) {
    doSomething(key);
    }
    }
  • 2.7.7 referenced 如果一个 if 语句的结果总是返回一个 return 语句,那么最后的 else 是不必要的。eslint: no-else-return

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

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

    return y;
    }
  • 2.7.8 referenced 条件表达式的计算结果。

    条件表达式(例如 if 语句的条件)的值为通过抽象方法 ToBoolean 进行强制转换所得,计算结果遵守下面的规则:

    • 对象数组 被计算为 true
    • Undefined 被计算为 false
    • Null 被计算为 false
    • 布尔值 被计算为 布尔的值
    • 数字 如果是 +0、-0 或 NaN 被计算为 false,否则为 true
    • 字符串 如果是空字符串 '' 被计算为 false,否则为 true
    if ({}) { // => true
    }

    if ([]) { // => true
    }

    if (0) { // => false
    }

    if ('0') { // => true
    }

    if ('') { // => false
    }

2.8 其他

  • 2.8.1 mandatory 禁止使用 eval。eslint: no-eval

    eval 语句存在安全风险,可能导致注入攻击。

    // bad
    const obj = { x: 'foo' };
    const key = 'x';
    const value = eval('obj.' + key);

    // good
    const obj = { x: 'foo' };
    const key = 'x';
    const value = obj[key];
  • 2.8.2 mandatory 禁止使用 debugger。eslint: no-debugger

    debugger 语句会让程序暂停,并在当前位置开启调试器。它通常在程序调试阶段使用,不应发布到线上。

    // bad
    function isTruthy(x) {
    debugger;
    return Boolean(x);
    }
  • 2.8.3 recommended 禁止使用 alert。eslint: no-alert

    alert 语句会使浏览器弹出原生警告框,这可能让人感觉你的程序出错了。如果需要对用户弹出警告信息,好的做法是使用第三方的弹窗组件或自己定义警告框样式。同理,confirmprompt 语句也不应被使用。

    // bad
    alert('Oops!');

    // good - 使用自定义的 Alert 组件
    Alert('Oops!');
  • 2.8.4 recommended 生产环境禁止使用 console。eslint: no-console

    console 语句通常在调试阶段使用,发布上线前,应该去掉代码里所有的 console 语句。

    // bad
    console.log('Some debug messages..');
  • 2.8.5 mandatory 禁止对原生对象或只读的全局对象进行赋值。eslint: no-global-assign

    JS 执行环境中会包含一些全局变量和原生对象,如浏览器环境中的 window,node 环境中的 globalprocessObjectundefined 等。除了像 window 这样的众所周知的对象,JS 还提供了数百个内置全局对象,你可能在定义全局变量时无意对它们进行了重新赋值,因此最好的做法是不要定义全局变量。

    // bad
    window = {};
    Object = null;
    undefined = 1;

3 注释

注释的目的:提高代码的可读性,从而提高代码的可维护性 注释的原则:如无必要,勿增注释;如有必要,尽量详尽

  • 3.1 recommended 单行注释使用 //。

    注释应单独一行写在被注释对象的上方,不要追加在某条语句的后面:

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

    // bad - 注释行上面是一个块的顶部时不需要空行
    function getType() {

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

    return type;
    }

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

    return type;
    }
  • 3.2 recommended 多行注释使用 /** ... */,而不是多行的 //。

    // 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;
    }
  • 3.3 mandatory 注释内容和注释符之间需要有一个空格。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;
    }
  • 3.4 referenced 合理使用特殊注释标记。eslint: no-warning-comments

    有时我们发现某个可能的 bug,但因为一些原因还没法修复;或者某个地方还有一些待完成的功能,这时我们需要使用相应的特殊标记注释来告知未来的自己或合作者。最常用的特殊标记有两种:

    • // FIXME: 说明问题是什么
    • // TODO: 说明还要做什么或者问题的解决方案

    一个我们不愿看到却很普遍的情况是,我们给代码标记 FIXMETODO 后却一直没找到时间处理。所以当你做了特殊标记,你应该为它负责,在某个时间把它解决。

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

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

    // TODO: total should be configurable by an options param
    this.total = 0;
    }
    }
  • 3.5 referenced 文档类注释使用 jsdoc 规范。

    文档类注释,如函数、类、文件、事件等,推荐使用 jsdoc 规范或类 jsdoc 的规范。

    例如:

    /**
    * Book类,代表一个书本.
    * @constructor
    * @param {string} title - 书本的标题.
    * @param {string} author - 书本的作者.
    */
    function Book(title, author) {
    this.title = title;
    this.author = author;
    }

    Book.prototype = {
    /**
    * 获取书本的标题
    * @returns {string|*}
    */
    getTitle() {
    return this.title;
    },

    /**
    * 设置书本的页数
    * @param pageNum {number} 页数
    */
    setPageNum(pageNum) {
    this.pageNum = pageNum;
    },
    };
  • 3.6 referenced 无用的代码注释应被即时删除。

    无用的注释代码会使程序变得臃肿并降低可读性,应被即时删除。你可以通过版本控制系统找回被删除的代码。

4 命名

  • 4.1 referenced 使用小驼峰(camelCase)命名原始类型、对象、函数、实例。camelcase

    // bad
    const this_is_my_string = 'foo';
    const this_is_my_object = {};
    function this_is_my_function() {}

    // good
    const thisIsMyString = 'foo';
    const thisIsMyObject = {};
    function thisIsMyFunction() {}
  • 4.2 mandatory 使用大驼峰(PascalCase)命名类和构造函数。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',
    });
  • 4.3 referenced 命名不要以下划线开头或结尾。eslint: no-underscore-dangle

    JS 没有私有属性或私有方法的概念,这样的命名可能会让人误解。

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

    // good
    this.firstName = 'Panda';