requireJS實作一個簡單的模組載入器實例分享
本文主要介紹了深入理解requireJS-實作一個簡單的模組載入器,小編覺得蠻不錯的,現在分享給大家,也給大家做個參考。一起跟著小編過來看看吧,希望能幫助大家。
在前文中我們不只一次強調模組化程式設計的重要性,以及其可以解決的問題:
① 解決單檔案變數命名衝突問題
② 解決前端多人協作問題
③ 解決文件依賴問題
④ 按需加載(這個說法其實很假了)
⑤ ......
為了深入了解載入器,中間閱讀過一點requireJS的源碼,但對於很多同學來說,對加載器的實現依舊不太清楚
事實上不通過代碼實現,單單憑閱讀想理解一個函式庫或框架只能達到一知半解的地步,所以今天便來實作一個簡單的載入器
載入器原理分析
分與合
事實上,一個程式運行需要完整的模組,以下程式碼為例:
//求得绩效系数 var performanceCoefficient = function () { return 0.2; }; //住房公积金计算方式 var companyReserve = function (salary) { return salary * 0.2; }; //个人所得税 var incomeTax = function (salary) { return salary * 0.2; }; //基本工资 var salary = 1000; //最终工资 var mySalary = salary + salary * performanceCoefficient(); mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); console.log(mySalary);
我一份完整的工資來說,公司會有績效獎勵,但是其演算法可能非常複雜,其中可能涉及到出勤率,完成度什麼的,這裡暫時不管
而有增便有減,所以我們會交住房公積金,也會扣除個人所得稅,最終才是我的工資
對於完整的程序來說上面的流程缺一不可,但是各個函數中卻有可能異常的複雜,跟錢有關係的東西都複雜,所以單單是公司績效便有可能超過1000行程式碼
於是我們這邊便會開始分:
<script src="companyReserve.js" type="text/javascript"></script> <script src="incomeTax.js" type="text/javascript"></script> <script src="performanceCoefficient.js" type="text/javascript"></script> <script type="text/javascript"> //基本工资 var salary = 1000; //最终工资 var mySalary = salary + salary * performanceCoefficient(); mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); console.log(mySalary); </script>
上面的程式碼表明上是「分」開了,事實上也造成了「合」的問題,我要如何才能很好的把它們重新合到一起呢,畢竟其中的文件可能還涉及到依賴,這裡便進入我們的require與define
require與define
事實上,上面的方案仍然是以文件劃分,而不是以模組劃分的,若是檔名發生變化,頁面會涉及到改變,其實這裡應該有一個路徑的映射處理這個問題
var pathCfg = { 'companyReserve': 'companyReserve', 'incomeTax': 'incomeTax', 'performanceCoefficient': 'performanceCoefficient' };
於是我們一個模組便對應了一個路徑js文件,剩下的便是將之對應模組的載入了,因為前端模組涉及到請求。所以這種寫法:
companyReserve = requile('companyReserve');
對於前端來說是不適用的,就算你在哪裡看到這樣做了,也一定是其中做了一些“手腳” ,這裡我們便需要依據AMD規範了:
require.config({ 'companyReserve': 'companyReserve', 'incomeTax': 'incomeTax', 'performanceCoefficient': 'performanceCoefficient' }); require(['companyReserve', 'incomeTax', 'performanceCoefficient'], function (companyReserve, incomeTax, performanceCoefficient) { //基本工资 var salary = 1000; //最终工资 var mySalary = salary + salary * performanceCoefficient(); mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); console.log(mySalary); });
這裡便是一個標準的requireJS的寫法了,首先定義模組以及其路徑映射,其中定義依賴項
require(depArr, callback)
一個簡單完整的模組載入器基本上就是這個樣子了,首先是一個依賴的數組,其次是一個回調,回調要求依賴項全部載入才能運行,並且回調的參數便是依賴項執行的結果,所以一般要求define模組有一個回傳值
方案有了,那麼要如何實現呢?
實作方案
說到模組加載,人們第一反應都是ajax,因為無論何時,能拿到模組檔的內容,都是模組化的基本,但是採用ajax的方式是不行的,因為ajax有跨域的問題
而模組化方案又不可避免的要處理跨域的問題,所以使用動態創建script標籤加載js文件便成為了首選,但是,不使用ajax的方案,對於實現難度來說還是有要求
PS:我們實際工作中還會有加載html模板文件的場景,這個稍候再說
通常我們是這樣做的,require作為程式入口,調度javascript資源,而載入到各個define模組後,各個模組便悄無聲息的創建script標籤加載
加載結束後便往require模組佇列報告自己載入結束了,當require中多有依賴模組皆載入結束時,便執行其回呼
#原理大致如此,剩下的只是具體實現,而後在論證這個理論是否可靠即可
載入器閹割實作
核心模組
根據上述理論,我們由整體來說,先以入口三個基本函數來說
var require = function () { }; require.config = function () { }; require.define = function () { };
這三個模組比不可少:
① config用以配置模組與路徑的映射,或者還有其他用途
② require為程式入口
③ define設計各個模組,回應require的調度
然後我們這裡會有一個創建script標籤的方法,並且會監聽其onLoad事件
④ loadScript
其次我們載入script標籤後,應該有一個全域的模組對象,用來儲存已經載入好的模組,所以這裡提出了兩個需求:
⑤ require.moduleObj 模組儲存物件
⑥ Module,模組的建構子
有了以上核心模組,我們形成如下程式碼:
(function () { var Module = function () { this.status = 'loading'; //只具有loading与loaded两个状态 this.depCount = 0; //模块依赖项 this.value = null; //define函数回调执行的返回 }; var loadScript = function (url, callback) { }; var config = function () { }; var require = function (deps, callback) { }; require.config = function (cfg) { }; var define = function (deps, callback) { }; })();
于是接下来便是具体实现,然后在实现过程中补足不具备的接口与细节,往往在最后的实现与最初的设计没有半毛钱关系......
代码实现
这块最初实现时,本来想直接参考requireJS的实现,但是我们老大笑眯眯的拿出了一个他写的加载器,我一看不得不承认有点妖
于是这里便借鉴了其实现,做了简单改造:
(function () { //存储已经加载好的模块 var moduleCache = {}; var require = function (deps, callback) { var params = []; var depCount = 0; var i, len, isEmpty = false, modName; //获取当前正在执行的js代码段,这个在onLoad事件之前执行 modName = document.currentScript && document.currentScript.id || 'REQUIRE_MAIN'; //简单实现,这里未做参数检查,只考虑数组的情况 if (deps.length) { for (i = 0, len = deps.length; i < len; i++) { (function (i) { //依赖加一 depCount++; //这块回调很关键 loadMod(deps[i], function (param) { params[i] = param; depCount--; if (depCount == 0) { saveModule(modName, params, callback); } }); })(i); } } else { isEmpty = true; } if (isEmpty) { setTimeout(function () { saveModule(modName, null, callback); }, 0); } }; //考虑最简单逻辑即可 var _getPathUrl = function (modName) { var url = modName; //不严谨 if (url.indexOf('.js') == -1) url = url + '.js'; return url; }; //模块加载 var loadMod = function (modName, callback) { var url = _getPathUrl(modName), fs, mod; //如果该模块已经被加载 if (moduleCache[modName]) { mod = moduleCache[modName]; if (mod.status == 'loaded') { setTimeout(callback(this.params), 0); } else { //如果未到加载状态直接往onLoad插入值,在依赖项加载好后会解除依赖 mod.onload.push(callback); } } else { /* 这里重点说一下Module对象 status代表模块状态 onLoad事实上对应requireJS的事件回调,该模块被引用多少次变化执行多少次回调,通知被依赖项解除依赖 */ mod = moduleCache[modName] = { modName: modName, status: 'loading', export: null, onload: [callback] }; _script = document.createElement('script'); _script.id = modName; _script.type = 'text/javascript'; _script.charset = 'utf-8'; _script.async = true; _script.src = url; //这段代码在这个场景中意义不大,注释了 // _script.onload = function (e) {}; fs = document.getElementsByTagName('script')[0]; fs.parentNode.insertBefore(_script, fs); } }; var saveModule = function (modName, params, callback) { var mod, fn; if (moduleCache.hasOwnProperty(modName)) { mod = moduleCache[modName]; mod.status = 'loaded'; //输出项 mod.export = callback ? callback(params) : null; //解除父类依赖,这里事实上使用事件监听较好 while (fn = mod.onload.shift()) { fn(mod.export); } } else { callback && callback.apply(window, params); } }; window.require = require; window.define = require; })();
首先这段代码有一些问题:
没有处理参数问题,字符串之类皆未处理
未处理循环依赖问题
未处理CMD写法
未处理html模板加载相关
未处理参数配置,baseUrl什么都没有搞
基于此想实现打包文件也不可能
......
但就是这100行代码,便是加载器的核心,代码很短,对各位理解加载器很有帮助,里面有两点需要注意:
① requireJS是使用事件监听处理本身依赖,这里直接将之放到了onLoad数组中了
② 这里有一个很有意思的东西
document.currentScript
这个可以获取当前执行的代码段
requireJS是在onLoad中处理各个模块的,这里就用了一个不一样的实现,每个js文件加载后,都会执行require(define)方法
执行后便取到当前正在执行的文件,并且取到文件名加载之,正因为如此,连script的onLoad事件都省了......
demo实现
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> </body> <script src="require.js" type="text/javascript"></script> <script type="text/javascript"> require(['util', 'math', 'num'], function (util, math, num) { num = math.getRadom() + '_' + num; num = util.formatNum(num); console.log(num); }); </script> </html>
//util define([], function () { return { formatNum: function (n) { if (n < 10) return '0' + n; return n; } }; });
//math define(['num'], function (num) { return { getRadom: function () { return parseInt(Math.random() * num); } }; });
//math define(['num'], function (num) { return { getRadom: function () { return parseInt(Math.random() * num); } }; });
小结
今天我们实现了一个简单的模块加载器,通过他希望可以帮助各位了解requireJS或者seaJS,最后顺利进入模块化编程的行列。
相关推荐:
以上是requireJS實作一個簡單的模組載入器實例分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)
![WLAN擴充模組已停止[修復]](https://img.php.cn/upload/article/000/465/014/170832352052603.gif?x-oss-process=image/resize,m_fill,h_207,w_330)
如果您的Windows電腦上的WLAN擴充模組出現問題,可能會導致您與網際網路中斷連線。這種情況常常讓人感到困擾,但幸運的是,本文提供了一些簡單的建議,可以幫助您解決這個問題,讓您的無線連線重新正常運作。修復WLAN擴充模組已停止如果您的Windows電腦上的WLAN可擴充性模組已停止運作,請依照下列建議進行修復:執行網路和Internet故障排除程式停用並重新啟用無線網路連線重新啟動WLAN自動設定服務修改電源選項修改高級電源設定重新安裝網路適配器驅動程式運行一些網路命令現在,讓我們來詳細看

本文詳細介紹了解決事件ID10000的方法,該事件表明無線區域網路擴充模組無法啟動。在Windows11/10PC的事件日誌中可能會顯示此錯誤。 WLAN可擴充性模組是Windows的一個元件,允許獨立硬體供應商(IHV)和獨立軟體供應商(ISV)為使用者提供客製化的無線網路特性和功能。它透過增加Windows預設功能來擴充本機Windows網路元件的功能。在作業系統載入網路元件時,WLAN可擴充性模組會作為初始化的一部分啟動。如果無線區域網路擴充模組遇到問題無法啟動,您可能會在事件檢視器的日誌中看到錯誤消

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest
