require.js 最佳实践

来源:互联网 发布:网络推广的任职要求 编辑:程序博客网 时间:2024/05/20 19:52

require.js是一个js库,相关的基础知识,前面转载了两篇博文:Javascript模块化编程(require.js), Javascript模块化工具require.js教程,RequireJS 参考文章

1. require.js的主要作用是js的工程化,规范化:

1)它是一个js脚本的加载器,它遵循AMD(Asynchronous Module Definition)规范,实现js脚本的异步加载,不阻塞页面的渲染和其后的脚本的执行。

    并提供了在加载完成之后的执行相应回调函数的功能;

2)它要求js脚本的模块化,也就是文件化;require.js的作用之一就是加载js模块,也就是js文件。所以我们的js的书写应该模块化,也就是文件化。

3)它可以管理js模块/文件之间的依赖; js模块化,文件化之后,它们之间的依赖可以通过require.js优雅的解决;

4)require.js中提供的优化器 r.js 可以来优化页面中的js脚本和css文件,达到提高页面响应速度,减少页面所需要的http/https请求次数。在极端优化的情况下,通过r.js优化之后的页面只需要一次js脚本请求和一次CSS文件请求。这就极大的减少了页面所需要的http/https请求的次数,提高了页面的加载速度。r.js的优化分为两种方式:一是压缩js和css文件,也就是去掉空格,空行,将长变量名换成短变量名之类的;二是合并多个js文件为一个js文件,合并多个css文件为一个

5) 通过使用require.js之后,我们只需要在页面引入一行<script>标签,类似于:<script src="js/require.js" data-main="js/login.js"></script>,甚至也可以只引入一行<style>标签,十分优雅。注意引入一行<script>标签并不等价于只需要一次js的http/https的请求。

2. require.js模块的写法:

require.js要求我们的js模块,也就是js文件按照一定的格式书写:也就是最好通过define()函数来写js模块,比如:math.js

1
2
3
4
5
6
7
8
define(function(){
    var add = function(x,y){
        return x+y;
    };
    return{
        add:add
    };
});

math.js通过define()函数,定义了一个符合require.js要求的js模块,它的返回值是一个对象,有一个属性add,它是一个函数。通过下的方式就可以来调用该js模块中定义的函数:

1
2
3
4
5
6
7
8
9
10
require.config({
    baseUrl:"/ems/js/",
    paths:{
        "math":"math"
    }
});
 
require(["math"], function(math){
    alert(math.add(100,20));
});

require.config的主要作用是配置 模块ID/模块名称 和 它对应的js文件所在的位置。上面的那个配置就是将 /ems/js/math.js(ems是项目名称) 文件配置成一个ID为math的模块,然后通过 require(["math"], function(math)(){}); 就可以异步来加载 /ems/js/math.js 文件,加载完成之后,执行回调函数。回调函数中调用了math模块中的add方法。

在看一个例子:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
 * This jQuery plugin displays pagination links inside the selected elements.
 *
 * @author Gabriel Birke (birke *at* d-scribe *dot* de)
 * @version 1.2
 * @param {int} maxentries Number of entries to paginate
 * @param {Object} opts Several options (see README for documentation)
 * @return {Object} jQuery Object
 */
