[译]js谷歌代码风格(语法和命名部分)

前言:

本文的主要内容是介绍谷歌js代码风格指南第5-6节+第3节的esm ,是我认为对大家最有帮助的部分,和大家分享一下:

我保留了原标题。方便大家在原文中快速定位。

3 Source file structure

3.4 ES modules

3.4.1.1.1 File extensions in import paths

.js 文件扩展名在导入路径中不是可选的,必须始终包含在内

import '../directory/file'; // bad
复制代码
import '../directory/file.js';
复制代码
3.4.1.2 Importing the same file multiple times

不要多次导入同一个文件

// Imports have the same path, 但由于它没有对齐,所以很难看到
import {short} from './long/path/to/a/file.js';
import {aLongNameThatBreaksAlignment} from './long/path/to/a/file.js';
复制代码
3.4.1.3 Naming imports
3.4.1.3.1 Naming module imports

模块导入名称(import * as name)是从导入的文件名派生的小驼峰名称。

import * as fileOne from '../file-one.js';
import * as fileTwo from '../file_two.js';
import * as fileThree from '../filethree.js';
复制代码
import * as libString from './lib/string.js';
import * as math from './math/math.js';
import * as vectorMath from './vector/math.js';
复制代码

3.4.1.3.2 Naming default imports

默认导入名称源自导入的文件名,并遵循 6.2 Rules by identifier type.。

import MyClass from '../my-class.js';
import myFunction from '../my_function.js';
import SOME_CONSTANT from '../someconstant.js';
复制代码

注意:通常这不应该发生,因为此样式指南禁止默认导出,请参阅 3.4.2.1 命名与默认导出。默认导入仅用于导入不符合此样式指南的模块。

3.4.1.3.3 Naming named imports

通常,通过命名导入 (import {name}) 导入的符号应保持相同的名称。避免别名导入(import {SomeThing as SomeOtherThing})。常用于通过使用模块导入 (import *) 或重命名导出本身来修复名称冲突。

import * as bigAnimals from './biganimals.js';
import * as domesticatedAnimals from './domesticatedanimals.js';
​
new bigAnimals.Cat();
new domesticatedAnimals.Cat();
复制代码

如果需要重命名命名导入,则在生成的别名中使用导入模块的文件名路径的组成部分。

import {Cat as BigCat} from './biganimals.js';
import {Cat as DomesticatedCat} from './domesticatedanimals.js';
​
new BigCat();
new DomesticatedCat();
复制代码

3.4.2 Exports

仅当符号要在模块外使用时才会导出。未导出的模块本地符号未声明为 @private,它们的名称也不以下划线结尾。导出和模块本地符号没有规定的顺序。

3.4.2.1 Named vs default exports

在所有代码中使用命名导出。您可以将 export 关键字应用于声明,或使用 export {name};句法。

不要使用默认导出。导入模块必须为这些值命名,这会导致模块间命名不一致。

// Do not use default exports:
export default class Foo { ... } // BAD!
复制代码
// Use named exports:
export class Foo { ... }
复制代码
// Alternate style named exports:
class Foo { ... }
​
export {Foo};
复制代码

注意:不带变量声明,则需要用{}

3.4.2.2 Exporting static container classes and objects

不要为了命名空间而 导出带有静态方法或属性的容器类或对象。

// container.js
// Bad: Container is an exported class that has only static methods and fields.
export class Container {
  /** @return {number} */
  static bar() {
    return 1;
  }
}
​
/** @const {number} */
Container.FOO = 1;
复制代码

相反,导出单个常量和函数:

/** @return {number} */
export function bar() {
  return 1;
}
​
export const /** number */ FOO = 1;
复制代码
3.4.2.3 Mutability of exports

不得在模块初始化之外更改导出的变量。

如果需要变异,还有其他选择,包括 导出 对具有可变字段的对象的常量引用或导出可变数据的访问器函数

// Bad: both foo and mutateFoo are exported and mutated.
export let /** number */ foo = 0;
​
/**
 * Mutates foo.
 */
export function mutateFoo() {
  ++foo;
}
​
/**
 * @param {function(number): number} newMutateFoo
 */
export function setMutateFoo(newMutateFoo) {
  // Exported classes and functions can be mutated!
  mutateFoo = () => {
    foo = newMutateFoo(foo);
  };
}
复制代码
// Good: Rather than export the mutable variables foo and mutateFoo directly,
// instead make them module scoped and export a getter for foo and a wrapper for
// mutateFooFunc.
let /** number */ foo = 0;
let /** function(number): number */ mutateFooFunc = foo => foo + 1;
​
/** @return {number} */
export function getFoo() {
  return foo;
}
​
export function mutateFoo() {
  foo = mutateFooFunc(foo);
}
​
/** @param {function(number): number} mutateFoo */
export function setMutateFoo(mutateFoo) {
  mutateFooFunc = mutateFoo;
}
复制代码

3.4.3 Circular Dependencies in ES modules

不要在 ES 模块之间创建循环,即使 ECMAScript 规范允许这样做。请注意,可以使用 import 和 export 语句创建循环

// a.js
import './b.js';
复制代码
// b.js
import './a.js';
​
// `export from` can cause circular dependencies too!
export {x} from './c.js';
复制代码
// c.js
import './b.js';
​
export let x;
复制代码

5 Language features

JavaScript 包含许多可疑(甚至危险)的特性。本节描述了哪些功能可以使用或不可以使用,以及对其使用的任何其他限制。

5.1.1 Use const and let

使用 const 或 let 声明所有局部变量。默认情况下使用 const,除非需要重新分配变量。不得使用 var 关键字。

5.1.2 One variable per declaration

每个局部变量声明只声明一个变量:诸如 let a = 1, b = 2 之类的声明;没有使用

5.1.3 Declared when needed, initialized as soon as possible

局部变量通常不会在其包含块或类块构造的开始处声明。相反,局部变量在接近它们第一次使用的点(在合理范围内)被声明,以最小化它们的范围

5.1.4 Declare types as needed

JSDoc 类型注释可以添加在声明上方的行上,或者如果没有其他 JSDoc 存在,或者在变量名称之前内联。

Example:

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

/**
 * Some description.
 * @type {!Array<number>}
 */
const data = [];
复制代码

不允许混合使用内联和 JSDoc 样式:编译器只会处理第一个 JsDoc,内联注释将丢失。

/** Some description. */
const /** !Array<number> */ data = [];
复制代码

提示:在很多情况下,编译器可以推断模板化类型,但不能推断其参数。当初始化文字或构造函数调用不包含模板参数类型的任何值时尤其如此(例如,空数组、对象、Map 或 Set),或者是否在闭包中修改了变量。局部变量类型注释在这些情况下特别有用,否则编译器会将模板参数推断为未知。

5.2 Array literals

5.2.1 Use trailing commas

只要最后一个元素和右括号之间有换行符,就包括一个尾随逗号。

const values = [
  'first value',
  'second value',
];
复制代码

5.2.2 Do not use the variadic Array constructor

如果添加或删除参数,构造函数很容易出错。改用文字。

Disallowed:

const a1 = new Array(x1, x2, x3);
const a2 = new Array(x1, x2);
const a3 = new Array(x1);
const a4 = new Array();
复制代码

除了第三种情况外,这按预期工作:如果 x1 是整数,则 a3 是大小为 x1 的数组,其中所有元素都未定义。

如果 x1 是任何非整数数字,则将引发异常,如果是其他任何数字,则它将是一个单元素数组。

Instead, write

const a1 = [x1, x2, x3];
const a2 = [x1, x2];
const a3 = [x1];
const a4 = [];
复制代码

在适当的时候,允许使用 new Array(length) 显式分配给定长度的数组。

5.2.3 Non-numeric properties

不要在数组上定义或使用非数字属性(长度除外)。改用map(或对象)。

5.2.4 Destructuring

可以在赋值的左侧使用数组文字来执行解构(例如从单个数组或可迭代对象中解压缩多个值时)。

可以包含最后一个 rest 元素(在 ... 和变量名称之间没有空格)。如果元素未使用,则应省略它们。

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

解构也可用于函数参数(注意参数名称是必需的但被忽略)。如果解构数组参数是可选的,则始终将 [] 指定为默认值,并在左侧提供默认值:

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

Disallowed:

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

提示:对于将多个值(解)压缩到函数的参数或返回中,尽可能选择对象解构而不是数组解构,因为它允许命名单个元素并为每个元素指定不同的类型。

5.2.5 Spread operator

数组文字可能包含扩展运算符 (...) 以将元素从一个或多个其他可迭代对象中展平。应该使用扩展运算符而不是 Array.prototype 的更笨拙的构造。后面没有空格....

Example:

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

5.3 Object literals

5.3.1 Use trailing commas

只要最后一个属性和右大括号之间有换行符,就包括一个尾随逗号。

5.3.2 Do not use the Object constructor

虽然 Object 没有与 Array 相同的问题,但为了一致性仍然不允许使用。改用对象字面量({} 或 {a: 0, b: 1, c: 2})。

5.3.3 Do not mix quoted and unquoted keys

对象字面量可以表示结构(带有不带引号的键和/或符号)或字典(带有带引号和/或计算的键)。不要在单个对象文字中混合这些键类型。

Disallowed:

{
  width: 42, // struct-style unquoted key
  'maxWidth': 43, // dict-style quoted key
}
复制代码

这也扩展到将属性名称传递给函数,例如 hasOwnProperty。(即使属性的值是 nullundefined,只要属性存在,hasOwnProperty 依旧会返回 true

特别是,这样做会破坏已编译的代码,因为编译器无法重命名/混淆字符串文字。

Disallowed:

/** @type {{width: number, maxWidth: (number|undefined)}} */
const o = {width: 42};
if (o.hasOwnProperty('maxWidth')) {
  ...
}
复制代码

This is best implemented as:

/** @type {{width: number, maxWidth: (number|undefined)}} */
const o = {width: 42};
if (o.maxWidth != null) {
  ...
}
复制代码

5.3.4 Computed property names

允许使用计算属性名称(例如, {['key' + foo()]: 42}) ,并且被视为字典式(带引号的)键(i.e,不得与非带引号的键混合),除非计算属性是一个符号(e.g., [Symbol.iterator]).。

枚举值也可用于计算键,但不应与同一字面中的非枚举键混合使用。

5.3.5 Method shorthand

在对象字面量上定义方法 可以使用方法简写 ({method() {… }}) 代替冒号(后跟函数或箭头函数字面量)。

Example:

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

请注意,方法简写或函数中的 this 指的是对象字面量本身,而箭头函数中的 this 指的是对象字面量之外的范围。

Example:

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

5.3.6 Shorthand properties

对象文字上允许使用速记属性。

Example:

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

5.3.7 Destructuring

对象解构模式可用于赋值的左侧,以执行解构并从单个对象中解压多个值。

解构对象也可用作函数参数,但应尽可能简单:单级不带引号的速记属性。

在参数解构中可能不会使用更深层次的嵌套和计算属性。

在解构参数的左侧指定任何默认值({str = 'some default'} = {},而不是 {str} = {str: 'some default'}),并且如果解构对象是本身是可选的,它必须默认为 {}

可以为解构参数的 JSDoc 指定任何名称(该名称未使用但编译器需要)。

Example:

/**
 * @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'} = {})
复制代码

Disallowed:

/** @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} = {}) {}; // a 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}) {};
复制代码

5.3.8 Enums

枚举是通过将 @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 Classes

5.4.1 Constructors

构造函数是可选的。子类构造函数必须在设置任何字段或以其他方式访问 this 之前调用 super()。接口应该在构造函数中声明非方法属性。

5.4.2 Fields

在构造函数中设置所有具体对象的字段(即除方法之外的所有属性)。注释永远不会用@const 重新分配的字段(这些不必是非常不可变的)。使用适当的可见性注释(@private、@protected、@package)注释非公共字段,并以下划线结束所有 @private 字段的名称。永远不会在具体类的原型上设置字段。

Example:

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

    /** @protected @const {!Baz} */
    this.baz = computeBaz();
  }
}
复制代码

提示:在构造函数完成后,不应向实例添加或删除属性,因为它会严重阻碍虚拟机的优化能力。

如有必要,稍后初始化的字段应在构造函数中显式设置为 undefined 以防止稍后更改形状。

将@struct 添加到对象将检查未添加/访问未声明的属性。类默认添加了这个。

5.4.3 Computed properties

当属性是一个Symbol时,计算属性只能在类中使用。

不允许使用字典样式的属性(即引用或计算的非符号键,如 5.3.3 Do not mix quoted and unquoted keys) 不允许。

应该为逻辑上可迭代的任何类定义 [Symbol.iterator] 方法。除此之外,应谨慎使用 Symbol。

提示:小心使用任何其他内置符号(例如,Symbol.isConcatSpreadable),因为它们不是由编译器填充的,因此在旧浏览器中不起作用。

5.4.4 Static methods

不影响可读性的情况下,更喜欢模块本地函数而不是私有静态方法。

静态方法应该只在基类本身上调用。不应在包含动态实例的变量上调用静态方法,该实例可能是构造函数或子类构造函数(如果这样做,必须用@nocollapse 定义),并且不能直接在没有定义方法本身的子类上调用。

Disallowed:

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

5.4.5 Old-style class declarations

虽然 ES6 类是首选,但在某些情况下 ES6 类可能不可行。例如:

  1. 如果存在或将存在子类,包括创建子类的框架,不能立即更改为使用 ES6 类语法。如果这样的类使用 ES6 语法,则需要修改所有不使用 ES6 类语法的下游子类。
  2. 在调用 超类构造函数之前需要已知 this 值 的框架,因为具有 ES6 超类的构造函数在调用 super 返回之前无法访问实例 this 值。

5.4.6 Do not manipulate prototypes directly

——不要直接操作原型

与定义原型属性相比,class 关键字允许更清晰、更易读的类定义。普通的实现代码没有操作这些对象的业务,

尽管它们对于定义 5.4.5 Old-style class declarations.仍然很有用。

明确禁止混入和修改内置对象的原型。

例外:框架代码(例如 Polymer 或 Angular)可能需要使用原型,并且不应诉诸更糟糕的解决方法来避免这样做。

5.4.7 Getters and Setters

不要使用 JavaScript 的 getter 和 setter 属性。它们可能令人惊讶且难以推理,并且在编译器中的支持有限。用普通方法代替。

例外:在某些情况下,定义 getter 或 setter 是不可避免的(例如,Angular 和 Polymer 等数据绑定框架,或为了与无法调整的外部 API 兼容)。仅在这些情况下,可以谨慎使用 getter 和 setter,前提是它们是用 get 和 set 速记方法关键字或 Object.defineProperties 定义的 (不是 Object.defineProperty,它会干扰属性重命名)。 Getter 不得更改可观察状态。

Disallowed:

class Foo {
  get next() { return this.nextId++; }
}
复制代码

5.4.8 Overriding toString

toString 方法可以被覆盖,但必须始终成功并且永远不会有明显的副作用。

提示:尤其要注意从 toString 调用其他方法,因为异常情况可能导致无限循环。(toString经常自动调用)

5.4.9 Interfaces

接口可以用@interface 或@record 声明。使用@record 声明的接口可以显式(即通过@implements)或由类或对象字面量隐式实现。

接口上的所有非静态方法体都必须是空块。字段必须在类构造函数中声明为未初始化的成员。

/**
 * Something that can frobnicate.(口齿不清)
 * @record
 */
class Frobnicator {
  constructor() {
    /** @type {number} The number of attempts before giving up. */
    this.attempts;
  }

  /**
   * Performs the frobnication according to the given strategy.
   * @param {!FrobnicationStrategy} strategy
   */
  frobnicate(strategy) {}
}
复制代码

5.4.10 Abstract Classesc

适当时使用抽象类。抽象类和方法必须用@abstract 注释。不要使用 goog.abstractMethod。请参阅 abstract classes and methods.

5.5 Functions

5.5.1 Top-level functions

顶级函数可以直接在导出对象上定义,或者在本地声明并可选地导出。

Examples:

/** @param {string} str */
exports.processString = (str) => {
  // Process the string.
};
/** @param {string} str */
const processString = (str) => {
  // Process the string.
};

exports = {processString};
复制代码

5.5.2 Nested functions and closures

函数可能包含嵌套的函数定义。如果给函数命名有用,则应将其分配给局部const。

5.5.3 Arrow functions

箭头函数提供了简洁的函数语法并简化了嵌套函数的范围。优先使用箭头函数而不是 function 关键字,特别是对于嵌套函数(但请参阅 5.3.5 方法简写)。

比其他 this 作用域方法更喜欢箭头函数,例如 f.bind(this)、goog.bind(f, this) 和 const self = this.箭头函数对于调用回调特别有用,因为它们允许明确指定要传递给回调的参数,而bind将盲目地传递所有参数。

箭头的左侧包含零个或多个参数。如果只有一个不用解构的参数,则参数周围的括号是可选的。当使用括号时,可以指定内联参数类型(参见 7.8 方法和函数注释)。

提示:即使对于单参数箭头函数也始终使用括号可以避免添加参数但忘记添加括号可能导致可解析代码不再按预期工作的情况。

箭头的右侧包含函数的主体。默认情况下,主体是一个块语句(零个或多个用花括号括起来的语句).在以下情况下,主体也可以是隐式返回的单个表达式:程序逻辑需要返回一个值,或者 void 运算符位于单个函数或方法调用之前(使用 void 确保返回 undefined,防止泄漏值,并传达意图)。如果可以提高可读性(例如,对于简短或简单的表达式),最好使用单一表达式形式。

Examples:

/**
 * Arrow functions can be documented just like normal functions.
 * @param {number} numParam A number to add.
 * @param {string} strParam Another number to add that happens to be a string.
 * @return {number} The sum of the two parameters.
 */
const moduleLocalFunc = (numParam, strParam) => numParam + Number(strParam);

// Uses the single expression syntax with `void` because the program logic does
// not require returning a value.
getValue((result) => void alert(`Got ${result}`));

class CallbackExample {
  constructor() {
    /** @private {number} */
    this.cachedValue_ = 0;

    // For inline callbacks, you can use inline typing for parameters.
    // Uses a block statement because the value of the single expression should
    // not be returned and the expression is not a single function call.
    getNullableValue((/** ?number */ result) => {
      this.cachedValue_ = result == null ? 0 : result;
    });
  }
}
复制代码

Disallowed:

/**
 * A function with no params and no returned value.
 * This single expression body usage is illegal because the program logic does
 * not require returning a value and we're missing the `void` operator.
 */
const moduleLocalFunc = () => anotherFunction();
复制代码

5.5.4 Generators

生成器启用了许多有用的抽象,可以根据需要使用

定义生成器函数时,将 * 附加到 function 关键字(如果存在),并用空格将其与函数名称分开。使用delegating yield 时,将 * 附加到 yield 关键字。

Example:

/** @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 Parameter and return types

函数参数和返回类型通常应该用 JSDoc 注释记录。有关更多信息,请参阅 7.8 方法和函数注释。

5.5.5.1 Default parameters

当使用使用参数列表中的等于号, 可选参数 可用。

  • 可选参数必须在等于号的两侧包含空格,
  • 命名与所需参数完全相同(即,不以 opt_ 为前缀),
  • 在它们的 JSDoc 类型中使用 = 后缀,
  • 放在必需的参数之后,
  • 并且不要使用产生可观察到的副作用的初始化器。
  • 具体函数的所有可选参数都必须具有默认值,即使该值undefined。
  • 与具体函数相反,抽象和接口方法必须省略默认参数值。

Example:

/**
 * @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) {}

/** @interface */
class MyInterface {
  /**
   * Interface and abstract methods must omit default parameter values.
   * @param {string=} optional
   */
  someMethod(optional) {}
}
复制代码

谨慎使用默认参数。当有多个没有自然顺序的可选参数时,更喜欢解构(如 5.3.7 解构)来创建可读的 API。

注意:与 Python 的默认参数不同,使用返回新可变对象(例如 {} 或 [])的初始化器是可以的,因为每次使用默认值时都会评估初始值设定项,因此不会在调用之间共享单个对象。

提示:虽然可以使用包括函数调用在内的任意表达式作为初始值设定项,但它们应尽可能简单。避免暴露共享可变状态的初始化程序,因为这很容易在函数调用之间引入意外耦合。

5.5.5.2 Rest parameters

使用 rest 参数而不是访问 arguments。Rest 参数在其 JSDoc 中使用 ... 前缀键入。rest 参数必须是列表中的最后一个参数。 ... 和参数名称之间没有空格。不要将rest 参数命名为 var_args。永远不要命名局部变量或参数为arguments,这会混淆内置名称。

Example:

/**
 * @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 Generics

必要时在函数或方法定义上方的 JSDoc 中使用 @template TYPE 声明通用函数和方法

5.5.7 Spread operator

函数调用可以使用展开运算符 (...)。当数组或可迭代对象解包为可变参数函数的多个参数时,优先使用扩展运算符而不是 Function.prototype.apply。后面没有空格....

Example:

function myFunction(...elements) {}
myFunction(...array, ...iterable, ...generator());
复制代码

5.6 String literals

5.6.1 Use single quotes

普通字符串文字用单引号 (') 分隔,而不是双引号 (")。

提示:如果字符串包含单引号字符,请考虑使用模板字符串(template string)以避免对引号进行转义。

普通字符串文字不能跨越多行。

5.6.2 Template literals

在复杂的字符串连接上使用模板字面量(以 ` 分隔),尤其是在涉及多个字符串文字时。模板文字可能跨越多行。

如果模板文字跨越多行,则它不需要跟随封闭块的缩进,尽管添加的空格无关紧要。

Example:

function arithmetic(a, b) {
  return `Here is a table of arithmetic operations:
${a} + ${b} = ${a + b}
${a} - ${b} = ${a - b}
${a} * ${b} = ${a * b}
${a} / ${b} = ${a / b}`;
}
复制代码

5.6.3 No line continuations

不要在普通或模板字符串文字中使用续行(即,用反斜杠结束字符串文字内的一行)。尽管 ES5 允许这样做,但如果斜杠后面出现任何尾随空格,它可能会导致棘手的错误,并且对读者来说不太明显。

Disallowed:

const longString = 'This is a very long string that far exceeds the 80 \
    column limit. It unfortunately contains long stretches of spaces due \
    to how the continued lines are indented.';
复制代码

Instead, write

const longString = 'This is a very long string that far exceeds the 80 ' +
    'column limit. It does not contain long stretches of spaces since ' +
    'the concatenated strings are cleaner.';
复制代码

5.7 Number literals

数字可以用十进制、十六进制、八进制或二进制指定。对十六进制、八进制和二进制分别使用带有小写字母的 0x、0o 和 0b 前缀。永远不要包含前导零,除非它后面紧跟 x、o 或 b。

5.8 Control structures

5.8.1 For loops

在 ES6 中,该语言现在拥有三种不同类型的 for 循环。所有都可以使用,但在可能的情况下应该首选 for-of 循环。

for-in 循环只能用于 字典形式的对象(请参阅 5.3.3 不要混合引用和未引用的键),并且不应用于迭代数组。Object.prototype.hasOwnProperty 应该在 for-in 循环中使用以排除不需要的原型属性。如果可能,更喜欢 for-of 和 Object.keys 而不是 for-in。

5.8.2 Exceptions

异常是语言的重要组成部分,应在发生异常情况时使用。总是抛出ErrorError的子类:永远不要抛出字符串文字或其他对象。构造Error时始终使用 new

这种处理扩展到 Promise 拒绝值,因为 Promise.reject(obj) 等价于 throw obj;在异步函数中。

自定义异常提供了一种很好的方式来传达来自函数的附加错误信息。它们应该在本地Error类型不足的地方定义和使用。

与临时错误处理方法相比,更喜欢抛出异常(例如传递错误容器引用类型,或返回具有错误属性的对象)。

5.8.2.1 Empty catch blocks

对捕获的异常不做任何事情是很少正确的。当确实合适在 catch 块中不采取任何行动时,在注释中解释了合理的原因。

try {
  return handleNumericResponse(response);
} catch (ok) {
  // it's not numeric; that's fine, just continue
}
return handleTextResponse(response);
复制代码

Disallowed:

  try {
    shouldFail();
    fail('expected an error');
  } catch (expected) {
  }
复制代码

Tip: 与其他一些语言不同,上面的模式根本不起作用,因为这会捕获fail抛出的错误。请改用 assertThrows()。

5.8.3 Switch statements

术语说明:switch 块的大括号内是一个或多个语句组。每个语句组由一个或多个开关标签(case FOO: 或 default:)组成,后跟一个或多个语句。

5.8.3.1 Fall-through: commented

在 switch 块中,每个语句组要么突然终止(带有break、return或throw exception),要么用注释标记以指示执行将或可能继续到下一个语句组。任何传达失败思想的评论就足够了(通常是 // fall through)。 switch 块的最后一个语句组中不需要此特殊注释。

Example:

switch (input) {
  case 1:
  case 2:
    prepareOneOrTwo();
  // fall through
  case 3:
    handleOneTwoOrThree();
    break;
  default:
    handleLargeNumber(input);
}
复制代码
5.8.3.2 The default case is present

每个 switch 语句都包含一个 default语句组,即使它不包含任何代码。 default语句组必须在最后。

5.9 this

仅在类构造函数和方法中、在类构造函数和方法中定义的箭头函数中 或 在立即封闭函数的 JSDoc 中声明了显式 @this 的函数中使用 this。

永远不要使用 this 来引用全局对象、eval 的上下文、事件的目标 或 不必要的 call()ed 或 apply()ed 函数。

5.10 Equality Checks

一直使用恒等运算符 (===/!==) ,除非在下面记录的情况下。

5.10.1 Exceptions Where Coercion is Desirable

——需要强制的例外情况

捕获空值和未定义值:

if (someObjectOrPrimitive == null) {
  // Checking for null catches both null and undefined for objects and
  // primitives, but does not catch other falsy values like 0 or the empty
  // string.
}
复制代码

5.11 Disallowed features

5.11.1 with

不要使用 with 关键字。它使您的代码更难理解并且自 ES5 以来已在严格模式下被禁止

5.11.2 Dynamic code evaluation

不要使用 eval 或 Function(...string) 构造函数(代码加载器除外)。这些功能具有潜在危险,在 CSP 环境中根本不起作用。

5.11.3 Automatic semicolon insertion

总是用分号终止语句(函数和类声明除外,如上所述)。

5.11.4 Non-standard features

不要使用非标准功能。这包括已删除的旧功能(例如,WeakMap.clear)、尚未标准化的新功能(例如,当前的 TC39 工作草案、任何阶段的提案,或提出但尚未完成的 Web 标准),或仅在某些浏览器中实现的专有功能。仅使用当前 ECMA-262 或 WHATWG 标准中定义的功能。(请注意,针对特定 API 编写的项目,例如 Chrome 扩展程序或 Node.js,显然可以使用这些 API)。禁止使用非标准语言的“扩展”(例如某些外部转译器提供的扩展)。

5.11.5 Wrapper objects for primitive types

不要在原始对象包装器(Boolean、Number、String、Symbol)上使用 new,也不要将它们包含在类型注释中。

Disallowed:

const /** Boolean */ x = new Boolean(false);
if (x) alert(typeof x);  // alerts 'object' - WAT?
复制代码

