您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何JavaScript項目中實現一個模塊化功能,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
一、模塊化需要解決的問題
要對一個東西進行深入的剖析,有必要帶著目的去看。模塊化所要解決的問題可以用一句話概括
在沒有全局污染的情況下,更好的組織項目代碼
舉一個簡單的栗子,我們現在有如下的代碼:
function doSomething () { const a = 10; const b = 11; const add = function (a + b) { return a + b } add (a + b) }
在現實的應用場景中,doSomething 可能需要做很多很多的事情,add 函數可能也更為復雜,并且可以復用,那么我們希望可以將 add 函數獨立到一個單獨的文件中,于是:
// doSomething.js 文件 const add = require('add.js'); const a = 10; const b = 11; add(a+ b);
// add.js 文件 function add (a, b) { return a + b; } module.exports = add;
這樣做的目的顯而易見,更好的組織項目代碼,注意到兩個文件中的 require 和 module.exports,從現在的上帝視角來看,這出自 CommonJS 規范(后文會有一個章節來專門講規范)中的關鍵字,分別代表導入和導出,拋開規范而言,這其實是我們模塊化之路上需要解決的問題。另外,雖然 add 模塊需要得到復用,但是我們并不希望在引入 add 的時候造成全局污染
二、引入的模塊如何運行
在上述的例子中,我們已經將代碼拆分到了兩個模塊文件當中,在不造成全局污染的情況下,如何實現 require,才能使得例子中的代碼做到正常運行呢?
先不考慮模塊文件代碼的載入過程,假設 require 已經可以從模塊文件中讀取到代碼字符串,那么 require 可以這樣實現
function require (path) { // lode 方法讀取 path 對應的文件模塊的代碼字符串 // let code = load(path); // 不考慮 load 的過程,直接獲得模塊 add 代碼字符串 let code = 'function add(a, b) {return a+b}; module.exports = add'; // 封裝成閉包 code = `(function(module) {$[code]})(context)` // 相當于 exports,用于導出對象 let context = {}; // 運行代碼,使得結果影響到 context const run = new Function('context', code); run(context, code); //返回導出的結果 return context.exports; }
這有幾個要點:
1) 為了不造成全局污染,需要將代碼字符串封裝成閉包的形式,并且導出關鍵字 module.exports ,module 是與外界聯系的唯一載體,需要作為閉包匿名函數的入參,與引用方傳入的上下文 context 進行關聯
2) 使用 new Function 來執行代碼字符串,估計大部分同學對 new Function 是不熟悉的,因為一般情況下定義一個函數無需如此,要知道,用 Function 類可以直接創建函數,語法如下:
var function_name = new function(arg1, arg2, ..., argN, function_body)
在上面的形式中,每個 arg 都是一個參數,最后一個參數是函數主體(要執行的代碼)。這些參數必須是字符串。也就是說,可以使用它來執行字符串代碼,類似于 eval,并且相比 eval, 還可以通過參數的形式傳入字符串代碼中的某些變量的值
3)如果曾經你有疑惑過為什么規范的導出關鍵字只有 exports 而我們實際使用過程中卻要使用module.exports(寫過 Node 代碼的應該不會陌生),那在這段代碼中就可以找到答案了,如果只用 exports 來接收 context,那么對 exports 的重新賦值對 context 不會有任何影響(參數的地址傳遞),不信將代碼改成如下形式再跑一跑:
演示結果
三、代碼載入方式
解決了代碼的運行問題,還需要解決模塊文件代碼的載入問題,根據上述實例,我們的目標是將模塊文件代碼以字符串的形式載入
在 Node 容器,所有的模塊文件都在本地,只需要從本地磁盤讀取模塊文件載入字符串代碼,再走上述的流程就可以了。事實證明,Node 非內建、核心、c++ 模塊的載入執行方式大體如此(雖然使用的不是 new Function,但也是一個類似的方法)
在 RN/Weex 容器,要載入一個遠程 bundle.js,可以通過 Native 的能力請求一個遠程的 js 文件,再讀取成字符串代碼載入即可(按照這個邏輯,Node 讀取一個遠程的 js 模塊好像也無不可,雖然大多數情況下我們不需要這么做)
在瀏覽器環境,所有的 Js 模塊都需要遠程讀取,尷尬的是,受限于瀏覽器提供的能力,并不能通過 ajax 以文件流的形式將遠程的 js 文件直接讀取為字符串代碼。前提條件無法達成,上述運行策略便行不通,只能另辟蹊徑
這就是為什么有了 CommonJs 規范了,為什么還會出現 AMD/CMD 規范的原因
那么瀏覽器上是怎么做的呢?在瀏覽器中通過 Js 控制動態的載入一個遠程的 Js 模塊文件,需要動態的插入一個 <script> 節點:
// 摘抄自 require.js 的一段代碼 var node = config.xhtml ? document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : document.createElement('script'); node.type = config.scriptType || 'text/javascript'; node.charset = 'utf-8'; node.async = true; node.setAttribute('data-requirecontext', context.contextName); node.setAttribute('data-requiremodule', moduleName); node.addEventListener('load', context.onScriptLoad, false); node.addEventListener('error', context.onScriptError, false);
要知道,設置了 <script> 標簽的 src 之后,代碼一旦下載完成,就會立即執行,根本由不得你再封裝成閉包,所以文件模塊需要在定義之初就要做文章,這就是我們說熟知的 AMD/CMD 規范中的 define,開篇的 add.js 需要重新改寫一下
// add.js 文件 define ('add',function () { function add (a, b) { return a + b; } return add; })
而對于 define 的實現,最重要的就是將 callback 的執行結果注冊到 context 的一個模塊數組中:
context.modules = {} function define(name, callback) { context.modules[name] = callback && callback() }
于是 require 就可以從 context.modules 中根據模塊名載入模塊了,是不是有了一種自己去寫一個 “requirejs” 的沖動感
具體的 AMD 實現當然還會復雜很多,還需要控制模塊載入時序、模塊依賴等等,但是了解了這其中的靈魂,想必去精讀 require.js 的源碼也不是一件困難的事情
四、Webpack 中的模塊化
Webpack 也可以配置異步模塊,當配置為異步模塊的時候,在瀏覽器環境同樣的是基于動態插入 <script> 的方式載入遠程模塊。在大多數情況下,模塊的載入方式都是類似于 Node 的本地磁盤同步載入的方式
嫑忘記,Webpack 除了有模塊化的能力,還是一個在輔助完善開發工作流的工具,也就是說,Webpack 的模塊化是在開發階段的完成的,使用 Webpack 構筑的工作環境,在開發階段雖然是獨立的模塊文件,但是在運行時,卻是一個合并好的文件
所以 Webpack 是一種在非運行時的模塊化方案(基于 CommonJs),只有在配置了異步模塊的時候對異步模塊的加載才是運行時的(基于 AMD)
五、模塊化規范
通用的問題在解決的過程中總會形成規范,上文已經多次提到 CommonJs、AMD、CMD,有必要花點篇幅來講一講規范
Js 的模塊化規范的萌發于將 Js 擴展到后端的想法,要使得 Js 具備類似于 Python、Ruby 和 Java 那樣具備開發大型應用的基礎能力,模塊化規范是必不可少的。CommonJS 規范的提出,為Js 制定了一個美好愿景,希望 Js 能在任何地方運行,包括但不限于:
服務器端 Js 應用
命令行工具
桌面應用
混合應用
CommonJS 對模塊的定義并不復雜,主要分為模塊引用、模塊定義和模塊標識
模塊引用:使用 require 方法來引入一個模塊
模塊定義:使用 exports 導出模塊對象
模塊標識:給 require 方法傳入的參數,小駝峰命名的字符串、相對路徑或者絕對路徑
模塊示意
CommonJs 規范在 Node 中大放異彩并且相互促進,但是在瀏覽器端,鑒于網絡的原因,同步的方式加載模塊顯然不太實用,在經過一段爭執之后,AMD 規范最終在前端場景中勝出(全稱 Asynchronous Module Definition,即“異步模塊定義”)
什么是 AMD,為什么需要 AMD ?在前述模塊化實現的推演過程中,你應該能夠找到答案
除此之外還有國內玉伯提出的 CMD 規范,AMD 和 CMD 的差異主要是,前者需要在定義之初聲明所有的依賴,后者可以在任意時機動態引入模塊。CMD 更接近于 CommonJS
兩種規范都需要從遠程網絡中載入模塊,不同之處在于,前者是預加載,后者是延遲加載
關于如何JavaScript項目中實現一個模塊化功能就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。