翻译谷歌代码风格之JavaScript篇(未完待续)

1.综述

此文档为谷歌基于JavaScript代码风格的完整定义。只有一篇JavaScript文件遵照了如下规则的状况下,此文件能够被称为听从谷歌代码风格。
正如其余谷歌代码风格同样,本文的跨度不只包括了代码格式的美观,一样也包括了代码标准以及惯例。node

1.1 术语

在本文中,除特别注明以外:python

  1. 术语“注释”通常指的是实现注释。咱们不会使用“文档注释”,而做为代替,咱们使用通常术语"JS注文(JSDoc)"说明在/** … */之中的人类可读以及机器可读的注释。
  2. 当用到"必须""必须不""应该""不该该"以及"能够"的时候,本文听从RFC 2119术语规则。其中,术语中的倾向于避免应该不该该一致,而相应的,本文中命令与陈述式的说明则与术语中的必须相一致。

1.2 特别提示

文中全部的示例代码都是非标准的。换句话说,给出的谷歌代码风格示例代码都不是基于风格下实现这段代码的惟一方法。示例代码中给出的可选格式并非须要强制执行的规则。shell

2.源文件基本规则

2.1 文件名

文件名必须小写,而且不能够含有除了下划线(_)和破折号(-)以外的标点符号。请与你的项目风格和习惯保持一致。文件名的后缀必须是.jsexpress

2.2 文件编码:UTF-8

全部源文件都必须在UTF-8编码下。数组

2.3 特殊字符

2.3.1 空白字符

除了换行符以外,在文件出现的空白字符只能是ASCII中的空格符(0x20)。这也就是说:浏览器

  1. 字符串中出现其余空白字符都会被转义
  2. 制表符(Tab)不能用于缩进

2.3.2 特殊转义字符

