全域變數應該由有系統範圍相關性的物件保留,並且它們的命名應該避免含糊並儘量減少命名衝突的風險。在實踐中,這意味著你應該避免創建全局對象,除非它們是絕對必須的。
不過,恩,這些你早都知道了…
所以你對此是怎麼做的?傳統方法告訴我們,最好的消除全局策略是創建少數作為潛在模組和子系統的實際命名空間的全局物件。我將探索幾種有關命名空間的方式,並以我基於 James Edwards 最近的一篇文章所獲得的一個優雅、安全和靈活的解決方案結束。
我用靜態命名空間
作為那些命名空間標籤實際上硬編碼的解決方案的涵蓋性術語。是的,你可以將一個命名空間重新分配給另一個,不過新的命名空間將會引用和舊的那一個同樣的物件。
最基礎的方法。這樣非常冗長,如果你還想重命名這些命名空間,你就有得活兒乾了。不過它是安全和清楚明白的。
var myApp = {} myApp.id = 0; myApp.next = function() { return myApp.id++; } myApp.reset = function() { myApp.id = 0; } window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ); //0, 1, undefined, 0
你也可以透過使用this
引用兄弟屬性來使將來的維護更輕鬆一些,不過這有一點冒險因為沒有什麼能阻止你的那些命名空間裡的方法被重新分配。
var myApp = {} myApp.id = 0; myApp.next = function() { return this.id++; } myApp.reset = function() { this.id = 0; } myApp.next(); //0 myApp.next(); //1 var getNextId = myApp.next; getNextId(); //NaN whoops!
現在我們只需要引用命名空間名一次,因此之後改變名字更簡單了一些(假設你還沒反覆引用這個命名空間)。仍有一個危險是this
的值可能會拋出一個『驚喜』 – 不過假設在一個物件字面結構裡定義的物件不會被重新分配相對安全一點。
var myApp = { id: 0, next: function() { return this.id++; }, reset: function() { this.id = 0; } } window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) //0, 1, undefined, 0
我發現自己最近用模組模式
更多。邏輯被一個方法包裝從全域域隔離開了(通常是自呼叫的),它傳回一個代表這個模組公開介面的物件。透過立即呼叫這個方法並分配結果給一個命名空間變量,我們就鎖住了這個命名變數中模組的 API。此外,任何沒有包含在傳回值中的變數將永遠保持私有,只對引用他們的公開方法可見。
var myApp = (function() { var id= 0; return { next: function() { return id++; }, reset: function() { id = 0; } }; })(); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) //0, 1, undefined, 0
如上物件字面量例子,命名空間名字可以輕易更換,不過還有額外優勢:物件字面量是四班的 – 它全是關於屬性分配,沒有支援邏輯的空間。此外,所有屬性必須被初始化,且屬性值無法輕易跨物件參考(因此,例如,內部閉包就不可能使用了)。模組模式沒有任何上述約束,並且給我們額外的隱私福利。
我們也可以將這一節稱為命名空間注入
。命名空間由一個直接引用方法包裝內部
的代理代表 – 這意味著我們不再需要打包分配給命名空間的回傳值。這讓命名空間定義變得更靈活並且讓擁有多個存在於獨立命名空間中(或甚至在全局上下文中)的模組的獨立實例。動態命名空間支援模組模式的全部特徵並附加直覺和可讀性強的優勢。
在這裡我們只是將命名空間作為參數傳給自呼叫方法。變數id
是私有的,因為他並沒有被指派給context
。
var myApp = {}; (function(context) { var id = 0; context.next = function() { return id++; }; context.reset = function() { id = 0; } })(myApp); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) //0, 1, undefined, 0
我們甚至可以把context
設定給全域物件(透過一個字的改變!)。這是庫主們的巨大財富– 他們可以將他們的特性包裝在一個自調用函數中,然後讓用戶來決定它們是不是全局的(John Resig 在他寫JQuery 時就是一個這個理論的早期採用者) 。
var myApp = {}; (function(context) { var id = 0; context.next = function() { return id++; }; context.reset = function() { id = 0; } })(this); window.console && console.log( next(), next(), reset(), next() ) //0, 1, undefined, 0
this
作為命名空間代理James Edwads 最近發布的一篇文章激起了我的興趣。 《My Favorite JavaScript Design Patter》 顯然被許多評論者誤解了,他們認為他也可能藉助於模組模式。這篇文章宣傳了多種技術(可能導致了讀者的迷惑),但是在它的核心部分是一點我已經修改並呈現為一個命名空間工具的很天才的東西。
這個模式的美就在於它只是按照這個語言被設計的方式使用 – 不多不少、不投機也不取巧。此外因為命名空間是透過this
關鍵字(它在給定的執行上下文中是不變的)注入的,它不可能被意外修改。
var myApp = {}; (function() { var id = 0; this.next = function() { return id++; }; this.reset = function() { id = 0; } }).apply(myApp); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ); //0, 1, undefined, 0
更棒的是,apply
(以及call
) API 提供了與上下文和參數天然的隔離– 因此給模組創建者傳遞附加參數非常乾淨。下面的例子顯示了這一點,並且展示瞭如何獨立於多個命名空間來運行模組。
var subsys1 = {}, subsys2 = {}; var nextIdMod = function(startId) { var id = startId || 0; this.next = function() { return id++; }; this.reset = function() { id = 0; } }; nextIdMod.call(subsys1); nextIdMod.call(subsys2,1000); window.console && console.log( subsys1.next(), subsys1.next(), subsys2.next(), subsys1.reset(), subsys2.next(), subsys1.next() ) //0, 1, 1000, undefined, 1001, 0
當然如果我們如果我們如果我們需要一個全域 id 生成器,非常簡單…
nextIdMod(); window.console && console.log( next(), next(), reset(), next() ) //0, 1, undefined, 0
这个我们作为例子使用的 id 生成器工具并没有表现出这个模式的全部潜力。通过包裹一整个库和使用this
关键字作为命名空间的替身,我们使得用户在任何他们选择的上下文中运行这个库很轻松(包括全局上下文)。
//library code var protoQueryMooJo = function() { //everything } //user code var thirdParty = {}; protoQueryMooJo.apply(thirdParty);
我希望避免命名空间嵌套。它们很难追踪(对人和电脑都是)并且它们会让你的代码因为一些乱七八糟的东西变得很多。如 Peter Michaux 指出的,深度嵌套的命名空间可能是那些视图重新创建他们熟悉和热爱的长包链的老派 Java 开发者的遗产。
通过 .js 文件来固定一个单独的命名空间也是可以的(虽然只能通过命名空间注入或者直接分配每一个变量),不过你应该对依赖谨慎些。此外将命名空间绑定到文件上可以帮助读者更轻易弄清整个代码。
因为 JavaScript 并没有正式的命名空间结构,所以有很多自然形成的方法。这个调查只详细说明了其中的一部分,可能有更好的技术我没有发现。我很乐意知道它们。
以上就是JavaScript 中的命名空间详细介绍的内容,更多相关内容请关注PHP中文网(www.php.cn)!