define(['jquery'], function($){
    jQuery.fn.pagination = function(maxentries, opts){
        opts = jQuery.extend({
            items_per_page:10,
            num_display_entries:10,
            current_page:0,
            num_edge_entries:0,
            link_to:"#",
            prev_text:"Prev",
            next_text:"Next",
            ellipse_text:"...",
            prev_show_always:true,
            next_show_always:true,
            callback:function(){return false;}
        },opts||{});
         
        return this.each(function() {          
            function numPages() {
                return Math.ceil(maxentries/opts.items_per_page);
            }              
            function getInterval()  {
                var ne_half = Math.ceil(opts.num_display_entries/2);
                var np = numPages();
                var upper_limit = np-opts.num_display_entries;
                var start = current_page>ne_half?Math.max(Math.min(current_page-ne_half, upper_limit), 0):0;
                var end = current_page>ne_half?Math.min(current_page+ne_half, np):Math.min(opts.num_display_entries, np);
                return [start,end];
            }          
            function pageSelected(page_id, evt){
                current_page = page_id;
                drawLinks();
                var continuePropagation = opts.callback(page_id, panel);
                if (!continuePropagation) {
                    if (evt.stopPropagation) {
                        evt.stopPropagation();
                    }
                    else {
                        evt.cancelBubble = true;
                    }
                }
                return continuePropagation;
            }          
            function drawLinks() {
                panel.empty();
                var interval = getInterval();
                var np = numPages();               
                var getClickHandler = function(page_id) {
                    return function(evt){ return pageSelected(page_id,evt); };
                }              
                var appendItem = function(page_id, appendopts){
                    page_id = page_id<0?0:(page_id<np?page_id:np-1);
                    appendopts = jQuery.extend({text:page_id+1, classes:""}, appendopts||{});
                    if(page_id == current_page){
                        var lnk = jQuery("<span class='current'>"+(appendopts.text)+"</span>");
                    }else{
                        var lnk = jQuery("<a>"+(appendopts.text)+"</a>")
                            .bind("click", getClickHandler(page_id))
                            .attr('href', opts.link_to.replace(/__id__/,page_id));     
                    }
                    if(appendopts.classes){lnk.addClass(appendopts.classes);}
                    panel.append(lnk);
                }              
                if(opts.prev_text && (current_page > 0 || opts.prev_show_always)){
                    appendItem(current_page-1,{text:opts.prev_text, classes:"prev"});
                }              
                if (interval[0] > 0 && opts.num_edge_entries > 0)
                {
                    var end = Math.min(opts.num_edge_entries, interval[0]);
                    for(var i=0; i<end; i++) {
                        appendItem(i);
                    }
                    if(opts.num_edge_entries < interval[0] && opts.ellipse_text)
                    {
                        jQuery("<span>"+opts.ellipse_text+"</span>").appendTo(panel);
                    }
                }              
                for(var i=interval[0]; i<interval[1]; i++) {
                    appendItem(i);
                }              
                if (interval[1] < np && opts.num_edge_entries > 0)
                {
                    if(np-opts.num_edge_entries > interval[1]&& opts.ellipse_text)
                    {
                        jQuery("<span>"+opts.ellipse_text+"</span>").appendTo(panel);
                    }
                    var begin = Math.max(np-opts.num_edge_entries, interval[1]);
                    for(var i=begin; i<np; i++) {
                        appendItem(i);
                    }
                     
                }              
                if(opts.next_text && (current_page < np-1 || opts.next_show_always)){
                    appendItem(current_page+1,{text:opts.next_text, classes:"next"});
                }
            }          
            var current_page = opts.current_page;          
            maxentries = (!maxentries || maxentries < 0)?1:maxentries;
            opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0)?1:opts.items_per_page;          
            var panel = jQuery(this);
            this.selectPage = function(page_id){ pageSelected(page_id);}
            this.prevPage = function(){
                if (current_page > 0) {
                    pageSelected(current_page - 1);
                    return true;
                }
                else {
                    return false;
                }
            }
            this.nextPage = function(){
                if(current_page < numPages()-1) {
                    pageSelected(current_page+1);
                    return true;
                }
                else {
                    return false;
                }
            };         
            drawLinks();          
            opts.callback(current_page, this);
        });
    }; 
    return jQuery.fn.pagination;
});

