擁抱模塊化的JavaScript

来源:http://www.cnblogs.com/javascript-center/archive/2016/05/26/5532936.html
-Advertisement-
Play Games

前言 我們再一次被電腦的名詞、概念籠罩。 Backbone、Emberjs、Spinejs、Batmanjs 等MVC框架侵襲而來。CommonJS、AMD、NodeJS、RequireJS、SeaJS、Curljs 等模塊化的JavaScript概念及庫撲面而來。 模塊化JavaScript的概 ...


前言

我們再一次被電腦的名詞、概念籠罩。

Backbone、Emberjs、Spinejs、Batmanjs 等MVC框架侵襲而來。
CommonJS、AMD、NodeJS、RequireJS、SeaJS、Curljs 等模塊化的JavaScript概念及庫撲面而來。

模塊化JavaScript的概念尤為突出,似乎有趕超07年Ajax風潮之趨勢。

寫函數(過程式)
2005年以前,JavaScript沒人重視,只作為表單驗證等少量應用。那時一個網頁上寫不了幾行JS代碼,1000行算很複雜了。這時組織代碼的方式是過程式,幾十行的代碼甚至連一個函數都不用寫。稍多的需要提取抽象出一個函數,更複雜一些則需要更多函數,函數間互相調用。

寫類(面向對象)
2006年,Ajax席卷全球。JavaScript被重視了,越來越多的後端邏輯放到了前端。網頁中的JS代碼量急劇增加。這時寫函數方式組織大量代碼顯得力不從心。有時調試一個小功能,從一個函數可能會跳到第N個函數去。這時寫類的方式出現了,Prototype 率先流行開來。用它組織代碼,寫出的都是一個個類,每個類都是Class.create創建的。又有YUI、Ext等重量級框架。雖然它們的寫類方式各不同,但它們的設計思路卻都是要滿足大量JavaScript代碼的開發。

寫模塊(現在,未來?)
2009年,Nodejs誕生!這個伺服器端的JavaScript採用模塊化的寫法很快征服了瀏覽器端的JSer。牛人們紛紛仿效,各種寫模塊的規範也是層出不窮。CommonJS想統一前後端的寫法,AMD則認為自己是適合瀏覽器端的。好吧,無論寫模塊的風格是啥樣,寫模塊化的JavaScript卻已開始流行了。你,準備好了嗎?

模塊化的JavaScript是神馬? 這是我們發明瞭又一個銀彈嗎?無論是啥,就當學習吧。至於適不適合項目中使用,各自斟酌。

寫到這也沒說什麼是“模塊”。其實在電腦領域,模塊化的概念被推崇了近四十年。軟體總體結構體現模塊化思想,即把軟體劃分為一些獨立命名的部件,每個部件稱為一個模塊,當把所有模塊組裝在一起的時候,便可獲得問題的一個解。模塊化以分治法為依據,但是否意味著我們把軟體無限制的細分下去?事實上當分割過細,模塊總數增多,每個模塊的成本確實減少了,但模塊介面所需代價隨之增加。

要確保模塊的合理分割則須瞭解信息隱藏,內聚度及耦合度。

