欢迎光临
我们一直在努力

理解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的实现,但是我们老大笑眯眯的拿出了一个他写的加载器,我一看不得不承认有点妖

于是这里便借鉴了其实现,做了简单改造:

View Code

首先这段代码有一些问题:

没有处理参数问题,字符串之类皆未处理

未处理循环依赖问题

未处理CMD写法

未处理html模板加载相关

未处理参数配置,baseUrl什么都没有搞

基于此想实现打包文件也不可能

……

但就是这100行代码,便是加载器的核心,代码很短,对各位理解加载器很有帮助,里面有两点需要注意:

① requireJS是使用事件监听处理本身依赖,这里直接将之放到了onLoad数组中了

② 这里有一个很有意思的东西

document.currentScript

这个可以获取当前执行的代码段

requireJS是在onLoad中处理各个模块的,这里就用了一个不一样的实现,每个js文件加载后,都会执行require(define)方法

执行后便取到当前正在执行的文件,并且取到文件名加载之,正因为如此,连script的onLoad事件都省了……

demo实现

<!DOCTYPE html>

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

        }

    };

});


//num

define([], function () {

    return 10;

});

小结

今天我们实现了一个简单的模块加载器,通过他希望可以帮助各位了解requireJS或者seaJS,最后顺利进入模块化编程的行列

赞(0)
版权归原作者所有,如有侵权请告知。达维营-前端网 » 理解requireJS-实现一个简单的模块加载器

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址