分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 前端开发

JS -- 一篇文章掌握RequireJS常用知识

发布时间:2023-09-06 01:07责任编辑:彭小芳关键词:暂无标签

本文采取循序渐进的方式,从理论到实践,从RequireJS官方API文档中,总结出在使用RequireJS过程中最常用的一些用法,并对文档中不够清晰具体的内容,加以例证和分析,分享给大家供大家参考,具体内容如下

1. 模块化
相信每个前端开发人员在刚开始接触js编程时,都写过类似下面这样风格的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">
var a = 1;
var b = 2;
var c = a * a + b * b;
if(c> 1) {
alert(‘c > 1‘);
}
function add(a, b) {
return a + b;
}
c = add(a,b);
</script>
?
1
<a href="javascript:;" onclick="click(this);" title="">请点击</a>

这些代码的特点是:

  • 到处可见的全局变量
  • 大量的函数
  • 内嵌在html元素上的各种js调用

当然这些代码本身在实现功能上并没有错误,但是从代码的可重用性,健壮性以及可维护性来说,这种编程方式是有问题的,尤其是在页面逻辑较为复杂的应用中,这些问题会暴露地特别明显:

  • 全局变量极易造成命名冲突
  • 函数式编程非常不利于代码的组织和管理
  • 内嵌的js调用很不利于代码的维护,因为html代码有的时候是十分臃肿和庞大的

所以当这些问题出现的时候,js大牛们就开始寻找去解决这些问题的究极办法,于是模块化开发就出现了。正如模块化这个概念的表面意思一样,它要求在编写代码的时候,按层次,按功能,将独立的逻辑,封装成可重用的模块,对外提供直接明了的调用接口,内部实现细节完全私有,并且模块之间的内部实现在执行期间互不干扰,最终的结果就是可以解决前面举例提到的问题。一个简单遵循模块化开发要求编写的例子:

?
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//module.js
var student = function (name) {
return name && {
getName: function () {
return name;
}
};
},
course = function (name) {
return name && {
getName: function () {
return name;
}
}
},
controller = function () {
var data = {};
return {
add: function (stu, cour) {
var stuName = stu && stu.getName(),
courName = cour && cour.getName(),
current,
_filter = function (e) {
return e === courName;
};
if (!stuName || !courName) return;
current = data[stuName] = data[stuName] || [];
if (current.filter(_filter).length === 0) {
current.push(courName);
}
},
list: function (stu) {
var stuName = stu && stu.getName(),
current = data[stuName];
current && console.log(current.join(‘;‘));
}
}
};
//main.js
var stu = new student(‘lyzg‘),
c = new controller();
c.add(stu,new course(‘javascript‘));
c.add(stu,new course(‘html‘));
c.add(stu,new course(‘css‘));
c.list(stu);

以上代码定义了三个模块分别表示学生,课程和控制器,然后在main.js中调用了controller提供的add和list接口,为lyzg这个学生添加了三门课程,然后在控制台显示了出来。运行结果如下:

?
1
javascript;html;css

通过上例,可以看出模块化的代码结构和逻辑十分清晰,代码看起来十分优雅,另外由于逻辑都通过模块拆分,所以达到了解耦的目的,代码的功能也会比较健壮。不过上例使用的这种模块化开发方式也并不是没有问题,这个问题就是它还是把模块引用如student这些直接添加到了全局空间下,虽然通过模块减少了很多全局空间的变量和函数,但是模块引用本身还是要依赖全局空间,才能被调用,当模块较多,或者有引入第三方模块库时,仍然可能造成命名冲突的问题,所以这种全局空间下的模块化开发的方式并不是最完美的方式。目前常见的模块化开发方式,全局空间方式是最基本的一种,另外常见的还有遵循AMD规范的开发方式,遵循CMD规范的开发方式,和ECMAScript 6的开发方式。需要说明的是,CMD和ES6跟本文的核心没有关系,所以不会在此介绍,后面的内容主要介绍AMD以及实现了AMD规范的RequireJS。

2. AMD规范
正如上文提到,实现模块化开发的方式,另外常见的一种就是遵循AMD规范的实现方式,不过AMD规范并不是具体的实现方式,而仅仅是模块化开发的一种解决方案,你可以把它理解成模块化开发的一些接口声明,如果你要实现一个遵循该规范的模块化开发工具,就必须实现它预先定义的API。比如它要求在加载模块时,必须使用如下的API调用方式:

