在SF上看到這樣一個提問:html
如題,由於不得已的緣由,須要寫若干個全局函數。但又不想這樣:編程
window.a = function(){} window.b = function(){} window.c = function(){}
題主問有什麼好的寫法?數組
解答:瀏覽器
若是你用 jQuery,你能夠這樣寫緩存
$.extend(window, { a: function() {}, b: function() {}, c: function() {} });
若是你不用 jQuery,能夠直接實現相似的 extend:安全
(() => { var defining = { a: function() { }, b: function() { }, c: function() { } }; Object.keys(defining).forEach(key => { window[key] = defining[key]; }); })();
在JavaScript中,命名空間能夠幫助咱們防止與全局命名空間下的其餘對象或變量產生衝突。命名空間也有助於組織代碼,有更強的可維護性和可讀性。本文旨在探討JavaScript裏的幾種常見命名空間模式,爲咱們提供一個思路。閉包
通常命名空間的實現都以window爲根。當向window申請a.b.c的命名空間時,首先在window中查看是否存在a這個成員,若是沒有則在 window下新建一個名爲a的空關聯數組,若是已經存在a,則繼續在window.a中查看b是否存在,以此類推。下面分別是Atlas和YUI中的實現方法。app
Atlas命名空間的實現方法:函數
Function.registerNamespace =function(namespacePath){ //以window爲根 var rootObject =window; //對命名空間路徑拆分紅數組 var namespaceParts =namespacePath.split('.'); for (var i =0;i <namespaceParts.length;i++) { var currentPart =namespaceParts[i]; //若是當前命名空間下不存在,則新建一個Object對象,等效於一個關聯數組。 if(!rootObject[currentPart]) { rootObject[currentPart]=new Object(); } rootObject =rootObject[currentPart]; } }
YUI命名空間的實現方法:post
var YAHOO = window.YAHOO || {}; YAHOO.namespace = function(ns) { if (!ns || !ns.length) { return null; } var levels = ns.split("."); var nsobj = YAHOO; //若是申請的命名空間是在YAHOO下的,則必須忽略它,不然就成了YAHOO.YAHOO了 for (var i=(levels[0] == "YAHOO") ? 1 : 0; i<levels.length; ++i) { //若是當前命名空間下不存在,則新建一個關聯數組。 nsobj[levels[i]] = nsobj[levels[i]] || {}; nsobj = nsobj[levels[i]]; } //返回所申請命名空間的一個引用; return nsobj; };
YUI把全部申請的命名空間都放在了window.YAHOO下面,這樣有什麼好處呢?假如Yahoo和其餘公司有合做關係, 須要嵌入對方的腳本時,這樣能保證它本身編寫的代碼都在YAHOO這個空間下面,而其餘公司不大可能在這個空間下面編碼,就基本不會出現命名衝突的狀況。
咱們通常的實現方式:
namespace = function(){ var argus = arguments; for(var i = 0; i < argus.length; i++){ var objs = argus[i].split("."); var obj = window; for(var j = 0; j < objs.length; j++){ obj[objs[j]] = obj[objs[j]] || {}; obj = obj[objs[j]]; } } return obj; }; namespace("tools.base");
固然咱們也經常利用js對象字面量的方式來實現js的命名空間:
var school = { addClass:function(classnum){ console.log(classnum); }, addStudent:function(stuId){ console.log(stuId); } } school.addClass("2");
在全局做用域中聲明的任何變量和函數都是window對象的屬性,當名稱有衝突時,就會產生一些不可控的問題。全局變量會帶來如下問題:
在編程開發中合理的使用命名空間,能夠避免相同的變量或對象名稱產生的衝突。並且,命名空間也有助於組織代碼,有更強的可維護性和可讀性。JavaScript中雖然沒有提供原生的命名空間支持,但咱們可使用其餘的方法(對象和閉包)實現相似的效果。下面就是一些常見的命名空間模式:
JavaScript中一個流行的命名空間模式是選擇一個全局變量做爲主要的引用對象。由於每一個可能的全局變量都成爲惟一全局變量的屬性,也就不用再建立多個全局變量,那麼也就避免了和其餘聲明的衝突。
單一全局變量模式已經在很多的JavaScript類庫中使用,如:
示例以下:
var myApplication = (function() { var count = 1; function funcur() { console.log(count); }; return { funcur:funcur } })(); myApplication.funcur();
雖然單一全局變量模式適合某些狀況,但其最大的挑戰是確保單一全局變量在頁面中是惟一使用的,不會發生命名衝突
命名空間前綴模式其思路很是清晰,就是選擇一個獨特的命名空間,而後在其後面聲明聲明變量、方法和對象。示例以下:
var myApplication_propertyA = {}; var myApplication_propertyB = {}; function myApplication_myMethod() { // *** }
從某種程度上來講,它確實減小了命名衝突的發生機率,但其並無減小全局變量的數目。當應用程序規模擴大時,就會產生不少的全局變量。在全局命名空間內,這種模式對其餘人都沒有使用的這個前綴有很強的依賴,並且有些時候也很差判斷是否有人已經使用某個特殊前綴,在使用這種模式時必定要特別注意。
對象字面量模式能夠認爲是包含一組鍵值對的對象,每一對鍵和值由冒號分隔,鍵也能夠是代碼新的命名空間。示例以下:
var myApplication = { // 能夠很容易的爲對象字面量定義功能 getInfo:function() { // *** }, // 能夠進一步支撐對象命名空間 models:{}, views:{ pages:{} }, collections:{} };
與爲對象添加屬性同樣,咱們也能夠直接將屬性添加到命名空間。對象字面量方法不會污染全局命名空間,並在邏輯上協助組織代碼和參數。而且,這種方式可讀性和可維護性很是強,固然咱們在使用時應當進行同名變量的存在性測試,以此來避免衝突。下面是一些經常使用的檢測方法:
var myApplication = myApplication || {}; if(!myApplication) { myApplication = {}; } window.myApplication || (window.myApplication || {}); // 針對jQuery var myApplication = $.fn.myApplication = function() {}; var myApplication = myApplication === undefined ? {} :myApplication;
對象字面量爲咱們提供了優雅的鍵/值語法,咱們能夠很是便捷的組織代碼,封裝不一樣的邏輯或功能,並且可讀性、可維護性、可擴展性極強。
嵌套命名空間模式能夠說是對象字面量模式的升級版,它也是一種有效的避免衝突模式,由於即便一個命名空間存在,它也不太可能擁有一樣的嵌套子對象。示例以下:
var myApplication = myApplication || {}; // 定義嵌套子對象 myApplication.routers = myApplication.routers || {}; myApplication.routers.test = myApplication.routers.test || {};
固然,咱們也能夠選擇聲明新的嵌套命名空間或屬性做爲索引屬性,如:
myApplication['routers'] = myApplication['routers'] || {};
使用嵌套命名空間模式,可使代碼易讀且有組織性,並且相對安全,不易產生衝突。其弱點是,若是咱們的命名空間嵌套過多,會增長瀏覽器的查詢工做量,咱們能夠把要屢次訪問的子對象進行局部緩存,以此來減小查詢時間。
當即調用函數(IIFE)實際上就是匿名函數,被定義後當即被調用。在JavaScript中,因爲變量和函數都是在這樣一個只能在內部進行訪問的上下文中被顯式地定義,函數調用提供了一種實現私有變量和方法的便捷方式。IIFE是用於封裝應用程序邏輯的經常使用方法,以保護它免受全局名稱空間的影響,其在命名空間方面也能夠發揮其特殊的做用。示例以下:
;(function (namespace, undefined) { // 私有屬性 var foo = "foo"; bar = "bar"; // 公有方法和屬性 namespace.foobar = "foobar"; namespace.sayHello = function () { say("Hello World!"); }; // 私有方法 function say(str) { console.log("You said:" + str); }; })(window.namespace = window.namespace || {});
console.log(namespace.foobar); //foobar
可擴展性是任何可伸縮命名空間模式的關鍵,使用IIFE能夠輕鬆實現這一目的,咱們能夠再次使用IIFE給命名空間添加更多的功能。
命名空間注入是IIFE的另外一個變體,從函數包裝器內部爲一個特定的命名空間「注入」方法和屬性,使用this做爲命名空間代理。這種模式的優勢是能夠將功能行爲應用到多個對象或命名空間。示例以下:
var myApplication = myApplication || {}; myApplication.utils = {}; ;(function () { var value = 5; this.getValue = function () { return value; } // 定義新的子命名空間 this.tools = {}; }).apply(myApplication.utils); (function () { this.diagnose = function () { return "diagnose"; } }).apply(myApplication.utils.tools); // 一樣的方式在普通的IIFE上擴展功能,僅僅將上下文做爲參數傳遞並修改,而不是僅僅使用this
還有一種使用API來實現上下文和參數天然分離的方法,該模式感受更像是一個模塊的建立者,但做爲模塊,它還提供了一個封裝解決方案。示例以下:
var ns = ns || {}, ns1 = ns1 || {}; // 模塊、命名空間建立者 var creator = function (val) { var val = val || 0; this.next = function () { return val ++ ; }; this.reset = function () { val = 0; } } creator.call(ns); // ns.next, ns.reset 此時已經存在 creator.call(ns1, 5000); // ns1包含相同的方法,但值被重寫爲5000了 console.log(ns.next()); //0 console.log(ns1.next());//5000
命名空間注入是用於爲多個模塊或命名空間指定一個相似的功能基本集,但最好是在聲明私有變量或者方法時再使用它,其餘時候使用嵌套命名空間已經足以知足須要了。
嵌套命名空間模式能夠爲代碼單元提供有組織的結構層級,但每次建立一個層級時,咱們也得確保其有相應的父層級。當層級數量很大時,會給咱們帶來很大的麻煩,咱們不能快速便捷的建立想建立的層級。那麼如何解決這個問題呢?Stoyan Stefanov提出,建立一個方法,其接收字符串參數做爲一個嵌套,解析它,並自動用所需的對象填充基本名稱空間。下面是這種模式的一種實現:
function extend(ns, nsStr) { var parts = nsStr.split("."), parent = ns, pl; pl = parts.length; for (var i = 0; i < pl; i++) { // 屬性若是不存在,則建立它 if (typeof parent[parts[i]] === "undefined") { parent[prats[i]] = {}; } parent = parent[parts[i]]; } return parent; } // 用法 var myApplication = myApplication || {}; var mod = extend(myApplication, "module.module2");
之前咱們必須爲其命名空間將各類嵌套顯式聲明爲對象,如今用上述更簡潔、優雅的方式就實現了。
參考地址:JavaScript之命名空間模式 淺析