javascript 模塊化編程

来源:https://www.cnblogs.com/klsw/archive/2018/03/20/8612730.html
-Advertisement-
Play Games

The module pattern is a common JavaScript coding pattern. It’s generally well understood, but there are a number of advanced uses that have not gotten ...


 

The module pattern is a common JavaScript coding pattern. It’s generally well understood, but there are a number of advanced uses that have not gotten a lot of attention. In this article, I’ll review the basics and cover some truly remarkable advanced topics, including one which I think is original.

The Basics

We’ll start out with a simple overview of the module pattern, which has been well-known since Eric Miraglia (of YUI) first blogged about it three years ago. If you’re already familiar with the module pattern, feel free to skip ahead to “Advanced Patterns”.

Anonymous Closures

This is the fundamental construct that makes it all possible, and really is the single best feature of JavaScript. We’ll simply create an anonymous function, and execute it immediately. All of the code that runs inside the function lives in a closure, which provides privacy and state throughout the lifetime of our application.

(function () {
	// ... all vars and functions are in this scope only
	// still maintains access to all globals
}());

Notice the () around the anonymous function. This is required by the language, since statements that begin with the token function are always considered to be function declarations. Including () creates a function expression instead.

Global Import

JavaScript has a feature known as implied globals. Whenever a name is used, the interpreter walks the scope chain backwards looking for a var statement for that name. If none is found, that variable is assumed to be global. If it’s used in an assignment, the global is created if it doesn’t already exist. This means that using or creating global variables in an anonymous closure is easy. Unfortunately, this leads to hard-to-manage code, as it’s not obvious (to humans) which variables are global in a given file.

Luckily, our anonymous function provides an easy alternative. By passing globals as parameters to our anonymous function, we import them into our code, which is both clearer and faster than implied globals. Here’s an example:

(function ($, YAHOO) {
	// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));

Module Export

Sometimes you don’t just want to use globals, but you want to declare them. We can easily do this by exporting them, using the anonymous function’s return value. Doing so will complete the basic module pattern, so here’s a complete example:

var MODULE = (function () {
	var my = {},
		privateVariable = 1;

	function privateMethod() {
		// ...
	}

	my.moduleProperty = 1;
	my.moduleMethod = function () {
		// ...
	};

	return my;
}());

Notice that we’ve declared a global module named MODULE, with two public properties: a method named MODULE.moduleMethod and a variable named MODULE.moduleProperty. In addition, it maintains private internal stateusing the closure of the anonymous function. Also, we can easily import needed globals, using the pattern we learned above.

Advanced Patterns

While the above is enough for many uses, we can take this pattern farther and create some very powerful, extensible constructs. Lets work through them one-by-one, continuing with our module named MODULE.

Augmentation

One limitation of the module pattern so far is that the entire module must be in one file. Anyone who has worked in a large code-base understands the value of splitting among multiple files. Luckily, we have a nice solution to augment modules. First, we import the module, then we add properties, then we export it. Here’s an example, augmenting our MODULE from above:

var MODULE = (function (my) {
	my.anotherMethod = function () {
		// added method...
	};

	return my;
}(MODULE));

We use the var keyword again for consistency, even though it’s not necessary. After this code has run, our module will have gained a new public method named MODULE.anotherMethod. This augmentation file will also maintain its own private internal state and imports.

Loose Augmentation

While our example above requires our initial module creation to be first, and the augmentation to happen second, that isn’t always necessary. One of the best things a JavaScript application can do for performance is to load scripts asynchronously. We can create flexible multi-part modules that can load themselves in any order with loose augmentation. Each file should have the following structure:

var MODULE = (function (my) {
	// add capabilities...

	return my;
}(MODULE || {}));

In this pattern, the var statement is always necessary. Note that the import will create the module if it does not already exist. This means you can use a tool like LABjs and load all of your module files in parallel, without needing to block.

Tight Augmentation

While loose augmentation is great, it does place some limitations on your module. Most importantly, you cannot override module properties safely. You also cannot use module properties from other files during initialization (but you can at run-time after intialization). Tight augmentation implies a set loading order, but allows overrides. Here is a simple example (augmenting our original MODULE):

var MODULE = (function (my) {
	var old_moduleMethod = my.moduleMethod;

	my.moduleMethod = function () {
		// method override, has access to old through old_moduleMethod...
	};

	return my;
}(MODULE));

Here we’ve overridden MODULE.moduleMethod, but maintain a reference to the original method, if needed.

Cloning and Inheritance

var MODULE_TWO = (function (old) {
	var my = {},
		key;

	for (key in old) {
		if (old.hasOwnProperty(key)) {
			my[key] = old[key];
		}
	}

	var super_moduleMethod = old.moduleMethod;
	my.moduleMethod = function () {
		// override method on the clone, access to super through super_moduleMethod
	};

	return my;
}(MODULE));

This pattern is perhaps the least flexible option. It does allow some neat compositions, but that comes at the expense of flexibility. As I’ve written it, properties which are objects or functions will not be duplicated, they will exist as one object with two references. Changing one will change the other. This could be fixed for objects with a recursive cloning process, but probably cannot be fixed for functions, except perhaps with eval. Nevertheless, I’ve included it for completeness.

Cross-File Private State

One severe limitation of splitting a module across multiple files is that each file maintains its own private state, and does not get access to the private state of the other files. This can be fixed. Here is an example of a loosely augmented module that will maintain private state across all augmentations:

var MODULE = (function (my) {
	var _private = my._private = my._private || {},
		_seal = my._seal = my._seal || function () {
			delete my._private;
			delete my._seal;
			delete my._unseal;
		},
		_unseal = my._unseal = my._unseal || function () {
			my._private = _private;
			my._seal = _seal;
			my._unseal = _unseal;
		};

	// permanent access to _private, _seal, and _unseal

	return my;
}(MODULE || {}));

Any file can set properties on their local variable _private, and it will be immediately available to the others. Once this module has loaded completely, the application should call MODULE._seal(), which will prevent external access to the internal _private. If this module were to be augmented again, further in the application’s lifetime, one of the internal methods, in any file, can call _unseal() before loading the new file, and call _seal() again after it has been executed. This pattern occurred to me today while I was at work, I have not seen this elsewhere. I think this is a very useful pattern, and would have been worth writing about all on its own.

Sub-modules

Our final advanced pattern is actually the simplest. There are many good cases for creating sub-modules. It is just like creating regular modules:

MODULE.sub = (function () {
	var my = {};
	// ...

	return my;
}());

While this may have been obvious, I thought it worth including. Sub-modules have all the advanced capabilities of normal modules, including augmentation and private state.

Conclusions

Most of the advanced patterns can be combined with each other to create more useful patterns. If I had to advocate a route to take in designing a complex application, I’d combine loose augmentationprivate state, and sub-modules.

I haven’t touched on performance here at all, but I’d like to put in one quick note: The module pattern is good for performance. It minifies really well, which makes downloading the code faster. Using loose augmentation allows easy non-blocking parallel downloads, which also speeds up download speeds. Initialization time is probably a bit slower than other methods, but worth the trade-off. Run-time performance should suffer no penalties so long as globals are imported correctly, and will probably gain speed in sub-modules by shortening the reference chain with local variables.

To close, here’s an example of a sub-module that loads itself dynamically to its parent (creating it if it does not exist). I’ve left out private state for brevity, but including it would be simple. This code pattern allows an entire complex heirarchical code-base to be loaded completely in parallel with itself, sub-modules and all.

var UTIL = (function (parent, $) {
	var my = parent.ajax = parent.ajax || {};

	my.get = function (url, params, callback) {
		// ok, so I'm cheating a bit :)
		return $.getJSON(url, params, callback);
	};

	// etc...

	return parent;
}(UTIL || {}, jQuery));

I hope this has been useful, and please leave a comment to share your thoughts. Now, go forth and write better, more modular JavaScript!

中文解釋:

模塊模式是JavaScript一種常用的編碼模式。這是一般的理解,但也有一些高級應用沒有得到很多關註。在本文中,我將回顧基礎知識,瀏覽一些不錯的高級技巧,甚至我認為是原生基礎的。  

基礎知識

首先我們開始簡單概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式眾所周知。如果你已經很熟悉模型模式,可以直接閱讀“高級模式”。

匿名閉包

這是一切成為可能的基礎,也是JavaScript最好的特性。我們將簡單的創建匿名函數,並立即執行。所有函數內部代碼都在閉包(closure)內。它提供了整個應用生命周期的私有和狀態。

(function () {
	// ... all vars and functions are in this scope only
	// still maintains access to all globals
}());

註意匿名函數周圍的()。這是語言的要求。關鍵字function一般認為是函數聲明,包括()就是函數表達式。

 

引入全局

JavaScript有個特性,稱為隱性全局。使用變數名稱時,解釋器會從作用域向後尋找變數聲明。如果沒找到,變數會被假定入全局(以後可以全局調用)。如果會被分配使用,在還不存在時全局創建它。這意味著在匿名函數里使用全局變數很簡單。不幸的是,這會導致代碼難以管理,文件中不容易區分(對人而言)哪個變數是全局的。

幸好,匿名函數還有一個不錯的選擇。全局變數作為參數傳遞給匿名函數。將它們引入我們的代碼中,既更清晰,又比使用隱性全局更快。下麵是一個例子:

(function ($, YAHOO) {
	// 當前域有許可權訪問全局jQuery($)和YAHOO
}(jQuery, YAHOO));
   翻譯得不錯哦!  

模塊出口

有時你不只想用全局變數,但你需要先聲明他們(模塊的全局調用)。我們用匿名函數的返回值,很容易輸出他們。這樣做就完成了基本的模塊模式。以下是一個完整例子:

var MODULE = (function () {
	var my = {},
		privateVariable = 1;
	
	function privateMethod() {
		// ...
	}
	
	my.moduleProperty = 1;
	my.moduleMethod = function () {
		// ...
	};
	
	return my;
}());

註意,我們聲明瞭一個全局模塊MODULE,有兩個公開屬性:方法MODULE.moduleMethod和屬性MODULE.moduleProperty。而且,匿名函數的閉包還維持了私有內部狀態。同時學會之上的內容,我們就很容易引入需要的全局變數,和輸出到全局變數。

 

高級模式

對許多用戶而言以上的還不足,我們可以採用以下的模式創造強大的,可擴展的結構。讓我們使用MODULE模塊,一個一個繼續。

擴充

模塊模式的一個限制是整個模塊必須在一個文件里。任何人都瞭解長代碼分割到不同文件的必要。還好,我們有很好的辦法擴充模塊。(在擴充文件)首先我們引入模塊(從全局),給他添加屬性,再輸出他。下麵是一個例子擴充模塊:

var MODULE = (function (my) {
	my.anotherMethod = function () {
		// 此前的MODULE返回my對象作為全局輸出,因此這個匿名函數的參數MODULE就是上面MODULE匿名函數里的my
	};

	return my;
}(MODULE));

我們再次使用var關鍵字以保持一致性,雖然其實沒必要。代碼執行後,模塊獲得一個新公開方法MODULE.anotherMethod。擴充文件沒有影響模塊的私有內部狀態

 

松耦合擴充

上面的例子需要我們首先創建模塊,然後擴充它,這並不總是必要的。提升JavaScript應用性能最好的操作就是非同步載入腳本。因而我們可以創建靈活多部分的模塊,可以將他們無順序載入,以松耦合擴充。每個文件應有如下的結構:

var MODULE = (function (my) {
	// add capabilities...
	
	return my;
}(MODULE || {}));

這個模式里,var語句是必須的,以標記引入時不存在會創建。這意味著你可以像LABjs一樣同時載入所有模塊文件而不被阻塞。

   

緊耦合擴充

雖然松耦合很不錯,但模塊上也有些限制。最重要的,你不能安全的覆寫模塊屬性(因為沒有載入順序)。初始化時也無法使用其他文件定義的模塊屬性(但你可以在初始化後運行)。緊耦合擴充意味著一組載入順序,但是允許覆寫。下麵是一個例子(擴充最初定義的MODULE):

var MODULE = (function (my) {
	var old_moduleMethod = my.moduleMethod;
	
	my.moduleMethod = function () {
		// method override, has access to old through old_moduleMethod...
	};
	
	return my;
}(MODULE));

我們覆寫的MODULE.moduleMethod,但依舊保持著私有內部狀態

 

克隆和繼承

var MODULE_TWO = (function (old) {
	var my = {},
		key;
	
	for (key in old) {
		if (old.hasOwnProperty(key)) {
			my[key] = old[key];
		}
	}
	
	var super_moduleMethod = old.moduleMethod;
	my.moduleMethod = function () {
		// override method on the clone, access to super through super_moduleMethod
	};
	
	return my;
}(MODULE));

這種方式也許最不靈活。他可以實現巧妙的組合,但是犧牲了靈活性。正如我寫的,對象的屬性或方法不是拷貝,而是一個對象的兩個引用。修改一個會影響其他。這可能可以保持遞歸克隆對象的屬性固定,但無法固定方法,除了帶eval的方法。不過,我已經完整的包含了模塊。(其實就是做了一次淺拷貝)。

 

跨文件私有狀態

一個模塊分割成幾個文件有一個嚴重缺陷。每個文件都有自身的私有狀態,且無權訪問別的文件的私有狀態。這可以修複的。下麵是一個松耦合擴充的例子,不同擴充文件之間保持了私有狀態:

var MODULE = (function (my) {
	var _private = my._private = my._private || {},
		_seal = my._seal = my._seal || function () {
			delete my._private;
			delete my._seal;
			delete my._unseal;
		},//模塊載入後,調用以移除對_private的訪問許可權
		_unseal = my._unseal = my._unseal || function () {
			my._private = _private;
			my._seal = _seal;
			my._unseal = _unseal;
		};//模塊載入前,開啟對_private的訪問,以實現擴充部分對私有內容的操作
	
	// permanent access to _private, _seal, and _unseal
	
	return my;
}(MODULE || {}));

任何文件都可以在本地的變數_private中設置屬性,他會對別的擴充立即生效(即初始化時所有擴充的私有狀態都保存在_private變數,並被my._private輸出)。模塊完全載入了,應用調用MODULE._seal()方法阻止對私有屬性的讀取(幹掉my._private輸出)。如果此後模塊又需要擴充,帶有一個私有方法。載入擴充文件前調用MODULE._unseal()方法(恢復my._private,外部恢復操作許可權)。載入後調用再seal()。 

子模塊

最後的高級模式實際上最簡單。有很多好方法創建子模塊。和創建父模塊是一樣的:

MODULE.sub = (function () {
	var my = {};
	// 就是多一級命名空間
	
	return my;
}());

雖然很簡單,但我還是提一下。子模塊有所有正常模塊的功能,包括擴充和私有狀態。


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

-Advertisement-
Play Games
更多相關文章
  • 一、前言 最近由於公司同事離職,頂替這位同事從事手機App的研發工作,BIM數據平臺部門採用的是HBuilder作為手機App的製作環境。本篇介紹我是如何將HBuilder的Release包發佈至App Store的。 二、內容 1. 首先登錄Apple Developer網站 2. 點擊iTune ...
  • 簡要:本系列文章講會對expo進行全面的介紹,本人從2017年6月份接觸expo以來,對expo的研究斷斷續續,一路走來將近10個月,廢話不多說,接下來你看到內容,講全部來與官網 我猜去全部機翻+個人修改補充+demo測試的形式,對expo進行一次大補血!歡迎加入expo興趣學習交流群:597732 ...
  • CRC校驗 迴圈冗餘校驗(Cyclic Redundancy Check, CRC)是一種根據網路數據包或電腦文件等數據產生簡短固定位數校驗碼的一種散列函數,主要用來檢測或校驗數據傳輸或者保存後可能出現的錯誤。它是利用除法及餘數的原理來作錯誤偵測的。在數據傳輸過程中,無論傳輸系統的設計再怎麼完美,差 ...
  • 前言 為什麼要說 WKWebview,在之前做電子書筆記時已經提過 WKWebview 在iOS8之後已完全替代 Webview,原因就不多說了,主要還是記憶體過大; 封裝 封裝一個基於 UIViewController 類: WKWebViewController WKWebViewControll ...
  • 一:業務描述 最近公司有一個小需求,用戶點擊wifi掃描按鈕(註意:是用戶主動點擊wifi掃描按鈕),app去掃描附近的wifi,顯示在listView中,僅此而已,app都不用去連接某個wifi,看似簡單的需求,如果沒處理好,可能導致app直接掛掉。 二:代碼如下 註冊接收掃描wifi的廣播 啟動 ...
  • Core Data 多線程環境 Core Data 對併發模式的支持非常完備,NSManagedObjectContext 的指定初始化方法中就指定了併發模式: 兩種模式(本來三種,iOS 9 捨棄了一種): a. (捨棄了)confinementConcurrencyType 這種模式是用於向後兼 ...
  • 最近在開發中有一個移植代碼的工作,但是呢那塊代碼是n多年前寫的,雖然還沒看時就感覺到坑不小,到真正看的時候才發現是個隕石坑。具體的說呢,所有的界面都是xib托的而且沒用Auto Layout約束,而且還是直接單個控制項單打獨鬥內有父視圖,根本無法適配iPhone X,導致牽一發而動全身,無奈之下只好從 ...
  • html部分 css部分 js部分 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...