信息隱藏
模塊應設計的使其所包含的信息(過程和數據)對於那些不需要用到它的模塊不可見。每個模塊只完成一個獨立的功能,然後提供該功能的介面。模塊間通過介面訪問。JavaScript中可以用函數去隱藏,封裝,而後返回介面對象。如下是一個提供事件管理的模塊event。

 
1 2 3 4 5 6 7 8 Event = function() {     // do more     return {         bind: function() {},         unbind: function() {},         trigger: function() {}     }; }();

函數內為了實現想要的介面bind、unbind、trigger可能需要寫很多很多代碼,但這些代碼(過程和數據)對於其它模塊來說不必公開,外部只要能訪問介面bind,unbind,trigger即可。
信息隱藏對於模塊設計好處十分明顯,它不僅支持模塊的並行開發,而且還可減少測試或後期維護工作量。如日後要修改代碼,模塊的隱藏部分可隨意更改,前提是介面不變。如事件模塊開始實現時為了相容舊版本IE及標準瀏覽器,寫了很多IE Special代碼,有一天舊版本IE消失了(猴年馬月),只需從容刪去即可。

內聚度
內聚是來自結構化設計的一個概念,簡單說內聚測量了單個模塊內各個元素的聯繫程度。最不希望出現的內聚就是偶然性內聚,即將完全無關的抽象塞進同一個模塊或類中。最希望出現的內聚是功能性內聚,即一個模塊或類的各元素一同工作,提供某種清晰界定的行為。
內聚度指模塊內部實現,它是信息隱藏和局部化概念的自然擴展,它標志著一個模塊內部各成分彼此結合的緊密程度。好處也很明顯,當把相關的任務分組後去閱讀就容易多了。設計時應該儘可能的提高模塊內聚度,從而獲得較高的模塊獨立性。

耦合度
耦合也是來自結構化設計,Stevens、Myers和Constantine將耦合定義為「一個模塊與另一個模塊之間建立起的關聯強度的測量。強耦合使系統變得複雜,因為如果模塊與其它模塊高度相連,它就難以獨立的被理解、變化和修正」
內聚度是指特定模塊內部實現的一種度量,耦合度則是指模塊之間的關聯程度的度量。耦合度取決於模塊之間介面的複雜性,進入或調用模塊的位置等。與內聚度相反,在設計時應儘量追求鬆散耦合的系統。

JavaScript中模塊“寫法”

在JavaScript模塊到底是什麼,能用代碼具體展現一下嗎?其實上面已經寫了一段事件模塊代碼

這能代表“模塊”嗎?這就是一個JS對象啊,以為有多麼深奧。

是的,JavaScript中模塊多數時候被實現為一個對象。這麼看來,多數時候我們都寫過“模塊”(但沒有在整個項目中應用模塊化思想)。或許每個人寫模塊的方式(風格)還不同。比如上面的事件模塊是一個匿名函數執行,匿名函數中封裝了很多代碼,最後通過return返回給Event變數,這個Event就是事件模塊的介面。

又如jQuery,它也是一個匿名函數執行,但它並不返回介面對象。而是將自己暴露給window對象。

 
1 2 3 4 5 (function(window){     // ..     // exports     window.jQuery = window.$ = jQuery; })(window);

再如SeaJS,它一開始就將介面公開了

 
1 2 3 4 /** * Base namespace for the framework. */ this.seajs = { _seajs: this.seajs };

後續是很多的匿名函數執行給變數seajs添加很多工具方法。註意,這裡的this在瀏覽器環境指window對象,如果是定位在瀏覽器中,這個this也可以去掉。就象Ext。

 
1 2 3 4 5 6 7 Ext = {     /**      * The version of the framework      * @type String      */     version : '3.1.0' };

我們已經看到了四種方式寫模塊(把jQuery,SeaJS,Ext看成模塊,呃很大的模塊)。哪一種更好呢? 哪一種更適合在瀏覽器端呢?純從代碼風格上說,是蘿蔔白菜各有所愛。只要我們運用了“模塊化”的思想來開發就行了。

但如果有一種統一的語法格式來寫模塊豈不是更好,就不會出現各用各的風格來寫模塊而使代碼亂糟糟。

這就是目前的現狀,開發者強烈需要一種統一的風格來寫模塊(最好是語言內置了)。這時一些組織出現了,最具代表的如CommonJS,AMD。此外ECMAScript也開始著手模塊的標準化寫法。

無論它們提供什麼樣的寫法,我們需要的僅僅是:

  • 將一些零散代碼封裝成一個有用的單元(encapsulate)
  • 導出模塊的介面API(exports)
  • 方便友好引用其它模塊(dependency)

伺服器端的JSer是幸運的,它有Node.js,Node.js遵循了一個稱為CommonJS的規範。CommonJS其中就有對寫模塊的標準化。當然模塊化只是其中的一部分而已。

具體來說Node.js實現了:

在模塊化方面,它實現了Modules/1.0(已經更新到1.1.1),以下是node中是寫模塊的一個示例。

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 MATH.JS exports.add = function() {     var sum = 0, i = 0, args = arguments, l = args.length;     while (i < l) {         sum += args[i++];     }     return sum; };   INCREMENT.JS var add = require('math').add; exports.increment = function(val) {     return add(val, 1); };   MAIN.JS var inc = require('increment').increment; var a = 1; inc(a); // 2

這就寫了一個math、increment、main模塊。math提供了add方法來實現數字相加。increment模塊依賴於math模塊,它提供increment方法實現相加。main獲取到increment方法,執行相加操作。

以上代碼示例可以看到:

  • node要求一個js文件對應一個模塊。可以把該文件中的代碼想象成是包在一個匿名函數中,所有代碼都在匿名函數中,其它模塊不可訪問除exports外的私有變數
  • 使用exports導出API
  • 使用require載入其它模塊

CommonJS module基本要求如下:

  • 標示符require,為一個函數,它僅有一個參數為字元串,該字元串須遵守Module Identifiers的6點規定
  • require方法返回指定的模塊API
  • 如果存在依賴的其它模塊,那麼依次載入
  • require不能返回,則拋異常
  • 僅能使用標示符exports導出API

Modules/1.1較1.0僅增加了標示符module,require函數增加了main和paths屬性。而仔細比對1.1與1.1.1後發現除了格式調整了下幾乎沒有變化。

Node.js模塊格式在瀏覽器中的嘗試

前面提到Node.js有一套簡潔的格式寫模塊,它遵循的就是 Moudles。
瀏覽器里的JavaScript呢? 儘管語言本身暫不支持模塊(ES6打算支持),但可以用現有的API包裝一個寫法出來。
毫無疑問,首先想到的是Node.js的Modules格式,它是最好的效仿對象。因為前後端有一個統一的方式寫JS模塊豈不樂哉!
但一開始就碰到一些難題:
伺服器端JS模塊文件就在本地,瀏覽器端則需要通過網路請求。
伺服器端可以很容易的實現同步或非同步請求模塊,瀏覽器端則問題多多。
如下。

 
1 2 3 4 var event = require("event"); event.bind(el, 'click', function() {     // todo });

這段代碼中require如果是非同步執行的,則event.bind的執行有可能會出錯。
那實現同步的require不就行了嗎?的確可以使用 XHR 實現同步載入模塊JS文件。但XHR的缺點也是明顯的,它不能跨域,這點讓人很難接受,因為有些場景需要模塊部署在不同的伺服器。
那隻能通過script tag來實現模塊載入了!但script tag預設就是非同步的,要實現Node.js的一模一樣風格(Modules)很難,幾乎是不可能。

這時,“救世主”出現了:Modules/Wrappings ,顧名思義包裹的模塊。該規範約定如下:

  • 定義模塊用module變數,它有一個方法declare。
  • declare接受一個函數類型的參數,如稱為factory。
  • factory有三個參數分別為require、exports、module。
  • factory使用返回值和exports導出API。
  • factory如果是對象類型,則將該對象作為模塊輸出。

描述有拗口,代碼卻很簡單,使用了一個function包裹模塊(Node.js模塊則無需包裹)。

 
1 2 3 4 5 6 7 8 9 10 11 一個基本的模塊定義 module.declare(function(require, exports, module) {     exports.foo = "bar"; });   直接使用對象作為模塊 module.declare( {     foo: "bar" });

Modules/Wrappings的出現使得瀏覽器中實現它變得可能,包裹的函數作為回調。即使用script tag作為模塊載入器,script完全下載後去回調,回調中進行模塊定義。

好了,截止目前我們已經看到了兩種風格的模塊定義:Modules 和 Modules/Wrappings。

CommonJS Modules有1.0、1.1、1.1.1三個版本:
Node.js、SproutCore實現了 Modules 1.0
SeaJS、AvocadoDB、CouchDB等實現了Modules 1.1.1
SeaJS、FlyScript實現了Modules/Wrappings

註意:
SeaJS未實現全部的 Modules 1.1.1,如require函數的main,paths屬性在SeaJS中沒有。但SeaJS給require添加了async、resolve、load、constructor。
SeaJS沒有使用 Modules/Wrappings 中的module.declare定義模塊,而是使用define函數(看起來象AMD中的define,實則不然)。

AMD:瀏覽器中的模塊規範

前面提到,為實現與Node.js相同方式的模塊寫法,大牛們做了很多努力。

但瀏覽器環境不同於伺服器端,它的模塊有一個HTTP請求過程(而Node.js的模塊文件就在本地),這個請求過程多數使用script tag,script 預設的非同步性導致很難實現與Node.js一模一樣的模塊格式。

Modules/Wrappings 使得實現變為現實。雖然和Node.js的模塊寫法不完全一致,但也有很多相似之處,使得熟悉Node.js的程式員有一些親切感。

但Node.js終究是伺服器端的JavaScript,沒有必要把這些條條框框放到瀏覽器JavaScript環境中。

這時AMD 誕生了,它的全稱為非同步模塊定義。從名稱上看便知它是適合script tag的。也可以說AMD是專門為瀏覽器中JavaScript環境設計的規範。它吸取了CommonJS的一些優點,但又不照搬它的格式。開始AMD作為CommonJS的transport format 存在,因無法與CommonJS開發者達成一致而獨立出來。它有自己的wiki 和討論組 。

AMD設計出一個簡潔的寫模塊API:
define(id?, dependencies?, factory);
其中:
id: 模塊標識,可以省略。
dependencies: 所依賴的模塊,可以省略。
factory: 模塊的實現,或者一個JavaScript對象。
特別指出,id遵循CommonJS Module Identifiers 。dependencies元素的順序和factory參數一一對應。

以下是使用AMD模式開發的簡單三層結構(基礎庫/UI層/應用層),用於展示模塊的五種寫法:

  1. 定義無依賴的模塊(base.js)
  2. 定義有依賴的模塊(ui.js,page.js)
  3. 定義數據對象模塊(data.js)
  4. 具名模塊
  5. 包裝模塊

 

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 BASE.JS   define(function() {     return {         mix: function(source, target) {         }     }; });   UI.JS define(['base'], function(base) {     return {         show: function() {             // todo with module base         }     } });   PAGE.JS define(['data', 'ui'], function(data, ui) {     // init here });   DATA.JS define({     users: [],     members: [] });

以上同時演示了define的前三種用法。細心的會發現,還有兩種沒有出現:

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 具名模塊   define('index', ['data','base'], function(data, base) {     // todo });   包裝模塊   define(function(require, exports, module) {     var base = require('base');     exports.show = function() {         // todo with module base     } });

如果不考慮多了一層函數外,格式和Node.js是一樣的:使用require獲取依賴模塊,使用exports導出API。

除了define外,AMD還保留一個關鍵字require。require 作為規範保留的全局標識符,可以實現為 module loader,也可以不實現。

目前,實現AMD的庫有RequireJS 、curl 、Dojo 、bdLoad、JSLocalnet 、Nodules 等。也有很多庫支持AMD規範,即將自己作為一個模塊存在,如MooTools 、jQuery 、qwery 、bonzo 甚至還有 firebug 。

UMD:各種模塊格式的糅合

UMD是AMD 和CommonJS的糅合,前面花了很長的篇幅介紹了兩大類模塊規範,CommonJS(Modules/Modules/Wrappings)及AMD。

我們知道Modules/Wrappings是出於對Node.js模塊格式的偏好而包裝下使其在瀏覽器中得以實現。而Modules/Wrappings的格式通過某些工具(如r.js)也能運行在Node.js中。事實上,這兩種格式同時有效且都被廣泛使用。

AMD以瀏覽器為第一(browser-first)的原則發展,選擇非同步載入模塊。它的模塊支持對象(objects)、函數(functions)、構造器(constructors)、字元串(strings)、JSON等各種類型的模塊。因此在瀏覽器中它非常靈活。

CommonJS module以伺服器端為第一(server-first)的原則發展,選擇同步載入模塊。它的模塊是無需包裝的(unwrapped modules)且貼近於ES.next/Harmony的模塊格式。但它僅支持對象類型(objects)模塊。這迫使一些人又想出另一個更通用格式 UMD(Universal Module Definition)。希望提供一個前後端跨平臺的解決方案。

UMD的實現很簡單,先判斷是否支持Node.js模塊格式(exports是否存在),存在則使用Node.js模塊格式。接著判斷是否支持AMD(define是否存在),存在則使用AMD方式載入模塊。前兩個都不存在,則將模塊公開到全局(window或global)。下麵是一個示例:

下麵是一個示例:

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 EVENTUTIL.JS (function (root, factory) {     if (typeof exports === 'object') {         module.exports = factory();              } else if (typeof define === 'function' && define.amd) {         define(factory);              } else {         root.eventUtil = factory();     } })(this, function() {     // module     return {         addEvent: function(el, type, handle) {             //...         },         removeEvent: function(el, type, handle) {
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在做校園網視頻網站的時候,首頁有一個導航頁面要實現滾動效果,有樣例,但代碼是在難弄懂,貌似網頁設計這塊還是只有自己的代碼自己懂,索性就仿造別人的效果自己做了一個,大體上還行,看起來還是比較流暢的,不次於原作的么。 現在先把代碼拷貝到這裡,以後再逐一簡化修改: 實現混動效果,腳本代碼如下: var a ...
  • 一:跑通ui-router. ui-router源碼在最後面 跑通後的樣子: 這個不解釋了,都是很基本的東西. 二:切換視圖: 這裡的name可以不寫,但是你得放到state的第一個參數里. 跑起來後的後果: 三:如何通過鏈接切換視圖. 1 <!DOCTYPE html> 2 <html> 3 <h ...
  • 如果你是一名前端開發工程師,一般px和em使用頻率比較高。但是今天本文的重點是介紹一些我們使用很少、甚至麽有聽說的單位。 一、重溫em 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <style type="text/css"> body {font-size: 12px; ...
  • 由於游戲類官網在頁面背景和裝飾人物的設計上追求畫麗且與游戲風格想匹配,這就給前端頁面製作人員帶來了很多的麻煩,一個頁面的製作主要時間和精力花費在相容ie6上,而ie6因為不相容png-24的圖片一直被開發人員所鄙視。 由於市場決定了頁面的存在的價值,所以ie6還是必須要相容。 下麵介紹幾種常用的解決 ...
  • 在JavaScript中,正則表達式由RegExp對象表示。RegExp對象呢,又可以通過直接量和構造函數RegExp兩種方式創建,分別如下: 其中,末尾的可選字元(g、i和m)分別表示: g: 模式執行一個全局匹配。簡而言之,就是找到所有匹配,而不是在找到第一個之後就停止。 i: 模式執行不區分大 ...
  • 恢復內容開始 接下來項目需要網頁相關知識,故在大牛的指引下前來閱讀本書。 當前水平:HTML&CSS&JS基本掌握,能在閱讀文檔以及Google查找的情況下完成前端代碼編寫,但是學習不深,HTML5&CSS3新特性、JS基礎&框架皆不熟悉 讀書目的:瞭解DOM概念並通過訓練熟悉掌握,瞭解JS特性 博 ...
  • 現在最熱門的前端框架有AngularJS、React、Bootstrap等。自從接觸了ReactJS,ReactJs的虛擬DOM(Virtual DOM)和組件化的開發深深的吸引了我,下麵來跟我一起領略ReactJS的風采吧~~ 章有點長,耐心讀完,你會有很大收穫哦~ 一、ReactJS簡介 Rea ...
  • 前端開發知識點大綱: HTML&CSS:對Web標準的理解、瀏覽器內核差異、相容性、hack、CSS基本功:佈局、盒子模型、選擇器優先順序及使用、HTML5、CSS3、Flexbox、移動端適應。 JavaScript:數據類型、運算、對象(面向對象)、Function、繼承、閉包、作用域、插件、作用 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...