任何含有特殊转义字符的字符(', ", \, b, f, n, r, t, v)老是优先于相应的数字转义字符(如x0a, u000a, u{a})。
永远不要使用早已被舍弃的八进制转义字符。安全

2.3.3 非ASCII码字符

对于剩余的非ASCII码字符,不管是实际编码字符(例如),16进制字符或是转义字符(例如\u221e),只能在使代码可读性和可理解性更好的状况下使用。
提示:在使用转义字符的时候,或者甚至在使用实际编码字符的某些时候,在后面加上一个解释说明的注释会有助于代码的优雅和可读性。
例如:闭包

const units = 'μs';(最优秀的写法,即便没有注释也很明确清晰)                
 const units = '\u03bcs'; // 'μs'(容许可是不被推荐)       
 const units = '\u03bcs'; // Greek letter mu, 's'(容许可是简陋而又易于出错)        
 const units = '\u03bcs';(失败的写法,使阅读者难以理解)      
 return '\ufeff' + content; // byte order mark(很好的写法,用转义字符来表示非输出字符,后面必要时会有注释)

提示:因为某些程序可能没法正确处理非ASCII码字符,因此永远不要让你代码所以而失去可读性。
一样,你的代码也会所以而失败须要修复。app

3.源文件框架结构

一个单独源文件必须依次含有如下信息:框架

  1. 许可与版权信息(若是须要)
  2. 文件总览JS注文(@fileoverview JSDoc)(若是须要)
  3. 谷歌模块(goog.module)声明
  4. 谷歌引入文件(goog.require)声明
  5. 文件正文

3.1 许可与版权信息(若是须要)

若是文件有许可与版权信息,必须写在文件结构的第一层。

3.2 文件总览JS注文(@fileoverview JSDoc)(若是须要)

具体格式见7.5节

3.3 谷歌模块(goog.module)声明

全部文件都必须在单独的一行中声明做为谷歌模块的名字:含有谷歌模块命名的那一行不能换行,所以它会被排除在80列(字符)限制以外。
谷歌模块命名的所有就是定义一个命名空间。它做为这个包的名字(用来映射该源文件在整个代码资源目录里位置的标识),一样也能够表明该文件的主要类/枚举类型/接口。
例如:

*goog.module('search.urlHistory.UrlHistoryService');*

3.3.1 模块的层级关系

永远不要将一个命名空间定义为另外一个命名空间的直系子空间。
不被容许的操做:

goog.module('foo.bar');   // 'foo.bar.qux' would be fine, though goog.module('foo.bar.baz')*;

命名空间的层级关系表明了资源目录的层级关系,所以低级子空间必定表明了高级父系资源目录的子目录。这也就说,由于在同一个资源目录里,因此父系命名空间的全部者必须清楚的了解全部的子空间。

3.3.2 谷歌测试专用声明(goog.setTestOnly)

谷歌模块声明后能够再声明为测试专用(goog.setTestOnly())。

3.3.3 谷歌命名空间继承关系声明(goog.module.declareLegacyNamespac)

谷歌模块声明后能够再声明命名空间继承关系(goog.module.declareLegacyNamespace)。(尽可能避免)
例如:

goog.module('my.test.helpers');    
goog.module.declareLegacyNamespace();       
goog.setTestOnly();

谷歌声明命名空间继承关系是用来简易地过渡传统面向对象层级关系命名,可是有一些命名上的限制。由于子系模块的命名必定是在父系模块以后,因此它必定不能是另外一个谷歌模块的子系或者父系。(如:goog.module('parent);和goog.module('parent.child);同时存在会产生安全性问题goog.module('parent);goog.module('parent.child.grandchild')也会有一样的问题

3.3.4 ES6模块

由于到目前为止ES6模块的语言尚未彻底完善,因此如今请不要使用ES6模块(例如,关键词exportimport)。注意,当ES6模块语言彻底完善以后,就可使用那些模块了。

3.4 谷歌引入声明(goog.require)

在模块声明以后,引入的模块经过谷歌引入(goog.require)来声明。每一个声明的引入,都会分配一个固定的别名,或者拆分红几个固定的别名。在代码和注释里,除了声明引入的时候,这个别名是引入模块的惟一合法指代,引入模块的全名不能被使用。模块引入的别名尽量地和该模块全名经过点"."拆分后的最后一部分的名字相一致,可是在可以避免歧义或者明显增长代码可读性的状况下,也能够加上模块全名的其余部分。
若是只是由于一个模块的反作用(代码意义的反作用,而非医学意义,能够理解为"本不该有或者用户意料以外的做用",或者简单理解为为了不编译器警告[warning]的做用)而引入它,能够不用分配名字,可是在代码的其余地方不能出现该模块的全名。
引入声明前后排序规则:首先将带名字的引入声明按首字母排序列出,而后是解构(Destructuring)引入声明按首字母排序列出,最后再把剩余的引入声明单独列出(通常是用到反作用的引入模块)。
提示:没有必要彻底记住这个顺序,而后严格按照这个顺序排列。你能够根据你的IDE来排列你的引入声明,又是较长的模块名或者别名会违反80字符(列)限制,因此这一行不能换行。换句话说,引入声明的那几行不会有80字符(列)限制。
例子:

const MyClass = goog.require('some.package.MyClass');
 const NsMyClass = goog.require('other.ns.MyClass');
 const googAsserts = goog.require('goog.asserts');
 const testingAsserts = goog.require('goog.testing.asserts');
 const than80columns = goog.require('pretend.this.is.longer.than80columns');
 const {clear, forEach, map} = goog.require('goog.array');
 /** @suppress {extraRequire} Initializes MyFramework. */
    goog.require('my.framework.initialization');

不被容许的写法:

const randomName = goog.require('something.else'); // 名字不匹配
const {clear, forEach, map} = // 不要换行 
          goog.require('goog.array');              
  function someFunction() {
          const alias = goog.require('my.long.name.alias'); // 必须在顶部(层)
          // …
    }

3.4.1 谷歌前置声明(goog.forwardDeclare

前置声明不常被用到,可是倒是用来处理循环依赖和引用后期加载代码的有效方法。将全部前置声明一块儿写在引入声明以后。谷歌前置声明听从和谷歌引入声明同样的规则。

3.5 文件的正文

文件的正文如今全部依赖文件之后(中间至少隔一行)。
其中能够包括任何模块内部声明(常亮,变量,类,函数等等)和已引入的符号。

4.格式

术语解释:块状结构指的是类,函数,方法或者任何被大括号包住的内容里的代码。值得注意的是,因为5.2小节和5.3小节的规定,数组和对象也能够被当成块状结构。
小提示:推荐使用clang-format工具。JavaScript社区已经成功完成了clang-format对JavaScript语言的支持,其中也集成了几位著名代码开发者的努力。

4.1 大括号(花括号)

4.1.1 流程控制结构须要使用大括号

全部流程控制结构(例如if, else, for, do, while等等)都须要使用大括号,哪怕其包含的主体代码只有一条指令。第一条指令的非空块状结构必须另起一行。
不被容许的写法:

if (someVeryLongCondition())
 doSomething();
 for (let i = 0; i < foo.length; i++) bar(foo[i]);

例外状况:若是指令能够彻底地用写在一行里,那么能够不用大括号以增长可读性。如下的例子是流程控制结构里惟一能够不用大括号和空行的例子:

if (shortCondition()) return;

4.1.2 非空区块:K&R风格

根据K&R风格对于非空区块和非空块状结构中大括号的规定:

  1. 左大括号(前一个大括号)以前不空行
  2. 左大括号后新起一行
  3. 右大括号(后一个大括号)前新起一行
  4. 在函数,类,类的方法定义的右大括号以后新起一行,而在else, catch, while,逗号,分好,右小括号以后的右大括号不空行。

例如:

class InnerClass {
          constructor() {}

      /** @param {number} foo */
      method(foo) {
        if (condition(foo)) {
          try {
            // Note: this might fail.
            something();
          } catch (err) {
            recover();
          }
        }
      }
    }

4.1.3 空区块:可简化

空区块和空块状结构开始以后能够直接结束,在{}中间不用任何字符,空格和换行,除非它是多区块结构中的一部分(好比if/else/try/catch/finally)。
例如:

function doNothing() {}
不被容许的写法:
    if (condition) {
      // …
    } else if (otherCondition) {} else {
      // …
    }
    
    try {
      // …
    } catch (e) {}

4.2 区块缩进:两个空格

每当新开一个区块或块状结构,增长两个空格的缩进。区块结束以后,缩进恢复到前一级水平。缩进对该区块内的代码和注释一样有效(见4.2节的例子)。

4.2.1 数组声明:可做为块状结构

任何数组均可以按块状结构的格式书写。例如,如下的写法都是有效(不表明所有写法):

const a = [
  0,
  1,
  2,
];

const b =
    [0, 1, 2];

const c = [0, 1, 2];

someMethod(foo, [
  0, 1, 2,
], bar);

也可使用其余组合,特别是用来强调元素之间的分组,而不是只用来减小大数组代码中的垂直长度。

4.2.2 对象声明:可做为块状结构

任何对象均可以按块状结构的格式书写,就像4.2.1节的例子。例如,如下的写法都是有效(不表明所有写法):

const a = {
  a: 0,
  b: 1,
};

const b =
    {a: 0, b: 1};
const c = {a: 0, b: 1};

someMethod(foo, {
  a: 0, b: 1,
}, bar);

4.2.3 类的声明

类的声明(不管是内容声明[declarations]仍是表达式声明[expressions])都像块状结构同样缩进。在类的方法声明和类中的内容声明(表达式结束时仍然须要加分号)的右大括号(后一个大括号)以后不加分号。其中可使用关键字extends,可是不要用@extends的JS注文(JSDoc),除非你继承了一个模板类型(templatized type)。
例如:

class Foo {
  constructor() {
    /** @type {number} */
    this.x = 42;
  }

  /** @return {number} */
  method() {
    return this.x;
  }
}
Foo.Empty = class {};
/** @extends {Foo<string>} */
foo.Bar = class extends Foo {
  /** @override */
  method() {
    return super.method() / 2;
  }
};

/** @interface */
class Frobnicator {
  /** @param {string} message */
  frobnicate(message) {}
}

4.2.4 函数表达式

当声明匿名函数时,函数正文在原有缩进水平上增长两个空格的缩进。
例子:

prefix.something.reallyLongFunctionName('whatever', (a1, a2) => {
  // Indent the function body +2 relative to indentation depth
  // of the 'prefix' statement one line above.
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

some.reallyLongFunctionCall(arg1, arg2, arg3)
    .thatsWrapped()
    .then((result) => {
      // Indent the function body +2 relative to the indentation depth
      // of the '.then()' call.
      if (result) {
        result.use();
      }
    });

4.2.5 Switch语句

就像其余块状结构,该语句的缩进方式也是+2。
开始新的一条Switch标签,格式要像开始一个新的块状结构,新起一行,缩进+2。适当时候能够用块状结构来明确Switch全文范围。
而到下一条Switch标签的开始行,缩进(暂时)还原到原缩进水平。
break和下一条Switch标签之间能够适当地空一行。
例子:

switch (animal) {
  case Animal.BANDERSNATCH:
    handleBandersnatch();
    break;

  case Animal.JABBERWOCK:
    handleJabberwock();
    break;

  default:
    throw new Error('Unknown animal');
}

4.3 表达式

4.3.1 一个表达式一行

每个表达式都要新起一行。

4.3.2 分号结尾

每一个表达式都要用分号结尾。禁止根据分号自动插入。

4.4 字符限制:80

JavaScript有每行最多80字符的限制。除了下面列出的例外状况以外,每行的字符超过80个就会自动换行(具体规则见4.5节)
例外状况:

  1. 该行条件上不支持80字符限制的可能(一个很长的url,JS注文或者复制黏贴来的shell命令)
  2. 谷歌模块(goog.module)和谷歌引入(goog.require)的声明(见3.3节和3.4节)

4.5 自动换行(Line-wrapping

术语解释:自动换行是指将一行的代码分红几行。

没有固定的自动换行方法。一般来说,对于同一代码有好几种合法的自动换行的方法。
注意:虽然官方上自动换行的目的是规避每行的字符限制,可是在不违反字符限制的状况,文件做者也能够根据本身的判断来自动换行。
小提示:精简方法或者变量能够避免自动换行。

4.5.1 断句的位置

自动换行的第一要务:在更高语言优先级的位置断句
更好的写法:

currentEstimate =
calc(currentEstimate + x * currentEstimate) /
    2.0f;

不够优秀的写法:

currentEstimate = calc(currentEstimate + x *
currentEstimate) / 2.0f;

在上面的例子中,语言优先级从高到底依次排列:表达式,除号,函数调用,参数,数字。
运算符换行规则:

  1. 请在运算符以后换行(注意这和JAVA谷歌代码风格不一样)。“.”并非一个运算符,因此不适用上述规则。
  2. 方法和构造函数以后的左圆括号不能换行。
  3. 逗号紧跟前面的代码。

注意:自动换行的主要目的是使代码更清晰,代码不必定要越短越好。

4.5.2 自动换行新的一行缩进至少+4

除非根据缩进规则特别规定以外,自动换行后新的一行在原有的缩进水平上至少增长4个空格。
若是自动换行了多行,则能够适当的调整缩进水平。通常来说,自动换行新的一行缩进会基于4的倍数。只有同一层次的两行才会用一样的缩进水平。

4.5.3节列出了一个使用变量空间来对齐的不被推荐的写法。

4.6 空白

4.6.1 垂直空白(空行)

在如下状况下会出现空行:

  1. 一个类或者对象声明中的两个函数或者方法之间。

例外状况:类中的两个属性声明之间(中间没有其余代码)能够选择性地空一行。这样作能够对属性进行逻辑分组。

  1. 方法中尽可能少用空行的逻辑分组。函数主体的开头和结尾不能空行。
  2. 一个类或者对象声明中第一个方法以前或者最后一个方法以后能够选择性地空行(既不推荐也不反对)
  3. 文件其余须要的位置(例如3.4节所写的)

4.6.2 水平空白(空格)

水平空白根据出现的位置不一样分为三类:头部,尾部,中间。头部的空白(例如缩进)在本文的其余部分都已经解释过了,而尾部的空白禁止出现。
除了其余规则特别规定以及常量,注释,JS注文以外,中间部分的水平空白只能够在下列位置出现:

  1. 用来分隔保留关键词(例如if, for, catch)和以后的左圆括号(()。
  2. 用来分隔右花括号(})和保留关键词(例如else, catch)。
  3. 左花括号({)以前,可是有两个例外:

    a.数组字面量中一个函数的参数或者元素是对象字面量(例如foo({a: [{c:b.}])。
    b.模板扩张中(例如abc${1 + 2}def。

  4. 在二元或者三元运算符的两边
  5. 在逗号和分号以后。注意,逗号和分号以前不能空格。
  6. 在对象字面量中的冒号以后。
  7. 在标识注释的双斜杠(//)的两边。这里可使用多个空格(可是不是必须的)。
  8. 在JS注文(JSDoc)的开始标志以后和结束标志的两边(好比在简易声明或者造型定义中:this.foo = /** @type {number} */ (ba);*或者function(/** string */ foo) {)。

4.6.3 水平对齐:不被鼓励的用法

术语解释:水平对齐是指在标记后加空格让它定位到以前的标记的正下方。

这种写法是容许的,可是谷歌代码风格不鼓励这种方法。在用到水平对齐的时候也甚至不须要保持使用它。
下面是一个不使用对齐和使用了对齐的例子,然后者是不被鼓励的用法。

{
  tiny: 42, // this is great
  longer: 435, // this too
};

{
  tiny:   42,  // permitted, but future edits
  longer: 435, // may leave it unaligned
};

注意:对齐能够增长可读性,可是对后续的维护增长了困难。考虑到后续改写代码可能只会该代码中的一行。修改可能会致使规则容许下格式的崩坏。这经常会错使代码编写者(好比你)调整附近几行的空格,从而致使一系列的格式重写。这样,只是一行的修改就会有一个“爆炸半径”(对附近代码的影响)。这么作最多会让你作一些无用功,可是至少是个失败的历史版本,下降了阅读者的速度,也会致使一些合并冲突。

4.6.4 函数参数

本规则更倾向于把全部函数的参数放在函数名的同一行。若是这么作让代码超出了80字符的限制,那么就必须作基于可读性的自动换行。为了节约空间,最好每行都接近80字符,或者一个参数一行来增长可读性。缩进4个空格。容许和圆括号对齐,可是不推荐。
下列就是最多见的函数参数对齐模式:

// Arguments start on a new line, indented four spaces. Preferred when the
// arguments don't fit on the same line with the function name (or the keyword
// "function") but fit entirely on the second line. Works with very long
// function names, survives renaming without reindenting, low on space.
doSomething(
    descriptiveArgumentOne, descriptiveArgumentTwo, descriptiveArgumentThree) {
  // …
}

// If the argument list is longer, wrap at 80. Uses less vertical space,
// but violates the rectangle rule and is thus not recommended.
doSomething(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // …
}

// Four-space, one argument per line.  Works with long function names,
// survives renaming, and emphasizes each argument.
doSomething(
    veryDescriptiveArgumentNumberOne,
    veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy,
    artichokeDescriptorAdapterIterator) {
  // …
}

4.7 分组括号(Grouping parentheses):推荐的写法

分组括号也能够不加,在代码编写者或评定者都以为这样不会使代码产生歧义并且不会影响代码的可读性的状况下。由于咱们不能确定每一个代码的阅读者都记住了运算符优先表。
delete, typeof, void, return, throw, case, in, of,或 yield的表达式以后不要加无用的圆括号。
类型造型时要加圆括号:/ @type {!Foo} / (foo)*。

4.8注释

这一节讲的是实现注释的规则。JS注文规则请见第7章。

4.8.1 块注释风格

块注释听从上下文一致的缩进规则。它们能够用/* … */ 和 //。对于多行注释(/* … */),含有*的一行中的*必须与其余的对齐,以明确注释没有多出来的文本。若是方法和值的意义不明确,请在以后加上“参数名”注释。

/*
 * This is
 * okay.
 */

// And so
// is this.

/* This is fine, too. */

someFunction(obviousParam, true /* shouldRender */, 'hello' /* name */);

注释不要包含在带星号或者别的字符的框画(boxes drawn)中。
不要在实现注释中JS注文(/** … */)。

5.语言特征

JavaScript有一些暧昧的语言特征(甚至有些危险)。这一章讲述了哪些特征能够用,哪些特征不能够用,以及有限制的特征使用。

5.1 局部变量的声明

5.1.1 使用constlet

使用constlet来声明局部变量。若是一个变量不会改变,默认用const,请不要使用var

5.1.2 一条声明只声明一个变量

一条声明只声明一个变量:请不要使用这样的声明,例如let a = 1, b = 2;

5.1.3 声明时尽可能初始化

咱们不会经常在块状结构的头部声明局部变量。相反,咱们会在第一次使用的附近来声明(并初始化)它以减小代码量。

5.1.4 须要注释声明类型

在声明的这一行或者上一行加上JS类型注文
例如:

const /** !Array<number> */ data = [];

/** @type {!Array<number>} */
const data = [];

5.2 数组字面量

5.2.1 用逗号结尾

在最后一个元素和右方括号之间用逗号结尾,并分行。
例如:

const values = [
  'first value',
  'second value',
];

5.2.2 不要用变长数组构造函数(new)

当对代码进行修改时,这种构造函数很容易出错。
不被容许的写法:

const a1 = new Array(x1, x2, x3);
const a2 = new Array(x1, x2);
const a3 = new Array(x1);
const a4 = new Array()

第三个例子会出现异常:若是x1纯数字,a3就会是个共有x1undefined元素的数组,若是x1是个其余数字,系统会抛出异常,若是x1是个其余类型,a3会是个单元素的数组。
应该这么写:

const a1 = [x1, x2, x3];
const a2 = [x1, x2];
const a3 = [x1];
const a4 = [];

可是用 new Array(length)分配一个肯定长度的数组是容许的。

5.2.3 非数字属性

数组不要定义或使用非数字属性(除了length以外)。可使用map或者Object替代。

5.2.4 解构赋值

解构赋值时能够把数组字面量放在等式的左边(好比从单一数组或者迭代中提取多个值)。在字面量的最后能够包含一个rest元素(紧更着...和变量名)。不用的元素应该省略。

const [a, b, c, ...rest] = generateResults();
let [, b,, d] = someArray;

解构赋值能够做为函数的参数(注意这里参数名能够省略)。在字面量里能够定义默认值。

/** @param {!Array<number>=} param1 */
function optionalDestructuring([a = 4, b = 2] = []) { … };

不被容许的写法:

function badDestructuring([a, b] = [4, 2]) { … };

尽可能用对象字面量来把多个值封装成一个函数参数或者返回值,由于这样能够给元素命名而且声明不一样类型的元素。

5.2.5 展开运算符

数组字面量能够用展开运算符(...)来折叠元素。展开运算符用于代替一个很差的构造器Array.prototype。展开运算符(...)后面不空格。
例如:

[...foo]   // preferred over Array.prototype.slice.call(foo)
[...foo, ...bar]   // preferred over foo.concat(bar)

5.3 对象字面量

5.3.1 用逗号结尾

在最后一个元素和右方括号之间用逗号结尾,并分行。

5.3.2 不要用Object构造器

虽然Object构造器没有Array构造器相同的问题。可是出于一致性的考虑仍然不容许用Object构造器。在这里用对象字面量({}或者{a: 0, b: 1, c: 2})。

5.3.3 带引号键值和不带引号键值不要混用

结构体(含有不带引号键值或符号)和字符文本结构(含有计算属性或者带引号键值),在同一个对象字面量中不要混用二者。
不被容许的写法:

{
  a: 42, // struct-style unquoted key
  'b': 43, // dict-style quoted key
}

5.3.4 计算属性命名

容许使用计算属性(例如{['key' + foo()]: 42}),除非计算属性键值是个Symbol类型(好比[]Symbol.iterator]),它会做为字符文本风格(带引号)。也可使用枚举值做为计算属性键值,可是在同一个字面量里不能与非枚举值混用。

5.3.5 方法简写(速记方法)

在对象字面量中方法能够用方法简写({method() {… }}),其中用一个function或一个箭向函数字面量来代替冒号。
例如:

return {
  stuff: 'candy',
  method() {
    return this.stuff;  // Returns 'candy'
  },
};

注意在方法简写或者function中的this指的是对象字面量自己,可是箭向函数中的this指向的是对象字面量外面的域。
例如:

class {
  getObjectLiteral() {
    this.stuff = 'fruit';
    return {
      stuff: 'candy',
      method: () => this.stuff,  // Returns 'fruit'
    };
  }
}

5.3.6 属性简写(速记属性)

在对象字面量中容许属性简写。
例如:

const foo = 1;
const bar = 2;
const obj = {
  foo,
  bar,
  method() { return this.foo + this.bar; },
};
assertEquals(3, obj.method());

5.3.7 重构

在表达式等式的左边能够用对象重构模式来重构对象或者从单个对象中提取多个值。
重构能够做为函数参数,可是应该保持尽可能简洁:不带引号的速记属性。更深层次的嵌套属性和计算属性尽可能不要使用。重构参数尽可能在左手边定义默认值({str = 'some default'} = {}优于{str} = {str: 'some default'}),而后若是一个重构对象是它本身,必须默认设为{}。JS注文能够给重构参数一个名字(不会用到可是能够被编译器接受)。
例如:

/**
 * @param {string} ordinary
 * @param {{num: (number|undefined), str: (string|undefined)}=} param1
 *     num: The number of times to do something.
 *     str: A string to do stuff to.
 */
function destructured(ordinary, {num, str = 'some default'} = {})

不被容许的写法:

/** @param {{x: {num: (number|undefined), str: (string|undefined)}}} param1 */
function nestedTooDeeply({x: {num, str}}) {};
/** @param {{num: (number|undefined), str: (string|undefined)}=} param1 */
function nonShorthandProperty({num: a, str: b} = {}) {};
/** @param {{a: number, b: number}} param1 */
function computedKey({a, b, [a + b]: c}) {};
/** @param {{a: number, b: string}=} param1 */
function nontrivialDefault({a, b} = {a: 2, b: 4}) {};

谷歌模块引入goog.require也能够重构,这一行不能换行。整个代码不管多长都必须放在一行(见3.4节)。

5.3.8 枚举类型

对象字面量加上@enum注释以后能够定义枚举类型。一旦定义完成后,就不能再给枚举类型增长属性了。枚举类型必须保持不变,枚举类型中的值也不能更改。

/**
 * Supported temperature scales.
 * @enum {string}
 */
const TemperatureScale = {
  CELSIUS: 'celsius',
  FAHRENHEIT: 'fahrenheit',
};

/**
 * An enum with two options.
 * @enum {number}
 */
const Option = {
  /** The option used shall have been the first. */
  FIRST_OPTION: 1,
  /** The second among two options. */
  SECOND_OPTION: 2,
};

5.4 类

5.4.1 构造

可使用构造器来构造类。在设置域或者访问this以前构造子类必须调用super()。接口中不能够调用构造函数。

5.4.2 域

在构造器中设置全部实例的域(例如除了方法以外的全部属性)。永不要用@const再指定注释域。私有域必须带@private的注释,名字用下划线结尾。不要用类的prototype设置域。
例如:

class Foo {
  constructor() {
    /** @private @const {!Bar} */
    this.bar_ = computeBar();
  }
}

注意:构造器构造完成以后不要给一个实例添加或移除属性,这样会大大下降VM优化效果。若是一个域在定义的时候没有初始化,那应该在构造器中把它设为undefined以防止以后类型转变。对象中增长@struct会检查未声明的属性不能访问和增长。类默认有这个属性。

5.4.3 计算属性

类中只有当属性是Symbol类型的时候才能使用计算属性。不容许使用文本属性(如5.3.3节中间定义,也就是带引号或者非符号的计算属性)。任何类均可以定义可迭代的[Symbol.iterator]的方法。
注意:使用其余内置Symbol的时候要注意不要被编译器填充致使其余浏览器失效。

5.4.4 静态方法

在不影响可读性的状况,咱们更推荐使用模型内置局部方法,而不是私有静态方法。
静态方法应该只在基类中被访问。静态方法不能访问包含本构造器或者子类构造器(若是这么作,必须在定义含有@nocollapse)建立实例的变量,也不能在没有定义过该方法的子类中调用。
不被容许的写法:

class Base { /** @nocollapse */ static foo() {} }
class Sub extends Base {}
function callFoo(cls) { cls.foo(); }  // discouraged: don't call static methods dynamically
Sub.foo();  // illegal: don't call static methods on subclasses that don't define it themselves

5.4.5 旧风格的类声明

尽管咱们推荐ES6的类,可是有些状况下ES6的类不可用。
例如:

  1. 若是存在或者即将存在子类,包括建立子类的框架,就不能马上转为ES6语法。若是这种状况下类使用了ES6语法,那么它下属的全部子类都要用ES6语法规范。
  2. 由于ES6在调用super返回值以前没法访问this实例,因此在调用超类构造器以前须要this的框架会出问题。

如下规则仍然使用:适当时应当使用letconst,默认参数(缺省参数),rest参数和箭头函数。
可使用goog.defineClass来模拟一个相似ES6的类声明:

let C = goog.defineClass(S, {
  /**
   * @param {string} value
   */
  constructor(value) {
    S.call(this, 2);
    /** @const */
    this.prop = value;
  },

  /**
   * @param {string} param
   * @return {number}
   */
  method(param) {
    return 0;
  },
});

另外,尽管goog.defineClass更推荐用新语法代码,可是也容许使用相对传统语法的代码。

/**
  * @constructor @extends {S}
  * @param {string} value
  */
function C(value) {
  S.call(this, 2);
  /** @const */
  this.prop = value;
}
goog.inherits(C, S);

/**
 * @param {string} param
 * @return {number}
 */
C.prototype.method = function(param) {
  return 0;
};

若是有超类,应在超类构造器被调用以后在本构造器中定义实例的属性。方法应在构造器的prototype定义。
正肯定义构造器的prototype层级是很困难的。全部,最好使用the Closure Library 中的goog.inherits

5.4.6 不要直接操做prototype

关键词class能够定义比prototype更清晰和可读性更好的类。常规实现代码不须要操做这些对象,尽管这样作能够定义5.4.5节中所说的@record接口和类。不容许混入和修改嵌入对象的prototype
例外:代码框架(例如Polymer或者Angular)有时须要用到prototype,以免求助于更不推荐的工做区。
例外2:定义接口中的类(见5.4.9节)

5.4.7 getter函数与setter函数

请不要使用JavaScript中的getter函数与setter函数。它们会产生潜在的危险和困难,并且只被部分编译器支持。建议使用常规方法来代替。
例外:当使用数据封装的框架(例如Polymer或者Angular),能够少许地使用getter函数与setter函数。可是请注意,这些方法只被部分编译器支持。使用的时候请在数组或者对象字面量中加上get foo()或者set foo(value)定义,若是作不到这些,请加上Object.defineProperties。请不要使用会重命名接口属性的Object.defineProperty。getter函数必定不能改变显状态。
不被容许的写法:

class Foo {
  get next() { return this.nextId++; }
}

5.4.8 toString方法的覆盖

方法toString能够被覆盖,可是定义的方法必定要能无反作用的运行。
特别值得注意的是,在toString中调用其余方法有可能致使无限循环的异常状况。

5.4.9 接口

接口能够用@interface@record来声明。@record声明的接口能够被类和对象字面量显式实现(例如经过@implements),也能够隐式实现。
在接口中的全部非静态方法都必须为空区块。在接口主体以后必须定义域做为prototype上留的底。
例如:

/**
 * Something that can frobnicate.
 * @record
 */
class Frobnicator {
  /**
   * Performs the frobnication according to the given strategy.
   * @param {!FrobnicationStrategy} strategy
   */
  frobnicate(strategy) {}
}

/** @type {number} The number of attempts before giving up. */
Frobnicator.prototype.attempts;

5.5 函数

5.5.1 顶层函数

须要导出的函数能够直接定义在exports对象中,也能够局部定义而后单独导出。咱们推荐使用不导出的函数,不要加上@private定义。
例如:

/** @return {number} */
function helperFunction() {
  return 42;
}
/** @return {number} */
function exportedFunction() {
  return helperFunction() * 2;
}
/**
 * @param {string} arg
 * @return {number}
 */
function anotherExportedFunction(arg) {
  return helperFunction() / arg.length;
}
/** @const */
exports = {exportedFunction, anotherExportedFunction};
/** @param {string} arg */
exports.foo = (arg) => {
  // do some stuff ...
};

5.5.2 嵌套函数和闭包

函数中能够嵌套函数,若是须要给函数命名,必须局部const定义。

5.5.3 箭头函数

箭头函数语法简洁,并且弥补了使用this的不少问题。比起使用关键词function,咱们更推荐箭头函数,特别适用于嵌套函数(见5.3.5节)。
比起f.bind(this),特别是goog.bind(f, this),咱们倾向于使用箭头函数。避免const self = this的写法。箭头函数经常对于有可能会传参的回调颇有效。
箭头的右边能够是个表达式和块状结构。若是只有一个单独的非解构参数,那么参数的圆括号能够省略。
注意:由于若是后期增长函数的参数,省略圆括号会使代码出错,因此即便箭头函数只有一个参数,加上圆括号也是很好的作法。

5.5.4 生成器函数(Generator

生成器函数提供了一些有用的抽象方法,能够在须要的时候使用。
定义生成器函数时,在function后附上*,并与函数名空一格。当使用受权域的时候,在yield后加上*
例如:

/** @return {!Iterator<number>} */
function* gen1() {
  yield 42;
}

/** @return {!Iterator<number>} */
const gen2 = function*() {
  yield* gen1();
}

class SomeClass {
  /** @return {!Iterator<number>} */
  * gen() {
    yield 42;
  }
}

5.5.5 参数

函数参数必须在JS注文中预约义格式化,除非使用了@override,全部类型省略。
内联参数类型必须在参数名前特别说明(好比/ number / foo, / string / bar) => foo + bar)。内联类型注释和@param类型注释在同一个函数声明时不能混用。

5.5.5.1 默认参数(缺省参数)

在参数列中可选参数容许使用等号操做符。就像必须参数同样写可选参数(好比不加opt_前缀),等号两边需加空格。在JS注文可选参数必须在类型声明中加上=后缀,不要用初始化以确保代码明确。就算可选参数的默认值是undefined,也要在函数声明中声明默认值。
例如:

/**
 * @param {string} required This parameter is always needed.
 * @param {string=} optional This parameter can be omitted.
 * @param {!Node=} node Another optional parameter.
 */
function maybeDoSomething(required, optional = '', node = undefined) {}

尽可能少用默认参数。当有较多非天然语序可选参数时,咱们更推荐重构(见5.3.7节)来建立可读性更好的API。
注意:不一样于python的缺省参数,容许使用返回新的可变对象(好比{}和[])的初始化模块由于它会预先设定每次都使用默认值,因此调用之间对象不会共享。
小提示:包括函数调用的任何表达式都会用到初始化模块,因此初始化模块应该尽可能简单。避免初始化模块暴露共享可变域,这容易致使函数调用之间的无心耦合。

5.5.5.2 剩余参数(定参数)

用剩余参数来代替访问arguments。在JS注文中剩余参数须要加一个...前缀。剩余参数必须在参数列表的最后。在参数名和...之间不要空格。不要把它命名成var_args,也不要用arguments来命名参数或局部变量,以免和内置名的混淆。
例如:

/**
 * @param {!Array<string>} array This is an ordinary parameter.
 * @param {...number} numbers The remainder of arguments are all numbers.
 */
function variadic(array, ...numbers) {}

5.5.6 返回

在函数声明以前的JS注文中必须明肯定义返回值类型,除非是@override状况下全部类型都省略。

5.5.7 泛型

定义泛型函数或方法时需在JS注文中加上@template TYPE

5.5.8 展开运算符

函数调用时可使用展开运算符(...)。一个可变函数中一个数组或者它的迭代被分配成了多个参数时,比起Function.prototype.apply,咱们更推荐展开运算符。
例如:

function myFunction(...elements) {}
myFunction(...array, ...iterable, ...generator());

5.6 字符串字面量

5.6.1 单引号

一般状况下,比起双引号,咱们更推荐单引号来修饰字符串字面量。
小提示:若是字符串中含有单引号,考虑使用模板字符串来避免解析错误。
一般状况下,字符串不能跨行。

5.6.2 模板字符串

使用模板字符串来处理复杂的字符串拼接,尤为当处理多条字符串字面量时。

相关文章
相关标签/搜索