JS之命名空間模式解析

来源:http://www.cnblogs.com/moqiutao/archive/2017/09/19/7553423.html
-Advertisement-
Play Games

簡介 在SF上看到這樣一個提問: 如題,因為不得已的原因,需要寫若幹個全局函數。但又不想這樣: 題主問有什麼好的寫法? 解答: 如果你用 jQuery,你可以這樣寫 如果你不用 jQuery,可以直接實現類似的 extend: 在JavaScript中,命名空間可以幫助我們防止與全局命名空間下的其他 ...


簡介

在SF上看到這樣一個提問:

如題,因為不得已的原因,需要寫若幹個全局函數。但又不想這樣:

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中的實 現方法。

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];
    }
}

另外一種實現方式:

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中雖然沒有提供原生的命名空間支持,但我們可以使用其他的方法(對象和閉包)實現類似的效果。下麵就是一些常見的命名空間模式:

1.單一全局變數

JavaScript中一個流行的命名空間模式是選擇一個全局變數作為主要的引用對象。因為每個可能的全局變數都成為唯一全局變數的屬性,也就不用再創建多個全局變數,那麼也就避免了和其他聲明的衝突。

單一全局變數模式已經在不少的JavaScript類庫中使用,如:

  • YUI定義了唯一的YUI全局對象
  • jQuery定義了$和jQuery,$由其他類庫使用時使用jQuery
  • Dojo定義了一個Dojo全局變數
  • Closure類庫定義了一個goog全局對象
  • Underscore類庫定義了一個_ 全局對象

示例如下:

var myApplication = (function() {
    var count = 1;
     function funcur() {
        console.log(count);
     };
     return {
        funcur:funcur
     }
 })();
myApplication.funcur();

雖然單一全局變數模式適合某些情況,但其最大的挑戰是確保單一全局變數在頁面中是唯一使用的,不會發生命名衝突

2.命名空間首碼

命名空間首碼模式其思路非常清晰,就是選擇一個獨特的命名空間,然後在其後面聲明聲明變數、方法和對象。示例如下:

var = myApplication_propertyA = {};
var = myApplication_propertyA = {};

function myApplication_myMethod() {
    // ***
}

從某種程度上來說,它確實減少了命名衝突的發生概率,但其並沒有減少全局變數的數目。當應用程式規模擴大時,就會產生很多的全局變數。在全局命名空間內,這種模式對其他人都沒有使用的這個首碼有很強的依賴,而且有些時候也不好判斷是否有人已經使用某個特殊首碼,在使用這種模式時一定要特別註意。

3.對象字面量表示法

對象字面量模式可以認為是包含一組鍵值對的對象,每一對鍵和值由冒號分隔,鍵也可以是代碼新的命名空間。示例如下:

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;

對象字面量為我們提供了優雅的鍵/值語法,我們可以非常便捷的組織代碼,封裝不同的邏輯或功能,而且可讀性、可維護性、可擴展性極強。

4.嵌套命名空間

嵌套命名空間模式可以說是對象字面量模式的升級版,它也是一種有效的避免衝突模式,因為即使一個命名空間存在,它也不太可能擁有同樣的嵌套子對象。示例如下:

var myApplication = myApplication || {};
 
 // 定義嵌套子對象
 myApplication.routers = myApplication.routers || {};
 myApplication.routers.test = myApplication.routers.test || {};

當然,我們也可以選擇聲明新的嵌套命名空間或屬性作為索引屬性,如:

myApplication['routers'] = myApplication['routers'] || {};

使用嵌套命名空間模式,可以使代碼易讀且有組織性,而且相對安全,不易產生衝突。其弱點是,如果我們的命名空間嵌套過多,會增加瀏覽器的查詢工作量,我們可以把要多次訪問的子對象進行局部緩存,以此來減少查詢時間。

5.立即調用的函數表達式

立即調用函數(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給命名空間添加更多的功能。

6.命名空間註入

命名空間註入是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

命名空間註入是用於為多個模塊或命名空間指定一個類似的功能基本集,但最好是在聲明私有變數或者方法時再使用它,其他時候使用嵌套命名空間已經足以滿足需要了。

7.自動嵌套的命名空間

嵌套命名空間模式可以為代碼單元提供有組織的結構層級,但每次創建一個層級時,我們也得確保其有相應的父層級。當層級數量很大時,會給我們帶來很大的麻煩,我們不能快速便捷的創建想創建的層級。那麼如何解決這個問題呢?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");

以前我們必須為其命名空間將各種嵌套顯式聲明為對象,現在用上述更簡潔、優雅的方式就實現了。

參考地址:http://www.cnblogs.com/syfwhu/p/4885628.html

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天做項目有一個功能,通過點擊事件複製一段文本到剪切板,在網上找了一些,整理了一下,方便需要的朋友使用。 複製文本 $(function(){ var clipboard = new Clipboard('#copy',{ text: function(trigger) { alert("複製成功!... ...
  • 接著這篇文章[js高手之路]Node.js+jade抓取博客所有文章生成靜態html文件繼續,在這篇文章中實現了採集與靜態文件的生成,在實際的採集項目中, 應該是先入庫再選擇性的生成靜態文件。 那麼我選擇的資料庫是mongodb,為什麼用這個資料庫,因為這個資料庫是基於集合,數據的操作基本是json ...
  • 這兩種寫法。這兩種寫法到底有什麼不同呢?用哪種來寫更加規範呢? 將href="#"是指聯接到當前頁面,其實是無意義的,頁面也不會刷新。這是一個錨鏈接。 在製作網頁時html語言里的參數,用於指定鏈接的url ####就是本頁鏈接,href="地址"就是鏈接到地址 鏈接本頁面 預設本頁,不彈出新視窗, ...
  • <!DOCTYPE html><html><head lang="zh-cn"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><m ...
  • function getobj(objs, key, value) { for (var i in objs) { var obj = $(objs[i]); if (obj.attr(key) == value) { return obj[0]; } } return null; }; ...
  • 首先瞭解下CSS的渲染邏輯,它是從標記的最後一位開始搜索的,例如:.myclass li a,首選它會遍歷所有的<a>,然後看哪些<a>之前有<li>,然後再看哪些<li>之前有.myclass。 所以:1、層級太多會增加CSS渲染的工作量。 如下: 除此之外,還有哪些可以優化的呢? 2、圖中樣式的 ...
  • 接著上篇vue-cli腳手架構建項目結構建好項目之後,就開始寫個“hello world!”吧~~~ vue玩的都是組件,所以開發的也是組件。 1.新建helloworld.vue。(刪除Hello.vue)代碼如下: 一個簡單的組件就完成了。 2.我們打開入口組件App.vue並把裡面的代碼替換成 ...
  • 使用 react已經有不短的時間了,最近看到關於 react高階組件的一篇文章,看了之後頓時眼前一亮,對於我這種還在新手村晃蕩、一切朝著打怪升級看齊的小嘍啰來說,像這種難度不是太高同時門檻也不是那麼低的東西如今可不多見了啊,是個不可多得的 zhuangbility的利器,自然不可輕易錯過,遂深入瞭解 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...