摘要:與在模塊化編程的世界中,有兩個規范不得不提,它們分別是和。所有依賴于某個模塊的代碼全部移到模塊加載語句的回調函數中去。的語句接受兩個參數在回調函數中,可以通過變量引用模塊。回調函數的返回值就是當前對象的導出值。
JavaScript本身不是一種模塊化語言,設計者在創造JavaScript之初應該也沒有想到這么一個腳本語言的作用領域會越來越大。以前一個頁面的JS代碼再多也不會多到哪兒去,而現在隨著越來越多的JavaScript庫和框架的出現,Single-page App的流行以及Node.js的迅猛發展,如果我們還不對自己的JS代碼進行一些模塊化的組織的話,開發過程會越來越困難,運行性能也會越來越低。因此,了解JS模塊化編程是非常重要的。
簡單的模塊什么是模塊?我認為將不同功能的函數放在一起,組成一個能實現某種或某些特定功能的整體就是一個模塊,因此這樣:
function add(a, b) { return a + b; } function divide(a, b) { return a / b; }
如此簡單的兩個函數就可以組成一個模塊,這個模塊可以進行一些數學運算。
當然沒有人會這么寫模塊。僅僅是從“型”上來看,兩個函數分散在全局環境中,這也看不出模塊的特點。模塊存在于全局變量中,應該提供一個命名空間,成為模塊內容的入口。那么我們可以將函數包裹在一個對象中:
var math = { add: function(a, b) { return a + b; }, divide: function(a, b) { return a / b; } }
這樣看起來似乎有模塊的“型”了。但是這樣還不完善,math中的所有成員都是對外暴露的,如果其中有一些變量不希望被修改的話那就有風險了。為了防止世界被破壞,為了維護私有變量不被修改,我們可以使用閉包。
var math = (function() { var _flag = 0; return { add: function(a, b) { return a + b; }, divide: function(a, b) { return a / b; } }; })();
外部代碼只能訪問返回的add和divide方法,內部的_flag變量是不能訪問的。關于創建對象的一些方法的解釋,可以參考我的另一篇博文,里面有較詳細的解釋。
利用自執行函數的特點,我們還可以很方便地為模塊添加方法:
var math = (function(module) { module.subtract = function(a, b) { return a - b; } })(math);
模塊在全局變量中的名稱可能會與其他的模塊產生沖突,例如$符號,雖然使用方便,但多個模塊可能都會用它作為自己的簡寫,例如jQuery。我們可以在模塊的組織代碼中用$作為形參,將模塊的全名變量作為參數傳入,可起到防沖突的效果。
var math = (function($) { // 這里的$指的就是Math })(math);
模塊的構建思想便是通過這樣的方式逐漸演化而來,下面將通過介紹一些JS模塊化編程的標準來展示如何組織,管理和編寫模塊。
AMD 與 CMD在JavaScript模塊化編程的世界中,有兩個規范不得不提,它們分別是AMD和CMD?,F在的JS庫或框架,凡是模塊化的,一般都是遵循了這兩個規范其中之一。
AMD(Asynchronous Module Definition)CommonJS
在說AMD之前,先要提一下CommonJS。CommonJS是為了彌補JavaScript標準庫過少的缺點而產生的,由于JS沒有模塊機制(ES6引入了模塊系統,但瀏覽器全面支持估計還有好幾年),CommonJS就幫助JS實現模塊的功能?,F在很熱門的Node.js就是CommonJS規范的一個實現。
CommonJS在模塊中定義方法要借助一個全局變量exports,它用來生成當前模塊的API:
/* math module */ exports.add = function(a, b) { return a + b; };
要加載模塊就要使用CommonJS的一個全局方法require()。加載之前實現的math模塊像這樣:
var math = require("math");
加載后math變量就是這個模塊對象的一個引用,要調用模塊中的方法就像調用普通對象的方法一樣了:
var math = require("math"); math.add(1, 3);
總之,CommonJS就是一個模塊加載器,可以方便地對JavaScript代碼進行模塊化管理。但它也有缺點,它在設計之初并沒有完全為瀏覽器環境考慮,瀏覽器環境的特點是所有的資源,不考慮本地緩存的因素,都需要從服務器端加載,加載的速度取決于網絡速度,而CommonJS的模塊加載過程是同步阻塞的。也就是說如果math模塊體積很大,網速又不好的時候,整個程序便會停止,等待模塊加載完成。
隨著瀏覽器端JS資源的體積越來越龐大,阻塞給體驗帶來的不良影響也越來越嚴重,終于從,在CommonJS社區中有了不同的聲音,AMD規范誕生了。
AMD
它的特點便是異步加載,模塊的加載不會影響其他代碼的運行。所有依賴于某個模塊的代碼全部移到模塊加載語句的回調函數中去。AMD的require()語句接受兩個參數:
// require([module], callback) require(["math"], function(math) { math.add(1, 3); });
在回調函數中,可以通過math變量引用模塊。
AMD規范也規定了模塊的定義規則,使用define()函數。
define(id?, dependencies?, factory);
它接受三個參數:
id
這是一個可選參數,相當于模塊的名字,加載器可通過id名加載對應的模塊。如果沒有提供id,加載器會將模塊文件名作為默認id。
dependencies
可選,接受一個數組參數,傳入當前對象依賴的對象id。
factory
回調函數,在依賴模塊加載完成后會調用,它的參數是所有依賴模塊的引用?;卣{函數的返回值就是當前對象的導出值。
用AMD規范實現一個簡單的模塊可以這樣:
define("foo", ["math"], function(math) { return { increase: function(x) { return math.add(x, 1); } }; });
如果省去id和dependencies參數的話,就是一個完全的匿名模塊。factory的參數將為默認值require,exports和module加載器將完全通過文件路徑的方式加載模塊,同時如果有依賴模塊的話可通過require方法加載。
define(function(require, exports, module) { var math = require("math"); exports.increase = function(x) { return math(x, 1); }; });
AMD規范也允許對加載進行一些配置,配置選項不是必須的,但靈活更改配置,會給開發帶來一些方便。
baseUrl 以字符串形式規定根目錄的路徑,以后在加載模塊時都會以該路徑為標準。在瀏覽器中,工作目錄的路徑就是運行腳本的網頁所在的路徑。
{ baseUrl: "./foo/bar" }
path 可以指定需加載模塊的路徑,模塊名與路徑以鍵-值對的方式寫在對象中。如果一個模塊有多個可選地址,可以將這些地址寫在一個數組中。
{ path: { "foo": "./bar" } }
關于模塊路徑的設置項還有packages,map。
shim
對于某些沒有按照AMD規范編寫的模塊,比如jQuery,來說,要使它們能被加載器加載,需要用shim方法為其配置一些屬性。在main模塊中,用require.config()方法:
require.config({ shim: { "jquery": { exports: "$" }, "foo": { deps: [ "bar", "jquery" ], exports: "foo" } } });
之后再用加載器加載就可以了。
目前實現了AMD規范的庫有很多,比較有名的是Require.js。
CMD(Common Module Definition)CMD在很多地方和AMD有相似之處,在這里我只說兩者的不同點。
首先,CMD規范和CommonJS規范是兼容的,相比AMD,它簡單很多。遵循CMD規范的模塊,可以在Node.js中運行。
define
與AMD規范不同的是CMD規范中不使用id和deps參數,只保留factory。其中:
1.factory接收對象/字符串時,表明模塊的接口就是對象/字符串。
define({ "foo": "bar" }); define("My name is classicemi.");
define.cmd
其值為一個空對象,用于判斷頁面中是否有CMD模塊加載器。
if (typeof define === "function" && define.cmd) { // 使用CMD模塊加載器編寫代碼 }
require
此函數同樣用于獲取模塊接口。如需異步加載模塊,使用require.async方法。
define(function(require, exports, module) { require.async("math", function(math) { math.add(1, 2); }); });
我們可以發現,require(id)的寫法和CommonJS一樣是以同步方式加載模塊。要像AMD規范一樣異步加載模塊則使用define.async方法。
exports
此方法用于模塊對外提供接口。
define(function(require, exports, module) { // 對外提供foo屬性 exports.foo = "bar"; // 對外提供add方法 exports.add = function(a, b) { return a + b; } });
提供接口的另一個方法是直接return包含接口鍵值對的對象:
define(function(require, exports, module) { return { foo: "bar", add: function(a, b) { return a + b; } } });
但是注意,不能用exports輸出接口對象:
define(function(require, exports, module) { exports = { foo: "bar", add: function(a, b) { return a + b; } } });
這樣寫是錯誤的!
替代方式是這樣寫:
define(function(require, exports, module) { module.exports = { foo: "bar", add: function(a, b) { return a + b; } } });
之前錯誤的原因是在factory內部,exports實際上是module.exports的一個引用,直接給exports賦值是不會改變module.exports的值的。
在module對象上,除了有上面提到的exports以外,還有一些別的屬性和方法。
module.id
模塊的標識。
define("math", [], function(require, exports, module) { // module.id 的值為 math });
module.uri
模塊的絕對路徑,由模塊系統解析得到。
define(function(require, exports, module) { console.log(module.uri); // http://xxx.com/path/ });
module.dependencies
值為一個數組,返回本模塊的依賴。
之前在說AMD規范的時候提到了Require.js。它是AMD規范的代表性產品。另一個Sea.js在前端界也是赫赫有名了,CMD規范實際上就是它的產出。它們之間的區別也很能表現AMD和CMD規范之間的區別。
AMD的依賴需要前置書寫
define(["foo", "bar"], function(foo, bar) { foo.add(1, 2); bar.subtract(3, 4); });
CMD的依賴就近書寫即可,不需要提前聲明:
同步式:
define(function(require, exports, module) { var foo = require("foo"); foo.add(1, 2); ... var bar = require("bar"); bar.subtract(3, 4); });
異步式:
define(function(require, exports, module) { ... require.async("math", function(math) { math.add(1, 2); }); ... });
雖然AMD也可以用和CMD相似的方法,但不是官方推薦的。
之前在介紹CMD的API時,我們可以發現其API職責專一,例如同步加載和異步加載的API都分為require和require.async,而AMD的API比較多功能。
總而言之,引用玉伯的總結:
1. Require.js同時適用于瀏覽器端和服務器環境的模塊加載。Sea.js則專注于瀏覽器端的模塊加載實現。通過Node擴展也可以運行于Node環境中。
2. Require.js -> AMD,Sea.js -> CMD。
3. RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區采納。Sea.js 不強推,采用自主封裝的方式來“海納百川”,目前已有較成熟的封裝策略。
4. Sea.js的調試工具比較完備,Require.js調試比較不方便。
5. RequireJS 采取的是在源碼中預留接口的形式,插件類型比較單一。Sea.js 采取的是通用事件機制,插件類型更豐富。
怎么看都像是在自夸啊= =,當然它有這個資格
參考文獻CommonJS官網
阮一峰博客
AMD Github
CMD Github
Sea.js
Require.js
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87540.html
摘要:面向對象面向對象編程的全稱是,簡稱,面向對象編程是用抽象方式創建基于現實世界模型的一種編程模式。面向對象編程的三個主要特征是封裝繼承多態。 面向對象 面向對象編程的全稱是Object Oriented Programming,簡稱OOP,面向對象編程是用抽象方式創建基于現實世界模型的一種編程模式。面向對象編程可以看做是使用一系列對象相互協作的軟件設計,面向對象程序設計的目的是在編程中促...
摘要:前端單元測試,推薦淘寶開源的工具,簡單易用,支持眾多測試框架,也支持調試。這些也是設計前端框架時需要權衡的重要方面。最后,其實大型網站不一定要設計自己的前端框架,完全可以選用現有的框架。 有人在知乎上提問如何設計大型網站的前端 JavaScript 框架,有不少回答,其中得贊較多的兩個回答如下: 相對大型的項目在前端 JS 方面有幾個需要達成的目標: 1. 代碼邏輯分層 ...
摘要:而哈士奇區別于普通狗,又有新的特征逗比,愛搗亂為了保證類之間的松綁定,通常會繼承抽象類,而且是淺繼承只有一層子類。如果知道所有類都會共享一個公共的行為實現,就使用抽象類,并在其中實現該行為。 為什么使用OOP OOP是一個模塊化的過程,目的是為了把復雜問題簡單化,一個模塊解決一個復雜問題的某一個方面,即一個類應當只有一個職責 OOP區別于順序式編程與過程式編程,在于: 1.順序編程...
摘要:事件循環背景是一門單線程非阻塞的腳本語言,單線程意味著,代碼在執行的任何時候,都只有一個主線程來處理所有的任務。在意識到該問題之際,新特性中的可以讓成為一門多線程語言,但實際開發中使用存在著諸多限制。這個地方被稱為執行棧。 事件循環(Event Loop) 背景 JavaScript是一門單線程非阻塞的腳本語言,單線程意味著,JavaScript代碼在執行的任何時候,都只有一個主線程來...
摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。 雖然今年沒有換工作的打算 但為了跟上時代的腳步 還是忍不住整理了一份最新前端知識點 知識點匯總 1.HTML HTML5新特性,語義化瀏覽器的標準模式和怪異模式xhtml和html的區別使用data-的好處meta標簽canvasHTML廢棄的標簽IE6 bug,和一些定位寫法css js放置位置和原因...
閱讀 3920·2021-11-24 10:46
閱讀 1816·2021-11-16 11:44
閱讀 2289·2021-09-22 16:02
閱讀 1401·2019-08-30 15:55
閱讀 1131·2019-08-30 12:46
閱讀 566·2019-08-28 18:31
閱讀 2762·2019-08-26 18:38
閱讀 1094·2019-08-23 16:51