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. define(function(){
  2. var add = function(x,y){
  3. return x+y;
  4. };
  5. return{
  6. add:add
  7. };
  8. });

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

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

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

在看一个例子:

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

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

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

该配置将 /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. require.config({
  2. baseUrl:"/ems/js/",
  3. paths: {
  4. "jquery": "jquery.min",
  5. "pagination": "jquery.pagination"
  6. }
  7. });
  8.  
  9. require(["pagination"], function(pagination){
  10. $.patination(20);// ....
  11.  
  12. });

再看一个例子(文件名:dateUtil.js):

  1. define(function(){
  2. var dateFormat = function(fmt, date){
  3. if(!(date instanceof Date))
  4. return;
  5. var o = {
  6. "M+": date.getMonth() + 1, // 月份
  7. "d+": date.getDate(), //日
  8. "H+": date.getHours(), //24小时制
  9. "h+" : date.getHours()%12 == 0 ? 12 : date.getHours()%12, //12小时制
  10. "m+": date.getMinutes(), //分
  11. "s+": date.getSeconds(), //秒
  12. "q+": Math.floor((date.getMonth() + 3) / 3), //季度
  13. "S": date.getMilliseconds() //毫秒
  14. };
  15. if (/(y+)/.test(fmt))
  16. fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
  17. for (var k in o)
  18. if (new RegExp("(" + k + ")").test(fmt))
  19. fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k])
  20. : (("00" + o[k]).substr(("" + o[k]).length)));
  21. return fmt;
  22. };
  23.  
  24. return {
  25. format:dateFormat
  26. };
  27. });

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

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

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

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. </head>
  5. <body>
  6. <span>body1111</span>
  7. <script src="js/require.js" data-main="js/main.js"></script>
  8. </body>
  9. </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. require.config({
  2. paths: {
  3. "jquery": "jquery.min",
  4. "math":"math",
  5. "dateUtil":"dateUtil"
  6. }
  7. });
  8.  
  9. require(['jquery', 'math', "dateUtil" ], function ($, math, dateUtil){
  10. alert($("span").text());
  11. alert(math.add(1,30));
  12. alert(dateUtil.format("yyyy-MM-dd", new Date()));
  13. });

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文件合并压缩:

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

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <link rel="stylesheet" href="css/bootstrap.min.css" />
  5. <link rel="stylesheet" href="css/matrix-login.css" />
  6. <link rel="stylesheet" href="css/bootstrap-responsive.min.css" />
  7. </head>
  8. <body>
  9. <!--Header-part-->
  10. <div id="header">
  11. <h1><a href="javascript:;">Admin</a></h1>
  12. </div>
  13. <!--close-Header-part-->
  14.  
  15. <!--top-Header-menu-->
  16. <div id="user-nav" class="navbar navbar-inverse">
  17. <ul class="nav">
  18. <li class=""><span id="top_header">xxxx系统</span></li>
  19. <li><span id="cur_user">当前登陆用户:</span></li>
  20. <li id="top_logout" style="float:right;">
  21. <a href="${ctx}/logout"><i class="icon icon-share-alt"></i></a>
  22. </li>
  23. </ul>
  24. </div>
  25. <script src="js/require.min.js" data-main="js/main.js"></script>
  26. </body>
  27. </html>

其main.js文件如下:

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

那么显然该页面有 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. @import url(bootstrap.min.css);
  2. @import url(bootstrap-responsive.min.css);
  3. @import url(matrix-login.css);

然后调用命令合并压缩:

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

上面三行CSS的link:

  1. <link rel="stylesheet" href="css/bootstrap.min.css" />
  2. <link rel="stylesheet" href="css/matrix-login.css" />
  3. <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压缩。

