本篇文章主要介紹了深入理解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); } }; });
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
详细介绍在Vue2.0中v-for迭代语法的变化(详细教程)
以上是requireJS如何實作模組載入器?的詳細內容。更多資訊請關注PHP中文網其他相關文章!