上面的define()函数定义了一个jquery的分页插件(文件名:jquery.pagination.js),它符合require.js模块的规范。define(['jquery'], function($)... 表示该模块依赖于 jquery 模块,并向回调函数传入jquery的全局对象 $, 那么这里的 ['jquery'] 又来自哪里呢?它其实来自于:

1
2
3
4
5
6
require.config({
        baseUrl:"/ems/js/",
    paths: {
        "jquery""jquery.min"
    }
});

该配置将 /ems/js/jquery.min.js 配置成require.js的模块,模块ID为"jquery",所以我们才能使用 define(['jquery'], function($) 来引用"jquery"模块。

define(["xxx","yyy"], function(xxx,yyy){}); define函数定义符合require.js规范的模块,数组参数指定该模块依赖的所有模块,那么这些被依赖的模块异步加载完成之后,然后执行回调函数,回调函数的返回值就是该模块的定义。返回值一般是一个对象,或者一个函数。然后该模块又可以被其它模块所依赖和使用。比如上面的: jquery.pagination.js,它定义了一个jquery的分页插件,那么通过下面的配置,我就可以使用它:

1
2
3
4
5
6
7
8
9
10
11
12
require.config({
    baseUrl:"/ems/js/",
    paths: {
        "jquery""jquery.min",
        "pagination""jquery.pagination"
    }  
});
 
require(["pagination"], function(pagination){
     $.patination(20);// ....
 
});

再看一个例子(文件名:dateUtil.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
define(function(){
    var dateFormat = function(fmt, date){
        if(!(date instanceof Date))
            return;
        var o = {
                "M+": date.getMonth() + 1, // 月份
                "d+": date.getDate(), //日
                "H+": date.getHours(), //24小时制
                "h+" : date.getHours()%12 == 0 ? 12 : date.getHours()%12, //12小时制 
                "m+": date.getMinutes(), //分
                "s+": date.getSeconds(), //秒
                "q+": Math.floor((date.getMonth() + 3) / 3), //季度
                "S": date.getMilliseconds()  //毫秒
            };
            if (/(y+)/.test(fmt))
                fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
            for (var in o)
            if (new RegExp("(" + k + ")").test(fmt))
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k])
                                    : (("00" + o[k]).substr(("" + o[k]).length)));
            return fmt;
    };
     
    return {
        format:dateFormat
    };
});

通过下面的配置,就可以使用dataUtil.js中的format()函数来格式化日期对象:

1
2
3
4
5
6
7
8
9
10
require.config({
    baseUrl:"/ems/js/",
    paths: {
        "dateUtil""dateUtil"     
    }  
});
 
require(["dateUtil"], function(dateUtil){
   alert(dateUtil.format("yyyy-MM-dd"new Date());
});

我们在页面中引入上面的文件(文件名:main.js),就可以看到执行效果:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
</head>
<body>
      <span>body1111</span>
<script src="js/require.js" data-main="js/main.js"></script>
</body>
</html>

执行结果:

3. require.config函数

require.config如上面所说,主要是定义 模块ID 和 它所对应的js文件的位置。参数是一个json格式的对象,baseUrl属性指定paths中的路径的相对路径。paths是一个key/value的键值对形式,key表示模块ID,value表示相对于baseUrl的相对路径,需要省略文件后缀 .js 。还有一个shim的常用属性,用来配置不符合require.js规范的js模块(没有使用define()来书写),使之也能被我们的require()函数来使用。但是shim配置的模块,无法通过cnd使用。其使用方法参见前面的转载文章。

4. require()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
require.config({
    paths: {
        "jquery""jquery.min",
        "math":"math",
        "dateUtil":"dateUtil"
    }
});
 
require(['jquery''math'"dateUtil" ], function ($, math, dateUtil){
    alert($("span").text());
    alert(math.add(1,30));
    alert(dateUtil.format("yyyy-MM-dd"new Date()));
});

require()函数,第一个参数,引入依赖的require模块, 在所有依赖异步加载完成之后,将这些模块的返回的对象或者返回的函数传入回调函数,那么在回调函数中就可以使用这些被依赖的模块的功能了。比如上面使用 math.add(1,30)。

5. r.js 优化(合并压缩js和CSS文件)

按照require方式进行模块化之后,必然会产生很多的js文件,它们通过环环相扣的方式,按照依赖关系异步加载,那么必然会导致js文件所需要的http/https请求极大的增加,这时,就应该使用 r.js 来优化了。它可以将一个页面比如 login.jsp, 需要的所有的js文件合并成一个js文件也就是说只需要一次http/https请求就行了。所以就解决了js模块化之后http/https请求增多的问题,并且还减少到了只需要一次请求。同时r.js还可以压缩js文件。并且正对css文件也可以同样的方式减小合并压缩。优化一般在开发完成之后,发布之前进行。

1)js文件合并压缩:

比如开发时,某页面如下:

其main.js文件如下:

1
2
3
4
5
6
7
8
9
10
11
require.config({   
    baseUrl:"/emsjs/",
    paths: {       
        "jquery":"jquery.min",
        "dateUtil":"dateUtil"
    }
});
 
require(['jquery','dateUtil'], function ($, dateUtil){
    // ...
});

那么显然该页面有 3个 CSS文件,2个js文件。那么针对js文件,我们可以使用node.js来合并:

我们看到将 jquery.js, dateUtil.js, main.js 三个文件合并压缩成了一个文件:login.js, 那么我们在页面中就只需要引入login.js文件就行了。

1
<script src="js/require.min.js" data-main="js/main.js"></script>

 改成:

1
<script src="js/require.min.js" data-main="js/login.js"></script>

2) CSS文件合并压缩:

合并压缩之前,需要先定义一个main.css文件:

1
2
3
@import url(bootstrap.min.css);
@import url(bootstrap-responsive.min.css);
@import url(matrix-login.css);

然后调用命令合并压缩:

四个CSS文件合并成了一个css文件:login.css。我们看下压缩之后的login.css:

上面三行CSS的link:

1
2
3
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/matrix-login.css" />
<link rel="stylesheet" href="css/bootstrap-responsive.min.css" />

就可以换成一行:

1
<link rel="stylesheet" href="css/login.css" />

r.js的优化的详细介绍,可以参考前面转载的 RequireJS 参考文章 中的进阶的三篇文章。也可以参考require.js官网关于r.js的介绍。

6. require.js 最佳实践

前面说了那么多,最后才说到require.js的最佳实践。

1)使用 define() 定义符合require规范的模块;

2)使用require.config() 配置模块ID和它对应的js模块所在文件路径;require.config()是将define()定义的模块和require()依赖的模块连接起来;

3)使用require()指定其所依赖的模块,在回调中实现页面上需要的功能,当然define()函数也需要指定其所依赖的模块;

     require()和define()函数其实十分相似,都指定依赖的模块,都有回调函数;

4)使用r.js合并优化。这里最重要。合并优化涉及到一个取舍问题,比如前面的 jquery.min.js 是否应该被合并进去呢?因为jquery.min.js是一个通用的js库文件,那么其实几乎每一个页面都需要改文件,那么其实我们只是在第一次访问该网站时,需要下载一次jquery.min.js文件,其后使用的都是缓存中的,status都是304;但是如果我们每个页面都将 jquery.min.js 合并进该页面的唯一的 js 文件,那么jquery.min.js就会被每个页面所下载,因为每个页面都合并了它。个人是觉得不应该将jquery.min.js这样的通用库合并进去的,而是应该放入cnd中,这样既不会受到浏览器访问同一个域名时,并发数量的限制,也可以使其能够被缓存。但是 304 好像也是需要发送一次http/https请求的?所以如何取舍呢?CSS文件bootstrap.min.css也遇到相似的取舍问题。

个人倾向于不合并jquery.min.js和bootstrap.min.css等类似的基础文件。所以最佳require.js的实践就是,每个页面只引入一个js文件,该js文件不合并jquery.min.js文件以及类似的js文件,合并其它所有依赖的js文件。每个页面除了bootstrap.min.css类似的基础文件需要的<link >之外,还引入一个合并其它所有需要的css文件的<link>标签。

7. 关于压缩

关于压缩,上面说到了使用 r.js 进行压缩是指去掉空格,空行,将长变量名换成短的等等;压缩还有另外一层压缩:配置tomcat或者nginx/apache等web服务器,也是可以配置对CSS/JS/HTML等进行压缩的,一般浏览器都支持(能解压)服务器端进行的gzip压缩。

1 0
原创粉丝点击