require.js 最佳实践的更多相关文章

  1. require.js 最佳实践【转】

    https://www.cnblogs.com/digdeep/p/4607131.html require.js是一个js库,相关的基础知识,前面转载了两篇博文:Javascript模块化编程(re ...

  2. Vue.js最佳实践

    Vue.js最佳实践 第一招:化繁为简的Watchers 场景还原: created(){ this.fetchPostList() }, watch: { searchInputValue(){ t ...

  3. JavaScript best practices JS最佳实践

    JavaScript best practices JS最佳实践 0 简介 最佳实践起初比较棘手,但最终会让你发现这是非常明智之举. 1.合理命名方法及变量名,简洁且可读 var someItem = ...

  4. Vue.js最佳实践--给大量子孙组件传值(provide/inject)

    开发中有个需求,有个Parent组件(例如div)下,输入框,下拉框,radiobutton等可编辑的子孙组件几百个,根据某个值统一控制Parent下面的所有控件的disabled状态 类似于这样,给 ...

  5. JavaScrtip之JS最佳实践

    一.JavaScript之平稳退化 这边使用一个当用户点击某个页面内某个链接弹出一个新窗口的案例: JavaScript使用window对象的open()方法来创建新的浏览器窗口; window.op ...

  6. Vue.js最佳实践(五招让你成为Vue.js大师)

    对大部分人来说,掌握Vue.js基本的几个API后就已经能够正常地开发前端网站.但如果你想更加高效地使用Vue来开发,成为Vue.js大师,那下面我要传授的这五招你一定得认真学习一下了. 第一招:化繁 ...

  7. js最佳实践

    JavaScript使用windows对象的open()方法来创建新的浏览器窗口,这个方法有三个参数:windows.open(url,name,features) 参数一:url:是想在新窗口里打开 ...

  8. Vue.js最佳实践(五招助你成为vuejs大师)

    转自https://www.jb51.net/article/139448.htm 本文面向对象是有一定Vue.js编程经验的开发者.如果有人需要Vue.js入门系列的文章可以在评论区告诉我,有空就给 ...

  9. Vue.js最佳实践--VueRouter的beforeEnter与beforeRouteLeave冲突解决

    用Vue做应用管理系统,通常会在离开某个页面的时候,需要检测用户是否有修改,询问用户需要不需要保存之类的需求 这时候,在读VueRouter文档:组件内的守卫 的时候,发现beforeRouteLea ...

随机推荐

  1. 装饰 Markdown

    利用 Font Awesome 提升 Markdown 的表现能力 Font Awesome 是一个字体和图标工具包,包含人物.动物.建筑.商业.品牌等等各种主题丰富的图标符号,可以通过相应的语法添加 ...

  2. JAVAEE——宜立方商城05:前台系统搭建、首页展示、Cms系统的实现

    1. 学习计划 1.前台系统搭建 2.商城首页展示 3.Cms系统的实现 a) 内容分类管理 b) 内容管理 4.前台内容动态展示 2. 商城首页展示 系统架构: 页面位置: 2.1. 工程搭建 可以 ...

  3. [ 原创 ] Java基础7--Java反射机制主要提供了以下哪些功能?

    AVA反射机制主要提供了以下哪些功能? 在运行时判断一个对象所属的类 在运行时构造一个类的对象 在运行时判断一个类所具有的成员变量和方法 在运行时调用一个对象的方法

  4. 【CF 585E】 E. Present for Vitalik the Philatelist

    E. Present for Vitalik the Philatelist time limit per test 5 seconds memory limit per test 256 megab ...

  5. Luogu P4606 [SDOI2018] 战略游戏 圆方树 虚树

    https://www.luogu.org/problemnew/show/P4606 把原来的图的点双联通分量缩点(每个双联通分量建一个点,每个割点再建一个点)(用符合逻辑的方式)建一棵树(我最开始 ...

  6. 方程式0day图形化利用工具

    最近方程式的漏洞着实活了一把,分析了下githup上面的文件目录,找到了利用文件,主要是针对windows主机的SMB.RDP协议进行攻击,因为我主要根据他们提供的payload的程序,利用这两个模块 ...

  7. [USACO11DEC]Grass Planting

    题目大意: 有一棵结点个数为n的树,有m个操作,可以将一段路径上每条边的权值+1或询问某一个边的权值. 思路: 树链剖分+线段树. 轻重链划分本身比较简单,主要需要思考如何用线段树维护每条链. 当x, ...

  8. 【洛谷】2144:[FJOI2007]轮状病毒【高精度】【数学推导??(找规律)】

    P2144 [FJOI2007]轮状病毒 题目描述 轮状病毒有很多变种.许多轮状病毒都是由一个轮状基产生.一个n轮状基由圆环上n个不同的基原子和圆心的一个核原子构成.2个原子之间的边表示这2个原子之间 ...

  9. 1、安装Redis的PHP扩展

    1.安装Redis的PHP扩展 1.1 安装phpize yum install php-devel 1.2 下载扩展源码包,直接用wget #wget下载github上的文件 wget https: ...

  10. [Dynamic Language] Python定时任务框架

    APScheduler是一个Python定时任务框架,使用起来十分方便.提供了基于日期.固定时间间隔以及crontab类型的任务,并且可以持久化任务.并以daemon方式运行应用. 在APSchedu ...