为了提高页面的性能,通常情况下,我们希望资源尽可能地早地并行加载。这里有两个要点,首先是尽早,其次是并行。
通过data-main
方式加载要尽可能地避免,因为它让requirejs、业务代码不必要地串行起来。下面就讲下如何尽可能地利用浏览器并行加载的能力来提高性能。
低效串行:想爱但却无力
最简单的优化,下面的例子中,通过两个并排的script标签加载require.js、main.js,这就达到了require.js、main.js并行加载的目的。
但这会有个问题,假设main.js依赖了jquery.js、anonymous.js(如下代码所示),那么,只有等main.js加载完成,其依赖模块才会开始加载。这显然不够理想,后面我们会讲到如何避免这种情况,下面是简单的源码以及效果示意图。
demo.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>main.js、anynomous.js串行加载</h1>
<script type="text/javascript" src="js/require.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
js/main.js:
require(['js/anonymous'], function(Anonymous) {
alert('加载成功');
});
js/anonymous.js:
define(['js/jquery'], function() {
console.log('匿名模块,require直接报错了。。。');
return{
say: function(msg){
console.log(msg);
}
}
});
最终效果:
简单匿名:一条走不通的路
正常情况下,假设页面里有如下几个<script>
标签,现代浏览器就会并发请求文件,并顺序执行。但在requirejs里,如果这样做的话,可能会遇到一些意料之外的情况。如下所示,四个并排的标签,依次请求了require.js
、jquery.js
、anonymous.js
、main.js
。
demo.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<h1>requirejs并行加载例子</h1>
<script type="text/javascript" src="js/require.js"></script>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/anonymous.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
预期中,资源会并行加载,但实际上,你会在控制台里看到下面的错误日志。
为什么呢?对于requirejs来说,上面的js/anonymous.js
是一个匿名的模块,requirejs对它一无所知。当你在main中告诉requirejs说我要用到js/anonymous
这个模块时,它就傻眼了。所以,这里就直接给你报个错误提个醒:不要这样写,我不买账。
那么,及早并行加载的路是否走不通了呢?未必,请继续往下看。
答案就在身边:注册为命名模块的jquery
简单改下上面的例子,比如这样,然后。。它就行了。。
<script type="text/javascript" src="js/require.js"></script>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/main.js"></script>
原因很简单。因为jquery把自己注册成了命名模块。requirejs于是就认得jquery了。
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
define( "jquery", [], function () { return jQuery; } );
}
jquery的启发:起个好名字很重要
上面我们看到,给模块起个名字,将匿名模块改成命名模块(named module),就开启了我们的并行加载之旅。从这点看来,起名字真的很重要。
那么我们对之前的例子进行简单的改造。这里用了个小技巧,利用命名模块js/name-module.js
来加载之前的匿名模块js/anonymous.js
。可以看到,requirejs不报错了,requirejs跟name-module.js也并行加载了。
demo.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<h1>并行加载requirejs、jquery</h1>
<script type="text/javascript" src="js/require.js"></script>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/name-module.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
js/name-module.js
define('name-module', 'js/anonymous', [], function() {
return {
say: function(msg){
alert(msg);
}
};
});
最终效果图:
通往希望之门:解决anonymous模块的串行问题
如果你能耐着性子看到这一节,说明少年你已经发现了上一节很明显的一个问题:尽管name-module.js并行加载了,但anonymou.js其实还是串行加载,那做这个优化还有什么意义?
没错,如果最终优化效果这样的话,那是完全无法接受的。不卖关子,这个时候就要请出我们的requirejs打包神器r.js
。通过打包优化,将anonymous.js
、name-module.js
打包生成一个文件,就解决了串行的问题。
1、安装打包工具
npm install -g requirejs
2、创建打包配置文件,注意,由于jquery.js比较通用,一般情况下会单独加载,所以从打包的列表里排除
{
"appDir": "./", // 应用根路径
"baseUrl": "./", //
"dir": "dist", // 打包的文件生成到哪个目录
"optimize": "none", // 是否压缩
"modules": [
{
"name": "js/name-module",
"exclude": [
"jquery" // 将jqury从打包规则里排除
]
}
]
}
3、运行如下命令打包
r.js -o ./build.js
4、打包后的name-module
,可以看到,匿名模块也被打包进去,同时被转换成了命名模块
define('js/anonymous',['jquery'], function() {
console.log('匿名模块,require直接报错了。。。');
return{
say: function(msg){
console.log('anonymous: '+msg);
}
}
});
define('js/name-module', ['js/anonymous'], function() {
return {
say: function(msg){
alert('name module: '+msg);
}
};
});
5、再次访问demo.html,很好,就是我们想要的结果
写在后面
上面主要提供了及早并行加载的思路,但在实际利用requirejs打包的过程中,还会遇到一些需要小心处理的细节问题,当然也有一些坑。后面有时间再总结一下。