Extern 的用途
Extern 是向 Closure Compiler 告知在高级编译期间不应重命名的符号名称的声明。之所以称为“外部”,是因为它们通常由编译之外的代码(如原生代码或第三方库)定义。因此,extern 通常还有类型注释,以便 Closure 编译器对这些符号的使用情况进行类型检查。
通常,最好将 extern 视为实现者与某段编译代码的使用方之间的 API 协定。exex 定义了实现者承诺提供的内容以及使用方可以依赖的内容。双方都需要合同副本。
Extern 与其他语言的头文件类似。
Externs 语法
Extern 是与为 Closure Compiler 注释的普通 JavaScript 非常相似的文件。主要区别在于,它们的内容绝不会作为编译输出的一部分输出,因此没有任何值有意义,只有名称和类型。
下面是一个简单库的 externs 文件的示例。
// The `@externs` annotation is the best way to indicate a file contains externs. /** * @fileoverview Public API of my_math.js. * @externs */ // Externs often declare global namespaces. const myMath = {}; // Externs can declare functions, most importantly their names. /** * @param {number} x * @param {number} y * @return {!myMath.DivResult} */ myMath.div = function(x, y) {}; // Note the empty body. // Externs can contain type declarations, such as classes and interfaces. /** The result of an integer division. */ myMath.DivResult = class { // Constructors are special; member fields can be declared in their bodies. constructor() { /** @type {number} */ this.quotient; /** @type {number} */ this.remainder; } // Methods can be declared as usual; their bodies are meaningless though. /** @return {!Array<number>} */ toPair() {} }; // Fields and methods can also be declared using prototype notation. /** * @override * @param {number=} radix */ myMath.DivResult.prototype.toString = function(radix) {};
--externs
标志
通常,@externs
注解是告知编译器文件包含 extern 的最佳方式。您可以使用 --js
命令行 flag 将此类文件添加为普通源文件,
不过,还有一种方法可以指定 extern 文件。--externs
命令行标记可用于明确传递 extern 文件。不建议使用此方法。
使用 Extern
上面的 Exexents 可以如下使用。
/** * @fileoverview Do some math. */ /** * @param {number} x * @param {number} y * @return {number} */ export function greatestCommonDivisor(x, y) { while (y != 0) { const temp = y; // `myMath` is a global, it and `myMath.div` are never renamed. const result = myMath.div(x, y); // `remainder` is also never renamed on instances of `DivResult`. y = result.remainder; x = temp; } return x; }
如何使用 Closure Compiler Service API 添加 Extern
Closure Compiler 应用和 Closure Compiler Service API 都允许执行外部声明。不过,Closure Compiler 服务界面不提供指定 extern 文件的界面元素。
您可以通过以下三种方式向 Closure Compiler 服务发送 extern 声明:
-
将包含
@externs
注解的文件作为源文件传递。 -
将 JavaScript 传递给
js_externs
参数中的 Closure Compiler 服务。 -
将 JavaScript 文件的网址传递给
externs_url
参数中的 Closure Compiler 服务。
使用 js_externs
和使用 externs_url
之间的唯一区别在于 JavaScript 如何传达给 Closure 编译器服务。
导出目的
导出是另一种用于在编译后提供符号一致名称的机制。它们的使用不如 Exex,通常也让人感到困惑。除简单情况外,最好避免这种情况。
导出所依赖的事实是,Closure 编译器不会修改字符串字面量。通过将对象分配给使用字面量命名的属性,即使在编译后,仍可通过该属性名称使用该对象。
下面是一个简单示例。
/** * @fileoverview Do some math. */ // Note that the concept of module exports is totally unrelated. /** @return {number} */ export function myFunction() { return 5; } // This assignment ensures `myFunctionAlias` will be a global alias exposing `myFunction`, // even after compilation. window['myFunctionAlias'] = myFunction;
如果您使用的是 Closure 库,还可以使用 goog.exportSymbol
和 goog.exportProperty
函数声明导出。
如需了解详情,请参阅这些函数的 Closure 库文档。不过请注意,它们具有特殊的编译器支持,并且会在已编译的输出中完全转换。
与导出有关的问题
导出与外部不同,它们仅创建公开的别名供使用方引用。在编译的代码中,导出的符号仍会重命名。因此,导出的符号必须是常量,因为在您的代码中重新分配符号会导致公开的别名指向错误的内容。
对于重命名的实例属性而言,重命名的这种细微差别特别复杂。
从理论上讲,与 extern 相比,导出所允许的代码大小可以更小,因为长名称仍可以更改为代码中的短名称。实际上,这些改进通常非常小,并且无法证明导出会造成混淆。
此外,导出功能也不会为消费者提供可按照 EXED 方式使用的 API。与导出功能相比,导出功能会记录您打算公开的符号及其类型,并为您提供添加使用情况信息的地方。此外,如果您的消费者还在使用 Closure 编译器,则需要借助 Exexsure 编译器进行编译。