最近在做新项目的时候自己利用一点业余时间写了一个简单的js模块加载器。后来因为用了webpack就没有考虑把它放到项目里面去,也没有继续更新它了。模块加载器开源的有很多,一般来说seaJS和reqiureJS都能满足基本需求。本篇博文主要分享一下卤煮写这个加载器的一些想法和思路,作为学习的记录。
js模块化加载已经不是一个新鲜概念了,很多人都一再强调,大型项目要使用模块化开发,因为一旦随着项目的增大,管理和组织代码的难度会越来越难,使得我们对代码的管理变得重要起来。当然,在后端模块化已经相当成熟,而作为前端的模块化概念,是很久之后才提出来的。模块化好处是使得代码结构更加清晰,高的内聚,功能独立,复用等等。在服务端,随着nodejs 的兴起,js模块化被越来越多地引起人们的注意。但是对于后端和前端来说,最大的区别就是同步和异步加载的问题,因为服务器上获取模块是不需要花费很多的,模块加载进来的时间就操作系统文件的时间,这个过程可以看成是同步的。而在浏览器的前端却需要发送请求到服务器来获取文件,这导致了一个异步延迟的问题,针对这个问题,以AMD规范的异步模块加载器requireJS应运而生。
加载原理
以上简单介绍了一下前端模块化的历程,下面主要介绍一下模块加载主要原理:
1. createElement(‘script’)和appendChild(script) 动态创建脚本,添加到head元素中。
2. fn.toString().match(/.require((“|’)[^)]*(“|’))/g) 将模块转换为字符串,然后通过正则表达式,匹配每个模块中的的依赖文件。
3. 建立脚本加载队列。
4.递归加载,分析完依赖之后,我们需要按照依赖出现的位置,将它们加载到客户端。
5.为每一个命名的模块建立缓存,即 module[name] = callback;
6.currentScript : 对于匿名模块,通过currentScript 来获取文件名,存入到缓存中。
下面贴出对应主要的代码:
一、动态创建脚本
创建脚本较为简单,主要是用createElement方法和appendChild。在创建脚本函数中,我们需要为该脚本绑定一个onload事件,这个事件是为了通知加载脚本队列执行的时间,告诉它什么时候可以加载下一个js文件了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function _createScript(url) { //创建script var script = doc.createElement('script'); var me = this; //设置属性为异步加载 script.async = true; script.src = url + '.js'; //为脚本添加加载完成事件 if ('onload' in script) { script.onload = function(event) { return _scriptLoaded.call(me, script); }; } else { script.onreadystatechange = function() { if (/loaded|complete/.test(node.readyState)) { me.next(); _scriptLoaded(script); } }; } //加入script head.appendChild(script); } |
二、分析依赖建立
分析依赖是模块加载器中最重要的环节之一。每个模块可能会依赖不同的模块,我们需要理清楚这些模块之间的依赖关系,然后分别将它们加载进来。为了分析依赖关系,我们使用toString的方法,将模块转化为一个string,然后去其中寻找依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
function _analyseDepend(func) { //匹配依赖,所有在.reqiure()括号内的依赖都会被匹配出来。 var firstReg = /.require(("|')[^)]*("|'))/g, secondReg = /(("|')[^)]*("|'))/g, lastReplaceRge = /(("|')|("|'))/g; //将模块字符串化 var string = func.toString(); var allFiles = string.match(firstReg); var newArr = []; if (!allFiles) { return ''; } //将依赖的文件名存入一个堆栈内 allFiles.map(function(v) { //对文件名做处理 var m = v.match(secondReg)[0].replace(lastReplaceRge, ''); //只有在异步加载的情况下需要 返回解析依赖 if(!modules[_analyseName(m)]) { newArr.push(m); } }); if(newArr.length > 0) { return newArr; }else{ return '' } } |
三、建立脚本加载队列
分析完依赖之后,我们可以得到一个脚本名称的栈,我们从其中获取脚本名称,依次按照顺序地加载它们。因为每个脚本加载过程都是异步的,所以,我们需要有一个异步加载机制。在这里,我们使用了设计模式中的职责链条模式来完成整个异步加载过程。通过在onload事件通知队列加载的完成情况。下面是职责链模式的实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ayon-striped-num" data-line="crayon-5812a00891858007207365-22">22 23 24 25 26 ߙ个加载器的一些想法和思路,作为学习的记录。
js模块化加载已经不是一个新鲜概念了,很多人都一再强调,大型项目要使用模块化开发,因为一旦随着项目的增大,管理和组织代码的难度会越来越难,使得我们对代码的管理变得重要起来。当然,在后端模块化已经相当成熟,而作为前端的模块化概念,是很久之后才提出来的。模块化好处是使得代码结构更加清晰,高的内聚,功能独立,复用等等。在服务端,随着nodejs 的兴起,js模块化被越来越多地引起人们的注意。但是对于后端和前端来说,最大的区别就是同步和异步加载的问题,因为服务器上获取模块是不需要花费很多的,模块加载进来的时间就操作系统文件的时间,这个过程可以看成是同步的。而在浏览器的前端却需要发送请求到服务器来获取文件,这导致了一个异步延迟的问题,针对这个问题,以AMD规范的异步模块加载器requireJS应运而生。 加载原理以上简单介绍了一下前端模块化的历程,下面主要介绍一下模块加载主要原理: 1. createElement(‘script’)和appendChild(script) 动态创建脚本,添加到head元素中。 2. fn.toString().match(/.require((“|’)[^)]*(“|’))/g) 将模块转换为字符串,然后通过正则表达式,匹配每个模块中的的依赖文件。 3. 建立脚本加载队列。 4.递归加载,分析完依赖之后,我们需要按照依赖出现的位置,将它们加载到客户端。 5.为每一个命名的模块建立缓存,即 module[name] = callback; 6.currentScript : 对于匿名模块,通过currentScript 来获取文件名,存入到缓存中。 下面贴出对应主要的代码: 一、动态创建脚本创建脚本较为简单,主要是用createElement方法和appendChild。在创建脚本函数中,我们需要为该脚本绑定一个onload事件,这个事件是为了通知加载脚本队列执行的时间,告诉它什么时候可以加载下一个js文件了。
二、分析依赖建立分析依赖是模块加载器中最重要的环节之一。每个模块可能会依赖不同的模块,我们需要理清楚这些模块之间的依赖关系,然后分别将它们加载进来。为了分析依赖关系,我们使用toString的方法,将模块转化为一个string,然后去其中寻找依赖。
三、建立脚本加载队列分析完依赖之后,我们可以得到一个脚本名称的栈,我们从其中获取脚本名称,依次按照顺序地加载它们。因为每个脚本加载过程都是异步的,所以,我们需要有一个异步加载机制。在这里,我们使用了设计模式中的职责链条模式来完成整个异步加载过程。通过在onload事件通知队列加载的完成情况。下面是职责链模式的实现代码
|