包装器可以被称为强制函数(比使用 + 或连接空字符串更可取)或创建符号。

Example:

const /** boolean */ x = Boolean(0);
if (!x) alert(typeof x);  // alerts 'boolean', as expected
复制代码

5.11.6 Modifying builtin objects

永远不要修改内置类型,无论是通过向其构造函数或原型添加方法。避免依赖执行此操作的库。请注意,JSCompiler 的运行时库将尽可能提供符合标准的 polyfill;没有其他东西可以修改内置对象。

除非绝对必要(例如第三方 API 要求),否则不要向全局对象添加符号。

5.11.7 Omitting () when invoking a constructor

切勿在不使用括号 () 的情况下在 new 语句中调用构造函数。

Disallowed:

new Foo;
复制代码

Use instead:

new Foo();
复制代码

Omitting parentheses can lead to subtle mistakes. These two lines are not equivalent:

new Foo().Bar();
new Foo.Bar();
复制代码

省略括号会导致细微的错误。这两行是不等价的:

new Foo().Bar();
new Foo.Bar();
复制代码

6 Naming

6.1 Rules common to all identifiers

标识符只使用ASCII字母和数字,在下面提到的少数情况下,使用下划线和美元符号(少用)。(当像Angular这样的框架需要时)。在合理范围内,给出尽可能具有描述性的名称。不要担心节省水平空间,因为让新读者立即理解您的代码更为重要。

在合理范围内,给出尽可能具有描述性的名称。不要担心节省水平空间,因为让新读者立即理解您的代码更为重要。不要使用项目之外的读者不熟悉或不熟悉的缩写,也不要通过删除单词中的字母来缩写。

errorCount          // No abbreviation.
dnsConnectionIndex  // Most people know what "DNS" stands for.
referrerUrl         // Ditto for "URL".
customerId          // "Id" is both ubiquitous and unlikely to be misunderstood.
复制代码

Disallowed:

n                   // Meaningless.
nErr                // Ambiguous abbreviation.
nCompConns          // Ambiguous abbreviation.
wgcConnections      // Only your group knows what this stands for.
pcReader            // Lots of things can be abbreviated "pc".
cstmrId             // Deletes internal letters.
kSecondsPerDay      // Do not use Hungarian notation.
复制代码

6.2 Rules by identifier type

6.2.1 Package names

包名都是小驼峰lowerCamelCase的。例如,my.exampleCode.deepSpace,但不是 my.examplecode.deepspace 或 my.example_code.deep_space。

6.2.2 Class names

类、接口、记录(record)和 类型定义(typedef )名称均UpperCamelCase命名。未导出的类只是本地类:它们没有被标记为 @private,因此没有用尾随下划线命名。

类型名称通常是名词或名词短语。例如,RequestImmutableListVisibilityMode。此外,接口名称有时可能是形容词或形容词短语(例如,Readable)。

6.2.3 Method names

方法名称以 lowerCamelCase 命名。 @private 方法的名称必须以下划线结尾。

方法名称通常是动词或动词短语。例如,sendMessagestop_。属性的 getter 和 setter 方法从来都不是必需的,但如果使用它们,它们应该命名为 getFoo (或可选 isFoo 或 hasFoo 用于布尔值),或 setFoo(value) 用于 setter。

下划线也可能出现在 JsUnit 测试方法名称中以分隔名称的逻辑组件。一种典型的模式是 test<MethodUnderTest>_<state>_<expectedOutcome>,例如 testPop_emptyStack_throws。没有一种正确的方法来命名测试方法

6.2.4 Enum names

枚举名称用UpperCamelCase,类似于类,通常应该是单数名词。枚举中的单个项目在 CONSTANT_CASE 中命名。

6.2.5 Constant names

常量名称使用 CONSTANT_CASE:所有大写字母,单词用下划线分隔。没有理由用尾随下划线命名常量,因为私有静态属性可以被(隐式私有)模块本地变量替换。

6.2.5.1 Definition of “constant”

每个常量都是@const 静态属性或模块局部const声明( @const static property or a module-local const declaration) ,但并非所有 @const 静态属性和模块局部常量都是常量。

