JavaScript 代码风格指南

这份指南列出了编写 JavaScript 时需要遵守的规范, 指明哪些应该提倡, 哪些应该避免.
本文基于 google 的规范翻译整理(JavaScript 是许多 Google 开源项目使用的主要客户端脚本语言).

(3)

JavaScript 代码风格规范(2)

Javascript 类型

鼓励和强制使用编译器.

既然使用 JSDoc 来文档化, 那就尽可能规范和准确. 支持的类型基于 EcmaScript 4 spec.

JS 类型语言

ES4 建议中包含一种用于 JS 类型说明的语言. 我们在 JSDoc 中使用这种语言来说明函数的类型和返回值.

由于 ES4 建议还处于发展中, 因此这种说明语言已经有所变化. 编译器依然支持旧的类型语法, 单这些语法已经被废弃来.

参见下表:

JS 类型语言

JS 中的类型

参见下表:

JS 中的类型

类型转换

在那些类型检查基于表达式推断不够准确的情况中, 可以使用类型注解来增加一些类型转换注释, 并且用圆括号括起表达式. 圆括号是必须的.

1
/** @type {number} */ (x)

可空类型 vs. 可缺省参数/属性

由于 JS 属于弱类型语言. 因此理解可缺省, 可空和未定义函数参数和类属性之间的微妙差异是非常重要的.

接口以及类的实例默认是可空类型. 例如下面的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 一些类, 用一个值来初始化.
* @param {Object} value Some value.
* @constructor
*/

function MyClass(value) {
/**
* 一些值.
* @type {Object}
* @private
*/

this.myValue_ = value;
}

告诉编译器 myValue_ 属性既可能是对象也可能是 null. 如果不允许 myValue_null 则应该像这样声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Some class, initialized with a non-null value.
* @param {!Object} value Some value.
* @constructor
*/

function MyClass(value) {
/**
* Some value.
* @type {!Object}
* @private
*/

this.myValue_ = value;
}

如果采用这种方式, 当编译器检测到某段代码中 MyClass 使用 null 来初始化时, 就会报警告.

函数的可缺省参数在运行时可能是未定义的, 因此如果它们被赋给类的属性, 这些属性必须相应地声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 一些类, 包含可缺省参数被初始化.
* @param {Object=} opt_value Some value (optional).
* @constructor
*/

function MyClass(opt_value) {
/**
* 一些值.
* @type {Object|undefined}
* @private
*/

this.myValue_ = opt_value;
}

上述代码告诉编译器 myValue_ 可能包含一个对象, null 或未定义类型.

注意可缺省参数 opt_value 以类型 {Object=} 来声明, 而非 {Object|undefined}. 这是因为可缺省参数也有可能会是未定义类型. 虽然显式地说明可缺省变量可能是未定义类型并不会有什么害处, 但是这样既无必要也会降低代码的可读性.

最后, 注意可空类型和可缺省类型都是正交属性.

下面的四种声明都是不同的:

1
2
3
4
5
6
7
8
9
10
11
/**
* 接受 4 格参数, 其中两个是可空类型, 两个是可缺省类型.
* @param {!Object} nonNull Mandatory (must not be undefined), must not be null.
* @param {Object} mayBeNull Mandatory (must not be undefined), may be null.
* @param {!Object=} opt_nonNull Optional (may be undefined), but if present,
* must not be null!
* @param {Object=} opt_mayBeNull Optional (may be undefined), may be null.
*/

function strangeButTrue(nonNull, mayBeNull, opt_nonNull, opt_mayBeNull) {
// ...
};

类型定义

有时类型可以变得非常复杂. 一个接受某个元素内容的函数可能看上去会是这样:

1
2
3
4
5
6
7
8
/**
* @param {string} tagName
* @param {(string|Element|Text|Array.<Element>|Array.<Text>)} contents
* @return {!Element}
*/

goog.createElement = function(tagName, contents) {
...
};

你可以使用 @typedef 来标记常见的类型表达式, 比如:

1
2
3
4
5
6
7
8
9
10
11
/** @typedef {(string|Element|Text|Array.<Element>|Array.<Text>)} */
goog.ElementContent;

/**
* @param {string} tagName
* @param {goog.ElementContent} contents
* @return {!Element}
*/

goog.createElement = function(tagName, contents) {
...
};

模板类型

编译器已经对模板类提供了有限度的支持. 它只能通过 this 参数的类型和 this 参数是否遗失来判断匿名函数字面量中的 this 类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @param {function(this:T, ...)} fn
* @param {T} thisObj
* @param {...*} var_args
* @template T
*/

goog.bind = function(fn, thisObj, var_args) {
...
};
// 可能会产生属性遗失的警告.
goog.bind(function() { this.someProperty; }, new SomeClass());
// 会产生未定义 this 的警告.
goog.bind(function() { this.someProperty; });