標籤:win mod 就會 外部 post 函數調用 body 現象 get
前言
命名空間可以被認為是唯一識別碼下代碼的邏輯分組。為什麼會出現命名空間這一概念呢?因為可用的單詞數太少,並且不同的人寫的程式不可能所有的變數都沒有重名現象。在JavaScript中,命名空間可以協助我們防止與全域命名空間下的其他對象或變數產生衝突。命名空間也有助於組織代碼,有更強的可維護性和可讀性。本文旨在探討JavaScript裡的幾種常見命名空間模式,為我們提供一個思路。
JavaScript執行環境有很多獨特之處,全域變數和函數的使用就是其中之一。JavaScript的執行環境由各種各樣的全域變數構成,這些全域變數先於函數執行環境而建立。這些全域變數都掛載於“全域對象”下,在瀏覽器中,window對象就等同於全域對象。那麼,在全域範圍中聲明的任何變數和函數都是window對象的屬性,當名稱有衝突時,就會產生一些不可控的問題。全域變數會帶來以下問題:
在編程開發中合理的使用命名空間,可以避免相同的變數或對象名稱產生的衝突。而且,命名空間也有助於組織代碼,有更強的可維護性和可讀性。JavaScript中雖然沒有提供原生的命名空間支援,但我們可以使用其他的方法(對象和閉包)實作類別似的效果。下面就是一些常見的命名空間模式:
1.單一全域變數
JavaScript中一個流行的命名空間模式是選擇一個全域變數作為主要的引用對象。因為每個可能的全域變數都成為唯一全域變數的屬性,也就不用再建立多個全域變數,那麼也就避免了和其他聲明的衝突。
單一全域變數模式已經在不少的JavaScript類庫中使用,如:
- YUI定義了唯一的YUI全域對象
- jQuery定義了$和jQuery,$由其他類庫使用時使用jQuery
- Dojo定義了一個Dojo全域變數
- Closure類庫定義了一個goog全域對象
- Underscore類庫定義了一個_ 全域對象
樣本如下:
1 var myApplication = (function() {2 function() {3 // ***4 },5 return {6 // **7 }8 })();
雖然單一全域變數模式適合某些情況,但其最大的挑戰是確保單一全域變數在頁面中是唯一使用的,不會發生命名衝突。
2.命名空間首碼
命名空間前置位元型其思路非常清晰,就是選擇一個獨特的命名空間,然後在其後面聲明聲明變數、方法和對象。樣本如下:
1 var = myApplication_propertyA = {};2 var = myApplication_propertyA = {};3 4 function myApplication_myMethod() {5 // ***6 }
從某種程度上來說,它確實減少了命名衝突的發生機率,但其並沒有減少全域變數的數目。當應用程式規模擴大時,就會產生很多的全域變數。在全域命名空間內,這種模式對其他人都沒有使用的這個首碼有很強的依賴,而且有些時候也不好判斷是否有人已經使用某個特殊首碼,在使用這種模式時一定要特別注意。
3.對象字面量標記法
對象字面量模式可以認為是包含一組索引值對的對象,每一對鍵和值由冒號分隔,鍵也可以是代碼新的命名空間。樣本如下:
1 var myApplication = { 2 3 // 可以很容易的為對象字面量定義功能 4 getInfo:function() { 5 // *** 6 }, 7 8 // 可以進一步支撐對象命名空間 9 models:{},10 views:{11 pages:{}12 },13 collections:{}14 };
與為對象添加屬性一樣,我們也可以直接將屬性添加到命名空間。對象字面量方法不會汙染全域命名空間,並在邏輯上協助組織代碼和參數。並且,這種方式可讀性和可維護性非常強,當然我們在使用時應當進行同名變數的存在性測試,以此來避免衝突。下面是一些常用的檢測方法:
1 var myApplication = myApplication || {}; 2 3 if(!myApplication) { 4 myApplication = {}; 5 } 6 7 window.myApplication || (window.myApplication || {}); 8 9 // 針對jQuery10 var myApplication = $.fn.myApplication = function() {};11 12 var myApplication = myApplication === undefined ? {} :myApplication;
對象字面量為我們提供了優雅的鍵/值文法,我們可以非常便捷的組織代碼,封裝不同的邏輯或功能,而且可讀性、可維護性、可擴充性極強。
4.嵌套命名空間
嵌套命名空間模式可以說是對象字面量模式的升級版,它也是一種有效避免衝突模式,因為即使一個命名空間存在,它也不太可能擁有同樣的嵌套子物件。樣本如下:
1 var myApplication = myApplication || {};2 3 // 定義嵌套子物件4 myApplication.routers = myApplication.routers || {};5 myApplication.routers.test = myApplication.routers.test || {};
當然,我們也可以選擇聲明新的嵌套命名空間或屬性作為索引屬性,如:
myApplication[‘routers‘] = myApplication[‘routers‘] || {};
使用嵌套命名空間模式,可以使代碼易讀且有組織性,而且相對安全,不易產生衝突。其弱點是,如果我們的命名空間嵌套過多,會增加瀏覽器的查詢工作量,我們可以把要多次訪問的子物件進行局部緩衝,以此來減少查詢時間。
5.立即調用的函數運算式
立即調用函數(IIFE)實際上就是匿名函數,被定義後立即被調用。在JavaScript中,由於變數和函數都是在這樣一個只能在內部進行訪問的上下文中被顯式地定義,函數調用提供了一種實現私人變數和方法的便捷方式。IIFE是用於封裝應用程式邏輯的常用方法,以保護它免受全域名稱空間的影響,其在命名空間方面也可以發揮其特殊的作用。樣本如下:
1 // 命名空間和undefined作為參數傳遞,確保: 2 // 1.命名空間可以在局部修改,不重寫函數外部內容 3 // 2.undefined 的參數值是確保undefined,避免ES5規範裡定義的undefined 4 (function (namespace, undefined) { 5 6 // 私人屬性 7 var foo = "foo"; 8 bar = "bar"; 9 10 // 公有方法和屬性11 namespace.foobar = "foobar";12 namespace.sayHello = function () {13 say("Hello World!");14 };15 16 // 私人方法17 function say(str) {18 console.log("You said:" + str);19 };20 })(window.namespace = window.namespace || {});
可擴充性是任何可伸縮命名空間模式的關鍵,使用IIFE可以輕鬆實現這一目的,我們可以再次使用IIFE給命名空間添加更多的功能。
6.命名空間注入
命名空間注入是IIFE的另一個變體,從函數封裝器內部為一個特定的命名空間“注入”方法和屬性,使用this作為命名空間代理。這種模式的優點是可以將功能行為應用到多個對象或命名空間。樣本如下:
1 var myApplication = myApplication || {}; 2 myApplication.utils = {}; 3 4 (function () { 5 var value = 5; 6 7 this.getValue = function () { 8 return value; 9 }10 11 // 定義新的子命名空間12 this.tools = {};13 }).apply(myApplication.utils);14 15 (function () {16 this.diagnose = function () {17 return "diagnose";18 }19 }).apply(myApplication.utils.tools);20 21 // 同樣的方式在普通的IIFE上擴充功能,僅僅將上下文作為參數傳遞並修改,而不是僅僅使用this
還有一種使用API來實現上下文和參數自然分離的方法,該模式感覺更像是一個模組的建立者,但作為模組,它還提供了一個封裝解決方案。樣本如下:
1 var ns = ns || {}, 2 ns1 = ns1 || {}; 3 4 // 模組、命名空間建立者 5 var creator = function (val) { 6 var val = val || 0; 7 8 this.next = function () { 9 return val ++ ;10 };11 12 this.reset = function () {13 val = 0;14 }15 }16 17 creator.call(ns);18 // ns.next, ns.reset 此時已經存在19 20 creator.call(ns1, 5000);21 // ns1包含相同的方法,但值被重寫為5000了
命名空間注入是用於為多個模組或命名空間指定一個類似的功能基本集,但最好是在聲明私人變數或者方法時再使用它,其他時候使用嵌套命名空間已經足以滿足需要了。
7.自動嵌套的命名空間
嵌套命名空間模式可以為代碼單元提供有組織的結構層級,但每次建立一個層級時,我們也得確保其有相應的父層級。當層級數量很大時,會給我們帶來很大的麻煩,我們不能快速便捷的建立想建立的層級。那麼如何解決這個問題呢?Stoyan Stefanov提出,建立一個方法,其接收字串參數作為一個嵌套,解析它,並自動用所需的對象填充基本名稱空間。下面是這種模式的一種實現:
1 function extend(ns, nsStr) { 2 var parts = nsStr.split("."), 3 parent = ns, 4 pl; 5 6 pl = parts.length; 7 8 for (var i = 0; i < pl; i++) { 9 // 屬性如果不存在,則建立它10 if (typeof parent[parts[i]] === "undefined") {11 parent[prats[i]] = {};12 }13 parent = parent[parts[i]];14 }15 return parent;16 }17 18 // 用法19 var myApplication = myApplication || {};20 var mod = extend(myApplication, "module.module2");
以前我們必須為其命名空間將各種嵌套顯式聲明為對象,現在用上述更簡潔、優雅的方式就實現了。
本文大致探討了幾種JavaScript中常用的命名空間模式。總的來說,沒有大統一、通用的解決方案,只有適合的解決方案,具體選擇哪種模式還要看項目需要。
JavaScript之命名空間模式