译者:@const 静态属性应该是 某对象的属性,模块局部const就是在普通地在js文件声明的变量

在选择常量 case 之前,请考虑该字段是否真的感觉像是一个 deeply immutable深度不可变 的常量。例如,如果该实例的任何可观察状态可以改变,它几乎肯定不是一个常数。仅仅打算永远不改变对象通常是不够的。

// Constants
const NUMBER = 5;
/** @const */ exports.NAMES = ImmutableList.of('Ed', 'Ann');
/** @enum */ exports.SomeEnum = { ENUM_CONSTANT: 'value' };

// Not constants
let letVariable = 'non-const';
class MyClass { constructor() { /** @const {string} */ this.nonStatic = 'non-static'; } };
/** @type {string} */ MyClass.staticButMutable = 'not @const, can be reassigned';
const /** Set<string> */ mutableCollection = new Set();
const /** ImmutableSet<SomeMutableType> */ mutableElements = ImmutableSet.of(mutable);
const Foo = goog.require('my.Foo');  // mirrors imported name
const logger = log.getLogger('loggers.are.not.immutable');
复制代码

常量的名称通常是名词或名词短语。

6.2.5.2 Local aliases

只要本地别名提高了完全限定名称的可读性,就应该使用它们。别名也可以在函数中使用。别名必须是const

Examples:

const staticHelper = importedNamespace.staticHelper;
const CONSTANT_NAME = ImportedClass.CONSTANT_NAME;
const {assert, assertInstanceof} = asserts;
复制代码

6.2.6 Non-constant field names

非常量字段名称(静态或其他)以小驼峰式lowerCamelCase书写,私有字段尾随下划线。

这些名称通常是名词或名词短语。例如,computedValues 或 index_。

6.2.7 Parameter names

参数名称以小驼峰lowerCamelCase命名。请注意,即使参数需要构造函数,这也适用。

公共方法中不应使用单字符参数名称。

例外:当第三方框架需要时,参数名称可能以 $ 开头。此例外不适用于任何其他标识符(例如局部变量或属性)。

6.2.8 Local variable names

如上所述,局部变量名称以小驼峰命名,模块局部(顶级)常量除外。

函数作用域中的常量仍然以小驼峰命名。

请注意,即使变量包含构造函数,也会使用小驼峰

6.2.9 Template parameter names

模板参数名称应简洁,单个单词或单个字母的标识符,并且必须全部大写,例如 TYPE 或 THIS.

译者也不知道什么是Template parameter names

6.2.10 Module-local names

未导出的模块本地名称是隐式私有的。

它们没有被标记为@private 并且不以下划线结尾。

这适用于类、函数、变量、常量、枚举和其他模块本地标识符。

6.3 Camel case: defined

有时,将英语短语转换为驼峰式大小写的合理方法不止一种,例如存在首字母缩略词或 IPv6 或 iOS 等不寻常结构时。为了提高可预测性,Google Style 指定了以下(几乎)确定性方案。

从名字的散文形式开始:

  1. 将短语转换为纯 ASCII 并删除所有撇号。例如,”Müller's algorithm “可能会变成 ”Muellers algorithm“。
  2. 将此结果分成单词,在空格和任何剩余的标点符号(通常是连字符)上拆分。
    1. 建议:如果任何单词已经在常用的情况下具有传统的驼峰式外观,请将其拆分为其组成部分(例如,”AdWords “成为“ad words”)。请注意,像 iOS 这样的词本身并不是真正意义上的驼峰式;它无视任何约定,因此此建议不适用。
  3. 现在小写所有内容(包括首字母缩写词),然后只大写以下内容的第一个字符:
    1. ...每个单词,产生大驼峰大小写,或
    2. ... 除了第一个之外的每个单词,产生小驼峰式(和1一个意思)
  4. 最后,将所有单词连接成一个标识符。

请注意,几乎完全忽略了原始单词的大小写。

Examples:

Prose form Correct Incorrect
XML HTTP request XmlHttpRequest XMLHTTPRequest
new customer ID newCustomerId newCustomerID
inner stopwatch innerStopwatch innerStopWatch
supports IPv6 on iOS? supportsIpv6OnIos supportsIPv6OnIOS
YouTube importer YouTubeImporter YoutubeImporter*

可以接受,但不推荐。

注意:有些词在英语中有含糊不清的连字符:例如 nonempty 和 non-empty 都是正确的,因此方法名称 checkNonempty 和 checkNonEmpty 同样都是正确的。

猜你喜欢

转载自juejin.im/post/7012976447014830087