?
1
2
3
4
require([module], callback)
其中:
[module]:是一个数组,里面的成员就是要加载的模块;
callback:是模块加载完成之后的回调函数

所有遵循AMD规范的模块化工具,都必须按照它的要求去实现,比如RequireJS这个库,就是完全遵循AMD规范实现的,所以在利用RequireJS加载或者调用模块时,如果你事先知道AMD规范的话,你就知道该怎么用RequireJS了。规范的好处在于,不同的实现却有相同的调用方式,很容易切换不同的工具使用,至于具体用哪一个实现,这就跟各个工具的各自的优点跟项目的特点有关系,这些都是在项目开始选型的时候需要确定的。目前RequireJS不是唯一实现了AMD规范的库,像Dojo这种更全面的js库也都有AMD的实现。

最后对AMD全称做一个解释,译为:异步模块定义。异步强调的是,在加载模块以及模块所依赖的其它模块时,都采用异步加载的方式,避免模块加载阻塞了网页的渲染进度。相比传统的异步加载,AMD工具的异步加载更加简便,而且还能实现按需加载,具体解释在下一部分说明。

3. JavaScript的异步加载和按需加载
html中的script标签在加载和执行过程中会阻塞网页的渲染,所以一般要求尽量将script标签放置在body元素的底部,以便加快页面显示的速度,还有一种方式就是通过异步加载的方式来加载js,这样可以避免js文件对html渲染的阻塞。

第1种异步加载的方式是直接利用脚本生成script标签的方式:

?
1
2
3
4
5
6
7
8
(function() {
var s = document.createElement(‘script‘);
s.type = ‘text/javascript‘;
s.async = true;
s.src = ‘http://yourdomain.com/script.js‘;
var x = document.getElementsByTagName(‘script‘)[0];
x.parentNode.insertBefore(s, x);
})();

这段代码,放置在script标记内部,然后该script标记添加到body元素的底部即可。

第2种方式是借助script的属性:defer和async,defer这个属性在IE浏览器和早起的火狐浏览器中支持,async在支持html5的浏览器上都支持,只要有这两个属性,script就会以异步的方式来加载,所以script在html中的位置就不重要了:

?
1
2
3
<script defer async="true" type="text/javascript" src="app/foo.js"></script>
<script defer async="true" type="text/javascript" src="app/bar.js"></script>
<script defer async="true" type="text/javascript" src="app/main.js"></script>

这种方式下,所有异步js在执行的时候还是按顺序执行的,不然就会存在依赖问题,比如如果上例中的main.js依赖foo.js和bar.js,但是main.js先执行的话就会出错了。虽然从来理论上这种方式也算不错了,但是不够好,因为它用起来很繁琐,而且还有个问题就是页面需要添加多个script标记以及没有办法完全做到按需加载。

JS的按需加载分两个层次,第一个层次是只加载这个页面可能被用到的JS,第二个层次是在只在用到某个JS的时候才去加载。传统地方式很容易做到第一个层次,但是不容易做到第二个层次,虽然我们可以通过合并和压缩工具,将某个页面所有的JS都添加到一个文件中去,最大程度减少资源请求量,但是这个JS请求到客户端以后,其中有很多内容可能都用不上,要是有个工具能够做到在需要的时候才去加载相关js就完美解决问题了,比如RequireJS。

4. RequireJS常用用法总结
前文多次提及RequireJS,本部分将对它的常用用法详细说明,它的官方地址是:http://www.requirejs.cn/,你可以到该地址去下载最新版RequireJS文件。RequireJS作为目前使用最广泛的AMD工具,它的主要优点是:

  • 完全支持模块化开发
  • 能将非AMD规范的模块引入到RequireJS中使用
  • 异步加载JS
  • 完全按需加载依赖模块,模块文件只需要压缩混淆,不需要合并
  • 错误调试
  • 插件支持

4.01 如何使用RequireJS
使用方式很简单,只要一个script标记就可以在网页中加载RequireJS:

?
1
<script defer async="true" src="/bower_components/requirejs/require.js"></script>

由于这里用到了defer和async这两个异步加载的属性,所以require.js是异步加载的,你把这个script标记放置在任何地方都没有问题。

4.02 如何利用RequireJS加载并执行当前网页的逻辑JS

4.01解决的仅仅是RequireJS的使用问题,但它仅仅是一个JS库,是一个被当前页面的逻辑所利用的工具,真正实现网页功能逻辑的是我们要利用RequireJS编写的主JS,这个主JS(假设这些代码都放置在main.js文件中)又该如何利用RJ来加载执行呢?方式如下:

?
1
<script data-main="scripts/main.js" defer async="true" src="/bower_components/requirejs/require.js"></script>

对比4.01,你会发现script标记多了一个data-main,RJ用这个配置当前页面的主JS,你要把逻辑都写在这个main.js里面。当RJ自身加载执行后,就会再次异步加载main.js。这个main.js是当前网页所有逻辑的入口,理想情况下,整个网页只需要这一个script标记,利用RJ加载依赖的其它文件,如jquery等。

4.03 main.js怎么写
假设项目的目录结构为:

main.js是跟当前页面相关的主JS,app文件夹存放本项目自定义的模块,lib存放第三方库。

html中按4.02的方式配置RJ。main.js的代码如下:

?
1
2
3
require([‘lib/foo‘, ‘app/bar‘, ‘app/app‘], function(foo, bar, app) {
//use foo bar app do sth
});

在这段JS中,我们利用RJ提供的require方法,加载了三个模块,然后在这个三个模块都加载成功之后执行页面逻辑。require方法有2个参数,第一个参数是数组类型的,实际使用时,数组的每个元素都是一个模块的module ID,第二个参数是一个回调函数,这个函数在第一个参数定义的所有模块都加载成功后回调,形参的个数和顺序分别与第一个参数定义的模块对应,比如第一个模块时lib/foo,那么这个回调函数的第一个参数就是foo这个模块的引用,在回调函数中我们使用这些形参来调用各个模块的方法,由于回调是在各模块加载之后才调用的,所以这些模块引用肯定都是有效的。

从以上这个简短的代码,你应该已经知道该如何使用RJ了。

4.04 RJ的baseUrl和module ID
在介绍RJ如何去解析依赖的那些模块JS的路径时,必须先弄清楚baseUrl和module ID这两个概念。

html中的base元素可以定义当前页面内部任何http请求的url前缀部分,RJ的baseUrl跟这个base元素起的作用是类似的,由于RJ总是动态地请求依赖的JS文件,所以必然涉及到一个JS文件的路径解析问题,RJ默认采用一种baseUrl + moduleID的解析方式,这个解析方式后续会举例说明。这个baseUrl非常重要,RJ对它的处理遵循如下规则:

  • 在没有使用data-main和config的情况下,baseUrl默认为当前页面的目录
  • 在有data-main的情况下,main.js前面的部分就是baseUrl,比如上面的scripts/
  • 在有config的情况下,baseUrl以config配置的为准

上述三种方式,优先级由低到高排列。

data-main的使用方式,你已经知道了,config该如何配置,如下所示:

?
1
2
3
require.config({
baseUrl: ‘scripts‘
});

这个配置必须放置在main.js的最前面。data-main与config配置同时存在的时候,以config为准,由于RJ的其它配置也是在这个位置配置的,所以4.03中的main.js可以改成如下结构,以便将来的扩展:

?
1
2
3
4
5
6
7
require.config({
baseUrl: ‘scripts‘
});
require([‘lib/foo‘, ‘app/bar‘, ‘app/app‘], function(foo, bar, app) {
// use foo bar app do sth
});

关于module ID,就是在require方法以及后续的define方法里,用在依赖数组这个参数里,用来标识一个模块的字符串。上面代码中的[‘lib/foo‘, ‘app/bar‘, ‘app/app‘]就是一个依赖数组,其中的每个元素都是一个module ID。值得注意的是,module ID并不一定是该module 相关JS路径的一部分,有的module ID很短,但可能路径很长,这跟RJ的解析规则有关。下一节详细介绍。

4.05 RJ的文件解析规则
RJ默认按baseUrl + module ID的规则,解析文件,并且它默认要加载的文件都是js,所以你的module ID里面可以不包含.js的后缀,这就是为啥你看到的module ID都是lib/foo, app/bar这种形式了。有三种module ID,不适用这种规则:

  • 以/开头,如/lib/jquey.js
  • 以.js结尾,如test.js
  • 包含http或https,如http://xx.baidu.com/js/jquery.js

假如main.js如下使用:

?
1
2
3
4
5
6
7
require.config({
baseUrl: ‘scripts‘
});
require([‘/lib/foo‘, ‘test.js‘, ‘/js/jquery‘], function(foo, bar, app) {
// use foo bar app do sth
});

这三个module 都不会根据baseUrl + module ID的规则来解析,而是直接用module ID来解析,等效于下面的代码:

?
1
2
3
我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved