5 语言特性
JavaScript 包含许多可疑的(甚至危险的)特性。本节描述了哪些特性可以使用或不可以使用,以及使用时的任何额外约束。
本风格指南中未讨论的语言特性可以使用,对其用法没有推荐。
5.1 局部变量声明
5.1.1 使用 const 和 let
使用 const 或 let 声明所有局部变量。默认使用
const,除非变量需要被重新赋值。不得使用 var 关键字。
5.1.2 每次声明一个变量
每个局部变量声明只声明一个变量:不使用 let a = 1, b = 2; 这样的声明。
5.1.3 在需要时声明,尽快初始化
局部变量不习惯性地在其所在块或块状结构的开头声明。相反,局部变量在其首次使用的位置附近声明(在合理范围内),以最小化其作用域,并尽快初始化。
5.1.4 根据需要声明类型
JSDoc 类型注解可以添加在声明上方的行上,或者如果没有其他 JSDoc,可以在变量名之前内联。
示例:
const /** !Array<number> */ data = [];
/**
* Some description.
* @type {!Array<number>}
*/
const data = [];不允许混合使用内联和 JSDoc 风格:编译器将只处理第一个 JSDoc,内联注解将丢失。
/** Some description. */
const /** !Array<number> */ data = [];提示:在很多情况下,编译器可以推断模板化类型但无法推断其参数。当初始化字面量或构造函数调用不包含模板参数类型的任何值时(例如,空数组、对象、Map
或
Set),或者当变量在闭包(closure)中被修改时,尤其如此。在这些情况下,局部变量类型注解特别有用,否则编译器将推断模板参数为
unknown。
5.2 数组字面量
5.2.1 使用尾随逗号
每当最后一个元素和右括号之间有换行时,包含尾随逗号。
示例:
const values = [
'first value',
'second value',
];5.2.2 不要使用可变参数的 Array 构造函数
构造函数在添加或删除参数时容易出错。请使用字面量代替。
不允许:
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 的数组,其中所有元素都是 undefined。如果 x1
是任何其他数字,则会抛出异常,如果它是其他任何东西,则它将是一个单元素数组。
相反,请这样写
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 解构(Destructuring)
数组字面量可以用在赋值的左侧来执行解构(例如从单个数组或可迭代对象中解包多个值)。可以包含最终的 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 展开运算符(Spread operator)
数组字面量可以包含展开运算符(...)来将一个或多个其他可迭代对象的元素展开。应使用展开运算符而不是
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
相同的问题,但为了一致性仍然不允许使用。请使用对象字面量({} 或
{a: 0, b: 1, c: 2})代替。
5.3.3 不要混合使用引号和非引号键
对象字面量可以表示结构体(structs)(使用非引号键和/或符号)或字典(dicts)(使用引号和/或计算键)。不要在单个对象字面量中混合这些键类型。
不允许:
{
width: 42, // struct-style unquoted key
'maxWidth': 43, // dict-style quoted key
}这也延伸到将属性名传递给函数,如
hasOwnProperty。特别是,这样做会在编译后的代码中出问题,因为编译器无法重命名/混淆字符串字面量。
不允许:
/** @type {{width: number, maxWidth: (number|undefined)}} */
const o = {width: 42};
if (o.hasOwnProperty('maxWidth')) {
...
}最好这样实现:
/** @type {{width: number, maxWidth: (number|undefined)}} */
const o = {width: 42};
if (o.maxWidth != null) {
...
}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'}),如果解构的对象本身是可选的,它必须默认为
{}。解构参数的 JSDoc 可以使用任何名称(该名称未使用但编译器需要)。
示例:
/**
* @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
语句,在这种情况下不得换行:整个语句占一行,无论它有多长(参见
??)。
5.3.8 枚举(Enums)
枚举通过向对象字面量添加 @enum
注解来定义。枚举必须是模块本地的或直接赋值在 exports
上,不能嵌套在类型或对象下。
枚举定义后不得添加额外的属性。枚举必须是常量。所有枚举值必须是字符串字面量或数字。
/**
* Supported temperature scales.
* @enum {string}
*/
const TemperatureScale = {
CELSIUS: 'celsius',
FAHRENHEIT: 'fahrenheit',
};
/**
* An enum with two values.
* @enum {number}
*/
const Value = {
/** The value used shall have been the first. */
FIRST_VALUE: 1,
/** The second among two values. */
SECOND_VALUE: 2,
};对于字符串枚举,所有值必须是静态初始化的,不得使用算术运算符、模板字面量替换、函数调用或甚至变量引用来计算。
const ABSOLUTE_ZERO = '-273°F';
/**
* Not supported computed values in string enum.
* @enum {string}
*/
const TemperatureInFahrenheit = {
MIN_POSSIBLE: ABSOLUTE_ZERO,
ZERO_FAHRENHEIT: 0 + '°F',
ONE_FAHRENHEIT: `${Values.FIRST_VALUE}°F`,
TWO_FAHRENHEIT: Values.SECOND_VALUE + '°F',
SOME_FAHRENHEIT: getTemperatureInFahrenheit() + '°F',
};注意:虽然 TypeScript 支持更多的枚举值模式(例如 A: 'a'+'b'
等),但仅允许字符串字面量和数字作为枚举值的限制是为了帮助迁移到
TypeScript。对于复杂的值,考虑使用不带 @enum 的 const 对象。
5.4 类(Classes)
5.4.1 构造函数
构造函数是可选的。子类构造函数必须在设置任何字段或以其他方式访问 this
之前调用 super()。接口应在构造函数中声明非方法属性。
5.4.2 字段
在构造函数中定义具体对象的所有字段(即方法以外的所有属性)。用 @const
标注从不重新赋值的字段(这些不需要是深度不可变的)。用适当的可见性注解标注非公开字段(@private、@protected、@package)。@private
字段的名称可以选择性地以下划线结尾。字段不得定义在构造函数中的嵌套作用域内,也不得定义在具体类的
prototype 上。
示例:
class Foo {
constructor() {
/** @private @const {!Bar} */
this.bar_ = computeBar();
/** @protected @const {!Baz} */
this.baz = computeBaz();
}
}提示:构造函数完成后,不应再向实例添加或从实例中删除属性,因为这会严重妨碍虚拟机优化的能力。如有必要,稍后初始化的字段应在构造函数中显式设置为
undefined,以防止后续的形状变化。向对象添加 @struct
将检查是否添加/访问了未声明的属性。类默认添加了此注解。
5.4.3 计算属性
计算属性只能在属性是 symbol
时在类中使用。字典风格的属性(即引号或计算的非 symbol 键,如
??
中定义的)不允许使用。应为任何逻辑上可迭代的类定义 [Symbol.iterator]
方法。除此之外,Symbol 应谨慎使用。
提示:谨慎使用任何其他内置 symbol(例如
Symbol.isConcatSpreadable),因为它们不会被编译器进行
polyfill 处理,因此在旧浏览器中无法工作。
5.4.4 静态方法
在不影响可读性的情况下,优先使用模块本地函数而不是私有静态方法。
代码不应依赖静态方法的动态分派,因为这会干扰 Closure Compiler
的优化。静态方法应只在基类本身上调用。静态方法不应在包含可能是构造函数或子类构造函数的动态实例的变量上调用(如果这样做必须用
@nocollapse
定义),也不得直接在未定义该方法本身的子类上调用。不要在静态方法中访问
this。
不允许:
// Context for the examples below; by itself this code is allowed.
class Base {
/** @nocollapse */ static foo() {}
}
class Sub extends Base {}
// discouraged: don't call static methods dynamically
function callFoo(cls) { cls.foo(); }
// Disallowed: don't call static methods on subclasses that don't define it themselves
Sub.foo();
// Disallowed: don't access this in static methods.
class Clazz {
static foo() {
return this.staticField;
}
}
Class.staticField = 1;5.4.5 旧式类声明
虽然 ES6 类是首选,但在某些情况下 ES6 类可能不可行。例如:
-
如果存在或将存在子类(包括创建子类的框架),这些子类无法立即更改为使用 ES6 类语法。如果这样的类使用 ES6 语法,则所有不使用 ES6 类语法的下游子类都需要修改。
-
需要在调用超类构造函数之前使用已知
this值的框架,因为 ES6 super 类的构造函数在super调用返回之前无法访问实例this值。
在所有其他方面,风格指南仍然适用于此代码:let、const、默认参数、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;
};每个实例的属性应在调用超类构造函数之后(如果有超类)在构造函数中定义。方法应在构造函数的原型上定义。
正确定义构造函数原型层级结构比最初看起来更难!因此,最好使用 Closure
Library 中的 goog.inherits。
5.4.6 不要直接操作 prototype
class 关键字允许比定义 prototype
属性更清晰、更可读的类定义。普通实现代码没有理由操纵这些对象,尽管它们对于定义
?? 中定义的类仍然有用。明确禁止使用 mixin(混入)和修改内置对象的原型。
例外:框架代码(如 Polymer 或 Angular)可能需要使用
prototype,不应为了避免这样做而采用更糟糕的变通方法。
5.4.7 Getter 和 Setter
不要使用 JavaScript getter 和 setter 属性 。它们可能令人意外且难以推理,并且编译器支持有限。请提供普通方法代替。
例外:在某些情况下,定义 getter 或 setter
是不可避免的(例如数据绑定框架如 Angular 和
Polymer,或为了与无法调整的外部 API 兼容)。仅在这些情况下,getter 和
setter 可以谨慎使用,前提是它们使用 get 和 set 简写方法关键字或
Object.defineProperties(而不是
Object.defineProperty,因为它会干扰属性重命名)来定义。Getter
不得改变可观察的状态。
不允许:
class Foo {
get next() { return this.nextId++; }
}5.4.8 重写 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 Classes)
在适当时使用抽象类。抽象类和方法必须用 @abstract 注解。不要使用
goog.abstractMethod。参见
抽象类和方法 。
5.4.11 不要创建静态容器类
不要为了命名空间目的而使用仅具有静态方法或属性的容器类。
// container.js
// Bad: Container is an exported class that has only static methods and fields.
class Container {
/** @return {number} */
static bar() {
return 1;
}
}
/** @const {number} */
Container.FOO = 1;
exports = {Container};相反,导出单独的常量和函数:
/** @return {number} */
exports.bar = () => {
return 1;
}
/** @const {number} */
exports.FOO = 1;5.4.12 不要定义嵌套命名空间
不要在另一个模块本地名称上定义嵌套类型(例如类、typedef、枚举、接口)。
// foo.js
goog.module('my.namespace');
class Foo {...}
Foo.Bar = class {...};
/** @enum {number} */
Foo.Baz = {...};
/** @typedef {{value: number}} */
Foo.Qux;
/** @interface */
Foo.Quuz = class {...}
exports.Foo = Foo;这些值应该是顶级导出。为这些值选择清晰的名称(例如,对于可能嵌套在 Foo
上的 Converter,使用
FooConverter)。但是,当模块名称与类名称的一部分冗余时,考虑省略冗余部分:使用
foo.Foo 和 foo.Converter 而不是 foo.Foo 和
foo.FooConverter。导入者可以在需要清晰性时添加前缀(例如
import {Converter as FooConverter} from './foo';),但在将整个模块作为命名空间导入时无法轻松去除冗余。
// foo.js
goog.module('my.namespace');
class Foo {...}
class FooBar {...}
/** @enum {string} */
let FooBaz = {...};
/** @typedef {{value: number}} */
let FooQux;
/** @interface */
class FooQuuz {...};
exports = {
Foo,
FooBar,
FooBaz,
FooQux,
FooQuuz,
};5.5 函数
5.5.1 顶级函数
顶级函数可以直接定义在 exports
对象上,也可以在本地声明并选择性地导出。关于导出的更多信息,参见
??。
示例:
/** @param {string} str */
exports.processString = (str) => {
// Process the string.
};/** @param {string} str */
const processString = (str) => {
// Process the string.
};
exports = {processString};5.5.2 嵌套函数和闭包(Closures)
函数可以包含嵌套函数定义。如果给函数命名有用,它应该被赋值给一个本地
const。
5.5.3 箭头函数(Arrow functions)
箭头函数提供了简洁的函数语法,并简化了嵌套函数中 this
的作用域。对于嵌套函数,优先使用箭头函数而不是 function 关键字(但参见
??)。
优先使用箭头函数而不是其他 this 作用域方法,如
f.bind(this)、goog.bind(f, this) 和
const self = this。箭头函数特别适用于调用回调,因为它们允许显式指定传递给回调的参数,而
bind 会盲目地传递所有参数。
箭头的左侧包含零个或多个参数。如果只有一个非解构参数,则参数周围的括号是可选的。当使用括号时,可以指定内联参数类型(参见 ??)。
提示:即使对于单参数箭头函数,始终使用括号也可以避免添加参数但忘记添加括号导致可解析但不再按预期工作的代码的情况。
箭头的右侧包含函数的主体。默认情况下,主体是块语句(零个或多个由花括号包围的语句)。如果程序逻辑需要返回值,或者
void 运算符在单个函数或方法调用之前(使用 void 确保返回
undefined,防止泄漏值并传达意图),主体也可以是隐式返回的单个表达式。如果单表达式形式能提高可读性(例如,对于简短或简单的表达式),则首选该形式。
示例:
/**
* 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;
});
}
}不允许:
/**
* 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
关键字(如果存在),并用空格与函数名称分隔。使用委托 yield 时,将 *
附加到 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 参数和返回类型
函数参数和返回类型通常应使用 JSDoc 注解来记录。更多信息参见 ??。
5.5.5.1 默认参数
允许在参数列表中使用等号运算符来指定可选参数。可选参数必须在等号运算符两侧包含空格,命名方式与必需参数完全相同(即不以
opt_ 为前缀),在其 JSDoc 类型中使用 =
后缀,位于必需参数之后,并且不使用产生可观察副作用的初始化器。所有具体函数的可选参数必须有默认值,即使该值是
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) {}
/** @interface */
class MyInterface {
/**
* Interface and abstract methods must omit default parameter values.
* @param {string=} optional
*/
someMethod(optional) {}
}谨慎使用默认参数。当存在大量没有自然顺序的可选参数时,优先使用解构(如 ?? 中所述)来创建可读的 API。
注意:与 Python 的默认参数不同,使用返回新可变对象的初始化器(如 {} 或
[])是可以的,因为每次使用默认值时都会评估初始化器,所以不会在调用之间共享单个对象。
提示:虽然任意表达式(包括函数调用)都可以用作初始化器,但这些应尽可能简单。避免暴露共享可变状态的初始化器,因为这很容易引入函数调用之间的意外耦合。
5.5.5.2 Rest 参数
使用 rest 参数而不是访问 arguments。Rest 参数在其 JSDoc 中以 ...
前缀标注类型。Rest 参数必须是列表中的最后一个参数。...
和参数名之间没有空格。不要将 rest 参数命名为
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 泛型(Generics)
在必要时,在函数或方法定义上方的 JSDoc 中使用 @template TYPE
声明泛型函数和方法。
5.5.7 展开运算符
函数调用可以使用展开运算符(...)。当数组或可迭代对象被解包为可变参数函数的多个参数时,优先使用展开运算符而不是
Function.prototype.apply。... 后面没有空格。
示例:
function myFunction(...elements) {}
myFunction(...array, ...iterable, ...generator());5.6 字符串字面量
5.6.1 使用单引号
普通字符串字面量使用单引号(')分隔,而不是双引号(")。
提示:如果字符串包含单引号字符,考虑使用模板字符串以避免转义引号。
普通字符串字面量不得跨越多行。
5.6.2 模板字面量(Template literals)
使用模板字面量(以 `
分隔)代替复杂的字符串拼接,特别是涉及多个字符串字面量时。模板字面量可以跨越多行。
如果模板字面量跨越多行,它不需要遵循包围块的缩进,尽管如果添加的空白无关紧要则可以这样做。
示例:
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 禁止行续接
不要在普通或模板字符串字面量中使用行续接(即在字符串字面量内用反斜杠结束一行)。即使 ES5 允许这样做,如果反斜杠后面有任何尾随空白,它可能导致棘手的错误,并且对读者来说不太明显。
不允许:
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.';相反,请这样写
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 数字字面量
数字可以用十进制、十六进制、八进制或二进制指定。分别使用精确的
0x、0o 和 0b
前缀(小写字母)表示十六进制、八进制和二进制。除非紧跟 x、o 或
b,否则永远不要包含前导零。
5.8 控制结构
5.8.1 For 循环
随着 ES6 的推出,该语言现在有三种不同的 for
循环。所有都可以使用,但在可能的情况下应优先使用 for-of 循环。
for-in 循环只能用于字典风格的对象(参见
??),不应用于遍历数组。应在 for-in
循环中使用 Object.prototype.hasOwnProperty
来排除不需要的原型属性。尽可能优先使用 for-of 和 Object.keys
而不是 for-in。
5.8.2 异常(Exceptions)
异常是语言的重要部分,应在出现异常情况时使用。始终抛出 Error 或
Error 的子类:永远不要抛出字符串字面量或其他对象。在构造 Error
时始终使用 new。
这种处理也扩展到 Promise 拒绝值,因为 Promise.reject(obj) 在 async
函数中等同于 throw obj;。
自定义异常提供了从函数传递额外错误信息的好方法。只要原生 Error
类型不足,就应该定义和使用自定义异常。
优先抛出异常而不是临时的错误处理方法(如传递错误容器引用类型,或返回带有错误属性的对象)。
5.8.2.1 空 catch 块
对捕获的异常不做任何操作是很少正确的。当在 catch 块中确实不需要采取任何操作时,应在注释中解释其合理性。
try {
return handleNumericResponse(response);
} catch (ok) {
// it's not numeric; that's fine, just continue
}
return handleTextResponse(response);不允许:
try {
shouldFail();
fail('expected an error');
} catch (expected) {
}提示:与某些其他语言不同,上面这样的模式根本不起作用,因为这会捕获
fail 抛出的错误。请使用 assertThrows() 代替。
5.8.3 Switch 语句
术语说明:在 switch
块的花括号内是一个或多个语句组。每个语句组由一个或多个 switch
标签(case FOO: 或 default:)组成,后跟一个或多个语句。
5.8.3.1 Fall-through:需要注释
在 switch 块中,每个语句组要么突然终止(使用 break、return
或抛出异常),要么用注释标记以指示执行将或可能继续到下一个语句组。任何传达
fall-through 意图的注释都足够(通常是 // fall through)。此特殊注释在
switch 块的最后一个语句组中不是必需的。
示例:
switch (input) {
case 1:
case 2:
prepareOneOrTwo();
// fall through
case 3:
handleOneTwoOrThree();
break;
default:
handleLargeNumber(input);
}5.8.3.2 default case 必须存在
每个 switch 语句都包含一个 default
语句组,即使它不包含任何代码。default 语句组必须是最后一个。
5.9 this
仅在类构造函数和方法中、在类构造函数和方法中定义的箭头函数中,或在紧邻的封闭函数的
JSDoc 中有显式 @this 声明的函数中使用 this。
永远不要使用 this 来引用全局对象、eval
的上下文、事件的目标,或不必要地 call() 或 apply() 的函数。
5.10 相等性检查
使用恒等运算符(identity operators)(===/!==),以下记录的情况除外。
5.10.1 需要类型强制转换的例外
同时捕获 null 和 undefined 值:
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 禁用的特性
5.11.1 with
不要使用 with 关键字。它使你的代码更难理解,并且自 ES5
以来在严格模式中已被禁止。
5.11.2 动态代码求值
不要使用 eval 或 Function(...string)
构造函数(代码加载器除外)。这些特性具有潜在危险,在 CSP
环境中根本不起作用。
5.11.3 自动分号插入(ASI)
始终用分号终止语句(如上所述,函数和类声明除外)。
5.11.4 非标准特性
不要使用非标准特性。这包括已删除的旧特性(例如
WeakMap.clear)、尚未标准化的新特性(例如当前的 TC39
工作草案、任何阶段的提案,或已提议但尚未完成的 web
标准),或仅在某些浏览器中实现的专有特性。仅使用当前 ECMA-262 或 WHATWG
标准中定义的特性。(请注意,针对特定 API 编写的项目,如 Chrome
扩展程序或 Node.js,显然可以使用这些
API)。禁止使用非标准语言”扩展”(如某些外部转译器提供的)。
5.11.5 原始类型的包装对象
永远不要对原始对象包装器(Boolean、Number、String、Symbol)使用
new,也不要在类型注解中包含它们。
不允许:
const /** Boolean */ x = new Boolean(false);
if (x) alert(typeof x); // alerts 'object' - WAT?包装器可以作为函数调用来进行类型强制转换(优于使用 +
或拼接空字符串)或创建 symbol。
示例:
const /** boolean */ x = Boolean(0);
if (!x) alert(typeof x); // alerts 'boolean', as expected5.11.6 修改内置对象
永远不要修改内置类型,无论是向其构造函数还是其原型添加方法。避免依赖执行此操作的库。请注意,JSCompiler 的运行时库将在可能的情况下提供符合标准的 polyfill;其他任何东西都不得修改内置对象。
不要向全局对象添加符号,除非绝对必要(例如第三方 API 要求)。
5.11.7 调用构造函数时省略 ()
在 new 语句中调用构造函数时,永远不要省略括号 ()。
不允许:
new Foo;请使用:
new Foo();省略括号可能导致微妙的错误。以下两行是不等价的:
new Foo().Bar();
new Foo.Bar();