溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

深入理解requireJS-實(shí)現(xiàn)一個(gè)簡單的模塊加載器

發(fā)布時(shí)間:2020-09-11 18:13:29 來源:腳本之家 閱讀:104 作者:葉小釵 欄目:web開發(fā)

在前文中我們不止一次強(qiáng)調(diào)過模塊化編程的重要性,以及其可以解決的問題:

① 解決單文件變量命名沖突問題

② 解決前端多人協(xié)作問題

③ 解決文件依賴問題

④ 按需加載(這個(gè)說法其實(shí)很假了)

⑤ ......

為了深入了解加載器,中間閱讀過一點(diǎn)requireJS的源碼,但對(duì)于很多同學(xué)來說,對(duì)加載器的實(shí)現(xiàn)依舊不太清楚

事實(shí)上不通過代碼實(shí)現(xiàn),單單憑閱讀想理解一個(gè)庫或者框架只能達(dá)到一知半解的地步,所以今天便來實(shí)現(xiàn)一個(gè)簡單的加載器

加載器原理分析

分與合

事實(shí)上,一個(gè)程序運(yùn)行需要完整的模塊,以下代碼為例:

//求得績效系數(shù)
 var performanceCoefficient = function () {
  return 0.2;
 };

 //住房公積金計(jì)算方式
 var companyReserve = function (salary) {
  return salary * 0.2;
 };

 //個(gè)人所得稅
 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);

我一份完整的工資來說,公司會(huì)有績效獎(jiǎng)勵(lì),但是其算法可能非常復(fù)雜,其中可能涉及到出勤率,完成度什么的,這里暫時(shí)不管

而有增便有減,所以我們會(huì)交住房公積金,也會(huì)扣除個(gè)人所得稅,最終才是我的工資

對(duì)于完整的程序來說上面的流程缺一不可,但是各個(gè)函數(shù)中卻有可能異常的復(fù)雜,跟錢有關(guān)系的東西都復(fù)雜,所以單單是公司績效便有可能超過1000行代碼

于是我們這邊便會(huì)開始分:

深入理解requireJS-實(shí)現(xiàn)一個(gè)簡單的模塊加載器

<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>

上面的代碼表明上是“分”開了,事實(shí)上也造成了“合”的問題,我要如何才能很好的把它們重新合到一起呢,畢竟其中的文件可能還涉及到依賴,這里便進(jìn)入我們的require與define

require與define

事實(shí)上,上面的方案仍然是以文件劃分,而不是以模塊劃分的,若是文件名發(fā)生變化,頁面會(huì)涉及到改變,其實(shí)這里應(yīng)該有一個(gè)路徑的映射處理這個(gè)問題

var pathCfg = {
 'companyReserve': 'companyReserve',
 'incomeTax': 'incomeTax',
 'performanceCoefficient': 'performanceCoefficient'
};

于是我們一個(gè)模塊便對(duì)應(yīng)了一個(gè)路徑j(luò)s文件,剩下的便是將之對(duì)應(yīng)模塊的加載了,因?yàn)榍岸四K涉及到請(qǐng)求。所以這種寫法:

companyReserve = requile('companyReserve');

對(duì)于前端來說是不適用的,就算你在哪里看到這樣做了,也一定是其中做了一些“手腳”,這里我們便需要依據(jù)AMD規(guī)范了:

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);
});

這里便是一個(gè)標(biāo)準(zhǔn)的requireJS的寫法了,首先定義模塊以及其路徑映射,其中定義依賴項(xiàng)

require(depArr, callback)

一個(gè)簡單完整的模塊加載器基本就是這個(gè)樣子了,首先是一個(gè)依賴的數(shù)組,其次是一個(gè)回調(diào),回調(diào)要求依賴項(xiàng)全部加載才能運(yùn)行,并且回調(diào)的參數(shù)便是依賴項(xiàng)執(zhí)行的結(jié)果,所以一般要求define模塊具有一個(gè)返回值

方案有了,那么如何實(shí)現(xiàn)呢?

實(shí)現(xiàn)方案

說到模塊加載,人們第一反應(yīng)都是ajax,因?yàn)闊o論何時(shí),能拿到模塊文件的內(nèi)容,都是模塊化的基本,但是采用ajax的方式是不行的,因?yàn)閍jax有跨域的問題

而模塊化方案又不可避免的要處理跨域的問題,所以使用動(dòng)態(tài)創(chuàng)建script標(biāo)簽加載js文件便成為了首選,但是,不使用ajax的方案,對(duì)于實(shí)現(xiàn)難度來說還是有要求

PS:我們實(shí)際工作中還會(huì)有加載html模板文件的場景,這個(gè)稍候再說

通常我們是這樣做的,require作為程序入口,調(diào)度javascript資源,而加載到各個(gè)define模塊后,各個(gè)模塊便悄無聲息的創(chuàng)建script標(biāo)簽加載

加載結(jié)束后便往require模塊隊(duì)列報(bào)告自己加載結(jié)束了,當(dāng)require中多有依賴模塊皆加載結(jié)束時(shí),便執(zhí)行其回調(diào)

原理大致如此,剩下的只是具體實(shí)現(xiàn),而后在論證這個(gè)理論是否靠譜即可

加載器閹割實(shí)現(xiàn)

核心模塊

根據(jù)以上理論,我們由整體來說,首先以入口三個(gè)基本函數(shù)來說

var require = function () {
};
require.config = function () {
};
require.define = function () {
};

這三個(gè)模塊比不可少:

① config用以配置模塊與路徑的映射,或者還有其他用處

② require為程序入口

③ define設(shè)計(jì)各個(gè)模塊,響應(yīng)require的調(diào)度

然后我們這里會(huì)有一個(gè)創(chuàng)建script標(biāo)簽的方法,并且會(huì)監(jiān)聽其onLoad事件

④ loadScript

其次我們加載script標(biāo)簽后,應(yīng)該有一個(gè)全局的模塊對(duì)象,用于存儲(chǔ)已經(jīng)加載好的模塊,于是這里提出了兩個(gè)需求:

⑤ require.moduleObj 模塊存儲(chǔ)對(duì)象

⑥ Module,模塊的構(gòu)造函數(shù)

有了以上核心模塊,我們形成了如下代碼:

(function () {

 var Module = function () {
  this.status = 'loading'; //只具有l(wèi)oading與loaded兩個(gè)狀態(tài)
  this.depCount = 0; //模塊依賴項(xiàng)
  this.value = null; //define函數(shù)回調(diào)執(zhí)行的返回
 };


 var loadScript = function (url, callback) {

 };

 var config = function () {

 };

 var require = function (deps, callback) {

 };

 require.config = function (cfg) {

 };

 var define = function (deps, callback) {

 };

})();

于是接下來便是具體實(shí)現(xiàn),然后在實(shí)現(xiàn)過程中補(bǔ)足不具備的接口與細(xì)節(jié),往往在最后的實(shí)現(xiàn)與最初的設(shè)計(jì)沒有半毛錢關(guān)系......

代碼實(shí)現(xiàn)

這塊最初實(shí)現(xiàn)時(shí),本來想直接參考requireJS的實(shí)現(xiàn),但是我們老大笑瞇瞇的拿出了一個(gè)他寫的加載器,我一看不得不承認(rèn)有點(diǎn)妖

于是這里便借鑒了其實(shí)現(xiàn),做了簡單改造:

(function () {

 //存儲(chǔ)已經(jīng)加載好的模塊
 var moduleCache = {};

 var require = function (deps, callback) {
  var params = [];
  var depCount = 0;
  var i, len, isEmpty = false, modName;

  //獲取當(dāng)前正在執(zhí)行的js代碼段,這個(gè)在onLoad事件之前執(zhí)行
  modName = document.currentScript && document.currentScript.id || 'REQUIRE_MAIN';

  //簡單實(shí)現(xiàn),這里未做參數(shù)檢查,只考慮數(shù)組的情況
  if (deps.length) {
   for (i = 0, len = deps.length; i < len; i++) {
    (function (i) {
     //依賴加一
     depCount++;
     //這塊回調(diào)很關(guān)鍵
     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;
  //不嚴(yán)謹(jǐn)
  if (url.indexOf('.js') == -1) url = url + '.js';
  return url;
 };

 //模塊加載
 var loadMod = function (modName, callback) {
  var url = _getPathUrl(modName), fs, mod;

  //如果該模塊已經(jīng)被加載
  if (moduleCache[modName]) {
   mod = moduleCache[modName];
   if (mod.status == 'loaded') {
    setTimeout(callback(this.params), 0);
   } else {
    //如果未到加載狀態(tài)直接往onLoad插入值,在依賴項(xiàng)加載好后會(huì)解除依賴
    mod.onload.push(callback);
   }
  } else {

   /*
   這里重點(diǎn)說一下Module對(duì)象
   status代表模塊狀態(tài)
   onLoad事實(shí)上對(duì)應(yīng)requireJS的事件回調(diào),該模塊被引用多少次變化執(zhí)行多少次回調(diào),通知被依賴項(xiàng)解除依賴
   */
   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;

   //這段代碼在這個(gè)場景中意義不大,注釋了
   //   _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';
   //輸出項(xiàng)
   mod.export = callback ? callback(params) : null;

   //解除父類依賴,這里事實(shí)上使用事件監(jiān)聽較好
   while (fn = mod.onload.shift()) {
    fn(mod.export);
   }
  } else {
   callback && callback.apply(window, params);
  }
 };

 window.require = require;
 window.define = require;

})();

首先這段代碼有一些問題:

沒有處理參數(shù)問題,字符串之類皆未處理

未處理循環(huán)依賴問題

未處理CMD寫法

未處理html模板加載相關(guān)

未處理參數(shù)配置,baseUrl什么都沒有搞

基于此想實(shí)現(xiàn)打包文件也不可能

......

但就是這100行代碼,便是加載器的核心,代碼很短,對(duì)各位理解加載器很有幫助,里面有兩點(diǎn)需要注意:

① requireJS是使用事件監(jiān)聽處理本身依賴,這里直接將之放到了onLoad數(shù)組中了

② 這里有一個(gè)很有意思的東西

document.currentScript

這個(gè)可以獲取當(dāng)前執(zhí)行的代碼段

requireJS是在onLoad中處理各個(gè)模塊的,這里就用了一個(gè)不一樣的實(shí)現(xiàn),每個(gè)js文件加載后,都會(huì)執(zhí)行require(define)方法

執(zhí)行后便取到當(dāng)前正在執(zhí)行的文件,并且取到文件名加載之,正因?yàn)槿绱?,連script的onLoad事件都省了......

demo實(shí)現(xiàn)

<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);
  }
 };
});

小結(jié)

今天我們實(shí)現(xiàn)了一個(gè)簡單的模塊加載器,通過他希望可以幫助各位了解requireJS或者seaJS,最后順利進(jìn)入模塊化編程的行列

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI