[关闭]
@lizlalala 2016-08-22T10:57:04.000000Z 字数 9113 阅读 1467

从requirejs看模块化

requirejs 模块化


1.requirejs优势(使用场景)


2.实战演练

define跟require的区别在于一个是用于定义模块的,一个是使用已定义的模块的。

  1. //test.html
  2. <!DOCTYPE>
  3. <html>
  4. <head>
  5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  6. <title>requirejs初览</title>
  7. </head>
  8. <body>
  9. <div class="filter-item">
  10. <label for="productLine">业务线</label>
  11. <select id="productLine" class="mis-select">
  12. <option value="kuaiche">快车</option>
  13. <option value="sfc">顺风车</option>
  14. </select>
  15. </div>
  16. <div class="filter-item">
  17. <label for="target">指标</label>
  18. <select id="target" class="mis-select">
  19. <option value="need_num" selected="selected" class="need_num">需求数</option>
  20. <option value="call_num">呼叫量</option>
  21. </select>
  22. </div>
  23. <div class="filter-item">
  24. <label for="particleSize">粒度</label>
  25. <select id="particleSize" class="mis-select">
  26. <option value="1">1分钟</option>
  27. <option value="5">5分钟</option>
  28. </select>
  29. </div>
  30. <div class="filter-item">
  31. <label for="hourRange">区间</label>
  32. <select id="hourRange" class="mis-select">
  33. <option value="00">00:00-01:00</option>
  34. <option value="01">01:00-02:00</option>
  35. </select>
  36. </div>
  37. </body>
  38. <script src="./require.js"></script>
  39. //这边也可以直接设置data-main作为入口,举个栗子,test作为入口的话,把config设置放进去,然后直接require moment跟jquery,定义回调函数就可以,
  40. <script >
  41. require.config({
  42. paths:{
  43. heatForm:"./test",
  44. moment:"./moment",
  45. jquery:"./jquery"
  46. }
  47. });
  48. require(['heatForm'],function(heatForm){
  49. heatForm.bindEvent();
  50. })
  51. </script>
  52. </html>
  1. //test.js
  2. define('heatForm', ['jquery','moment'], function($,moment) {
  3. var o = {};
  4. var target = $("#target"); //指标
  5. var productLine = $("#productLine");
  6. var particleSize = $("#particleSize");
  7. var hourRange = $("#hourRange");
  8. var searchBtn = $("#searchBtn");
  9. function generateRangeHtml(size) {
  10. var totalMinutesOneDay = 1440,
  11. durationsArr = [],
  12. intervals;
  13. if (size === 0) {
  14. size = 1;
  15. intervals = moment().diff(moment({
  16. h: 0,
  17. m: 0
  18. }), 'm');
  19. } else
  20. intervals = totalMinutesOneDay / size;
  21. for (var i = 0; i < intervals; i++) {
  22. var durations = i * size;
  23. var h = Math.floor(durations / 60);
  24. var m = durations - h * 60;
  25. var startTime = moment({
  26. hour: h,
  27. minute: m
  28. });
  29. var endTime = moment({
  30. hour: h,
  31. minute: m
  32. }).add(size, 'm');
  33. durationsArr.push({
  34. startTime: startTime.format('HH:mm'),
  35. endTime: endTime.format("HH:mm")
  36. });
  37. }
  38. var newOptions = durationsArr.map(function(one) {
  39. return `<option value=${one.startTime}>${one.startTime}-${one.endTime}</option>`;
  40. }).join("");
  41. return newOptions;
  42. }
  43. function bindEvent() {
  44. particleSize.on("change", function(event) {
  45. var size = parseInt($(this).val());
  46. var newRangeOptions = generateRangeHtml(size);
  47. hourRange.html(newRangeOptions);
  48. });
  49. productLine.on("change", function(event) {
  50. var product = $(this).val();
  51. if (product === "sfc") {
  52. target.html("<option value='call_num'>呼叫量</option>");
  53. }
  54. });
  55. }
  56. o.bindEvent = bindEvent;
  57. return o;
  58. });

3. fis中的mod.js里面的require

实习的过程中最痛心的就是遗留代码的加功能点...之前的fe是用的旧版本的fis中的mod.js,定义了citylist组件,导致页面上要用到这一部分就得使用mod中的require。
然后比较神奇的有这么几点

  1. require.resourceMap({
  2. res:{
  3. 'cityList':{
  4. url:'/static/js/app/module_cityList.js'
  5. },
  6. 'echarts':{
  7. url:'/static/hotmap/js/libs/echarts/echarts.common.js'
  8. },
  9. 'moment':{
  10. url:"/static/hotmap/js/libs/moment/moment.js"
  11. },
  12. 'BMap':{
  13. url:'http://api.map.baidu.com/api?v=2.0&ak=8WEtlKYLwwssirUarD5O7ba0.js'
  14. },
  15. 'heatForm':{
  16. url:"/static/hotmap/js/components/formRelated.js"
  17. },
  18. },
  19. 'HeatmapOverlay':{
  20. deps:['BMap'], //**类似于shim**
  21. url:'http://api.map.baidu.com/library/Heatmap/2.0/src/Heatmap_min.js'
  22. }
  23. }
  24. });

eg.

  1. var moment = require(['moment']);

并不是我们通常意义上理解的模块、同步、引入。= =

demo如图。

其中,heatform的文件映射见上

  1. define('heatForm',function(require,exports, module){
  2. var moment = require(['moment']);
  3. var $ = require(["jquery"]);
  4. var o = {};
  5. var target = $("#target"); //指标
  6. var productLine = $("#productLine");
  7. var particleSize = $("#particleSize");
  8. var hourRange = $("#hourRange");
  9. var searchBtn = $("#searchBtn");
  10. //对应于size的当前时间的format,如粒度为15时,现在7:35,output:7:30-7:45
  11. function getCurStart(size) {
  12. if (size == 0) size = 1;
  13. var intervals = moment().diff(moment({
  14. h: 0,
  15. m: 0
  16. }), 'm');
  17. var result = moment({ h: 0, m: 0 }).add(Math.floor(intervals / size) * size, 'm').format("HH:mm");
  18. return result;
  19. }
  20. //时间段的下拉菜单html
  21. function generateRangeHtml(size) {
  22. var totalMinutesOneDay = 1440,
  23. durationsArr = [],
  24. intervals;
  25. if (size === 0) {
  26. size = 1;
  27. intervals = moment().diff(moment({
  28. h: 0,
  29. m: 0
  30. }), 'm') + 1;
  31. } else
  32. intervals = totalMinutesOneDay / size;
  33. for (var i = 0; i < intervals; i++) {
  34. var durations = i * size;
  35. var h = Math.floor(durations / 60);
  36. var m = durations - h * 60;
  37. var startTime = moment({
  38. hour: h,
  39. minute: m
  40. });
  41. var endTime = moment({
  42. hour: h,
  43. minute: m
  44. }).add(size, 'm');
  45. durationsArr.push({
  46. startTime: startTime.format('HH:mm'),
  47. endTime: endTime.format("HH:mm")
  48. });
  49. }
  50. var curTime = getCurStart(size);
  51. var newOptions = durationsArr.map(function(one) {
  52. if (one.startTime === curTime) {
  53. return `<option selected=${"selected"} value=${one.startTime}>${one.startTime}-${one.endTime}</option>`
  54. } else
  55. return `<option value=${one.startTime}>${one.startTime}-${one.endTime}</option>`;
  56. }).join("");
  57. return newOptions;
  58. }
  59. function bindEvent() {
  60. particleSize.on("change", function(event) {
  61. var size = parseInt($(this).val());
  62. var newRangeOptions = generateRangeHtml(size);
  63. hourRange.html(newRangeOptions);
  64. });
  65. productLine.on("change", function(event) {
  66. var product = $(this).val();
  67. if (product === "sfc") {
  68. target.html("<option value='callNum'>呼叫量</option>");
  69. }else{
  70. target.html(
  71. ["<option value='needNum'>需求数</option>",
  72. "<option value='callNum'>呼叫量</option>"].join(""));
  73. }
  74. });
  75. target.on("change",function(event){
  76. var $this = $(this);
  77. var sfcOption = "<option value='sfc'>顺风车</option>"
  78. if(($this).val()==="needNum"){
  79. productLine.children(".sfc").remove();
  80. }else{
  81. if(productLine.children(".sfc")===-1)
  82. productLine.append(sfcOption);
  83. }
  84. });
  85. //待加入searchBtn的click事件
  86. }
  87. o.bindEvent = bindEvent;
  88. // return o;
  89. module.exports=o;
  90. });
  1. //调用
  2. require.async('heatForm', function(heatForm) {
  3. heatForm.bindEvent();
  4. })

4,AMD,CMD,UMD概览

一般 require 用于处理页面首屏所需要的模块,require.async 用于处理首屏外的按需模块。

  1. {script type="text/javascript"}
  2. // 同步调用 jquery
  3. var $ = require('common:widget/jquery/jquery.js');
  4. $('#btn').click(function() {
  5. // 异步调用 respClick 模块
  6. require.async(['/widget/ui/respClick/respClick.js'], function() {
  7. respClick.hello();
  8. });
  9. });
  10. {/script}

4.1 三者代码示例

AMD:

见上

CMD:
  1. //commonjs
  2. // 文件名: foo.js
  3. var $ = require('jquery');
  4. var _ = require('underscore');
  5. // methods
  6. function a(){}; // 私有方法,因为它没在module.exports中 (见下面)
  7. function b(){}; // 公共方法,因为它在module.exports中定义了
  8. function c(){}; // 公共方法,因为它在module.exports中定义了
  9. // 暴露公共方法
  10. module.exports = {
  11. b: b,
  12. c: c
  13. };

