實習生的目的
外部項是宣告,可告知 Closure 編譯器在進階編譯期間,哪些符號名稱不應重新命名。這些符號最常由編譯外的程式碼 (例如原生程式碼或第三方程式庫) 定義,因此稱為外部符號。因此,外部函式通常也會有型別註解,方便 Closure 編譯器檢查您對這些符號的使用情形。
一般來說,最好將外部函式視為實作人員與某段已編譯程式碼的消費者之間的 API 合約。外部定義實作人員承諾提供的內容,以及消費者可依賴使用的內容。雙方都需要合約副本。
外部檔案與其他語言的標頭檔案類似。
Externs 語法
外部函式是類似於一般 JavaScript 的檔案,但會針對 Closure 編譯器加上註解。主要差異在於,編譯輸出內容中絕不會列印這些內容,因此這些值都沒有意義,只有名稱和型別有意義。
以下是簡單程式庫的外部檔案範例。
// 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
註解是告知編譯器檔案含有外部項目的最佳方式。這類檔案可使用 --js
指令列旗標,以一般來源檔案的形式納入,
不過,您也可以使用較舊的方法指定外部檔案。--externs
指令列標記可用來明確傳遞外部檔案。我們不建議使用這個方法。
使用 Externs
上述外部項可依下列方式使用。
/** * @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 編譯器不會修改字串常值。 將物件指派給以常值命名的屬性後,即使在編譯後,該物件仍可透過該屬性名稱使用。
以下是簡單的範例。
/** * @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 程式庫說明文件。不過請注意,這些標記需要特殊的編譯器支援, 且會在編譯輸出內容中完全轉換。
匯出作業相關問題
匯出內容與外部內容不同,因為匯出內容只會建立供消費者參照的公開別名。在編譯後的程式碼中,匯出的符號仍會重新命名。因此,匯出的符號必須是常數,因為在程式碼中重新指派這些符號,會導致公開別名指向錯誤的項目。
如果重新命名的是匯出的例項屬性,這項細微差異就會變得特別複雜。
理論上,與外部函式相比,匯出功能可縮減程式碼大小,因為長名稱仍可在程式碼中變更為較短的名稱。但實際上,這些改善通常非常微小,不足以證明匯出作業造成的混淆是合理的。
此外,匯出內容不會提供 API,供消費者以外部項目的方式追蹤。 相較於匯出內容,外部項目會記錄您要公開的符號、這些符號的類型,並提供新增使用資訊的位置。此外,如果消費者也使用 Closure Compiler,則需要外部函式來編譯。