本文主要介紹了深入理解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中文網其他相關文章!