此处需要注意exports是module.exports的一个引用,如果直接使用

  1. exports = {
  2. ...
  3. }

暴露模块的输出的话,是无效的。因为此时相当于exports的引用对象更改了,两者并不指向同一个对象。module.exports指向的才是。

UMD:
  1. //UMD
  2. (function (root, factory) {
  3. if (typeof define === 'function' && define.amd) {
  4. // AMD
  5. define(['jquery', 'underscore'], factory);
  6. } else if (typeof exports === 'object') {
  7. // Node, CommonJS之类的
  8. module.exports = factory(require('jquery'), require('underscore'));
  9. } else {
  10. // 浏览器全局变量(root 即 window)
  11. root.returnExports = factory(root.jQuery, root._);
  12. }
  13. }(this, function ($, _) {
  14. // 方法
  15. function a(){}; // 私有方法,因为它没被返回 (见下面)
  16. function b(){}; // 公共方法,因为被返回了
  17. function c(){}; // 公共方法,因为被返回了
  18. // 暴露公共方法
  19. return {
  20. b: b,
  21. c: c
  22. }
  23. }));

函数有两个参数,第一个参数是当前运行时环境,第二个参数是模块的定义体。在执行UMD规范时,会优先判断是当前环境是否支持AMD环境,然后再检验是否支持CommonJS环境,否则认为当前环境为浏览器环境( window )。

4.2 从模块加载流程看amd,cmd区别

总结来说,cmd是异步加载、延迟且同步执行、依赖就近
amd是异步加载(下载)、异步执行、依赖前置。

(1)示例

  1. //AMD
  2. define(["./a", "./b"], function(a, b) {
  3. //BEGIN 1
  4. if (true) {
  5. a.doSomething();
  6. } else {
  7. b.doSomething();
  8. }
  9. //END
  10. });
  11. //CMD
  12. define(function(require) {
  13. // BEGIN 2
  14. if(some_condition) {
  15. require('./a').doSomething();
  16. } else {
  17. require('./b').soSomething();
  18. }
  19. // END
  20. });

在BEGIN1位置处a、b模块都需要被执行一次。CMD中BEGIN 2处a、b都没有被执行,在END处,a、b只有一个被实际执行过。这就是cmd所说的延迟加载。

(2)大致流程

以requirejs为例,其实amd,cmd的流程基本一样,区别在于后面的执行方面。
requirejs:用registry({id:module})来维持一个全局的模块资源表,保证不重复,每次根据id来查找,有则返回,无则去new Module。new Module的时候会触发Module.init函数,依次进行createScript,loading,将模块的依赖加入到依赖数组里,触发自己的completeLoad事件,在该事件中,依次去get依赖的Module,(在getModule时又会回到上面的步骤,有则返回,无则new Module).再接下来会做 checkLoaded,其中每隔50ms去checkoutLoadTimeoutId,因为模块是异步加载的,所以用这个来保证加载结束。 define函数中进行了兼容,包括无id,无依赖的,commonjs写法的(这种情况下同seajs一样,factory.toString()后正则匹配出依赖项)等多种情况。

(3)区别/相同点

注:
amd,cmd都是基于commonjs的,commonjs服务于服务端,cmd和amd作用于浏览器端,因为cmd中的require是同步执行,需要执行完才能执行下面的代码,对于浏览器端来说,是一个很大的性能问题,因为模块在服务器端,完全拼网速。

“ 因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。” --阮一峰

(4)补充
requirejs性能好点的原因:


cmd 中require的加载与执行的关系?require的时候实际上是已经加载好了,去执行 exec()

  1. seajs.require = function(id) {
  2. var mod = Module.get(Module.resolve(id))
  3. if (mod.status < STATUS.EXECUTING) {
  4. mod.onload()
  5. mod.exec()
  6. }
  7. return mod.exports
  8. }

补充知识

  1. 浏览器通常解析script的时候是同步下载、同步阻塞执行。
    除非手动设置了async,defer等属性
    具体总结来说

    • defer:
      异步下载、最后(document被解析之后)执行,仍然在DOMContentLoaded之前

    • async:
      异步下载后立刻异步执行(执行时可能页面还在解析,不block parse,顺序不定)
      且在window的load事件之前执行

    • 不设置:
      同步下载、同步执行,在页面继续解析之前,因此会阻塞

2,动态创建script标签并插入==设置为async


references

1.Asynchronous and deferred JavaScript execution explained
2. script的defer和async
3. 以代码爱好者角度来看AMD与CMD
4. seajs 源码解读


to think:

  1. es6中的module机制以及webpack的处理
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注