從closuretools.blogspot.co.uk/2012/02/type-checking-tips.html複製逐句,由於「謝謝你的建議。太糟糕的blogspot在中國封鎖,所以不能讀取。我看到很多關於這個的文章都在blogspot上,但看不到它們「
Closure編譯器的類型語言有點複雜。它有聯合(「變量x可以是A或B」),結構函數(「變量x是一個返回數字的函數」)和記錄類型(「變量x是任何具有屬性foo和bar的對象」)。
很多人告訴我們,這仍然沒有足夠的表現力。有很多方法可以編寫不適合我們類型系統的JavaScript。人們建議我們應該添加mixins,特徵以及匿名對象的事後命名。
這對我們來說並不特別令人驚訝。 JavaScript中的對象規則有點像Calvinball的規則。你可以改變任何事情,並隨着時間的推移制定新的規則。許多人認爲一個好的類型系統爲你提供了一個強大的方式來描述你的程序的結構。但它也給你一套規則。類型系統確保每個人都同意什麼是「類」,什麼是「界面」,什麼是「是」。當你試圖將類型註釋添加到無類型的JS時,你不可避免地會遇到我頭腦中的規則與頭腦中的規則不匹配的問題。沒關係。
但是我們很驚訝,當我們給人這種類型的系統時,他們經常會找到多種方式來表達同樣的東西。有些方法比其他方法更好。我想我會寫這個來描述人們嘗試的一些事情,以及他們是如何制定出來的。
功能與函數()
有兩種方法來描述函數。一種是使用{Function}類型,編譯器將其解釋爲「任何對象x,其中'x instanceof Function'爲true」。 A {Function}
是故意糊塗。它可以接受任何參數,並返回任何內容。你甚至可以使用'新'。編譯器會讓你在不發出警告的情況下調用它。
結構函數更加具體,並且可以對函數的功能進行細化控制。 A {function()}
不需要任何參數,但我們不在乎它返回的結果。 A {function(?): number}
返回一個數字並只需要一個參數,但我們不關心該參數的類型。當你用「新」來調用它時,A {function(new:Array)}
創建一個數組。我們的類型文檔和JavaScript風格指南有更多關於如何使用結構函數的例子。
很多人問我們,{Function}
是不鼓勵,因爲它不是特定的。其實,這非常有用。例如,請考慮Function.prototype.bind
的定義。它可以讓你的咖喱函數:你可以給它一個函數和一個參數列表,它會給你一個帶有「預先填充的」參數的新函數。我們的類型系統不可能表達返回的函數類型是第一個參數類型的轉換。因此,Function.prototype.bind
上的JSDoc表示它返回{Function}
,編譯器必須使用手動編碼邏輯來確定真實類型。
還有很多情況下,您想通過回調函數來收集結果,但結果是特定於上下文的。
rpc.get(‘MyObject’, function(x) {
// process MyObject
});
的「rpc.get」的方法是一個很大的笨拙,如果你通過了回調參數有型投什麼就愈大。所以,只給參數一個{Function}類型通常會更簡單,並且相信調用者類型不值得進行類型檢查。
對象與匿名對象
很多JS庫定義一個全局對象,有很多的方法。該對象應該具有哪種類型的註釋?
var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};
如果你來自Java,你可能會試圖給它鍵入{Object}。
/** @type {Object} */ var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};
這通常不是你想要的。如果添加「@type {Object}」註釋,則不僅僅是告訴編譯器「bucket是Object」。您告訴它「bucket允許爲任何Object」。因此,編譯器必須假定任何人都可以將任何對象分配給「桶」,並且該程序仍然是類型安全的。
相反,你最好使用@const。
/** @const */ var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};
現在我們知道,桶不會被分配給任何其他對象,而編譯器的類型推理引擎可以使剷鬥及其方法強得多檢查。
一切都能成爲記錄類型嗎?
JavaScript的類型系統並不那麼複雜。它有8種特殊語法:null,undefined,boolean,number,string,Object,Array和Function。有些人已經注意到,記錄類型可以讓你定義「一個具有屬性x,y和z的對象」,並且該類型定義允許你給任何類型表達式命名。因此,在這兩者之間,您應該能夠使用記錄類型和typedef定義任何用戶定義的類型。這就是我們需要的一切嗎?
當您需要一個函數來接受大量的可選參數時,記錄類型非常棒。所以,如果你有這樣的功能:
/**
* @param {boolean=} withKetchup
* @param {boolean=} withLettuce
* @param {boolean=} withOnions
*/
function makeBurger(withKetchup, withLettuce, withOnions) {}
你可以把它有點容易調用是這樣的:
/**
* @param {{withKetchup: (boolean|undefined),
withLettuce: (boolean|undefined),
withOnions: (boolean|undefined)}=} options
*/
function makeBurger(options) {}
這種運作良好。但是當你在一個程序的許多地方使用相同的記錄類型時,事情會變得有點多毛。假設您爲makeBurger的參數創建了一個類型:
/** @typedef {{withKetchup: (boolean|undefined),
withLettuce: (boolean|undefined),
withOnions: (boolean|undefined)}=} */
var BurgerToppings;
/** @const */
var bobsBurgerToppings = {withKetchup: true};
function makeBurgerForBob() {
return makeBurger(bobsBurgerToppings);
}
後來,Alice在Bob的庫上創建了一個餐廳應用程序。在一個單獨的文件中,她嘗試添加洋蔥,但是搞砸了API。
bobsBurgerToppings.withOnions = 3;
關閉編譯器會注意到bobsBurgerToppings的BurgerToppings記錄類型不再匹配。但它不會抱怨Alice的代碼。它會抱怨鮑勃的代碼造成類型錯誤。對於非平凡的節目,鮑勃可能很難找出爲什麼這些類型不再匹配。
一個好的類型系統不僅僅表達關於類型的契約。它也爲我們提供了一種在代碼違約時分配責任的好方法。因爲一個類通常定義在一個地方,編譯器可以找出誰負責打破類的定義。但是,當你有一個匿名對象被傳遞給許多不同的函數,並且具有從許多不同的地方設置的屬性時,對於人類和編譯器來說,更難確定誰打破了類型契約。
發佈者尼克·桑托斯,軟件工程師
幾件事情,使用一個基於構造函數的對象而不是typedef(http://closuretools.blogspot.co.uk/2012/02/type-checking-tips.html),也需要註釋一個! 。 – lennel 2013-03-12 07:55:43
謝謝您的建議。太糟糕的blogspot在中國被阻止,因此無法閱讀。我看到很多關於此的文章都在blogspot上,但看不到它們。雖然triggerNext從來沒有用新的triggerNext創建。它是一個對象的屬性,如var mediator = {triggerNext:function(... – HMR 2013-03-12 08:57:25
將粘貼整個文章給你一個答案。 – lennel 2013-03-12 09:26:46