@lizlalala
2016-08-22T02:57:04.000000Z
字数 9113
阅读 1730
requirejs 模块化
define跟require的区别在于一个是用于定义模块的,一个是使用已定义的模块的。
//test.html<!DOCTYPE><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>requirejs初览</title></head><body><div class="filter-item"><label for="productLine">业务线</label><select id="productLine" class="mis-select"><option value="kuaiche">快车</option><option value="sfc">顺风车</option></select></div><div class="filter-item"><label for="target">指标</label><select id="target" class="mis-select"><option value="need_num" selected="selected" class="need_num">需求数</option><option value="call_num">呼叫量</option></select></div><div class="filter-item"><label for="particleSize">粒度</label><select id="particleSize" class="mis-select"><option value="1">1分钟</option><option value="5">5分钟</option></select></div><div class="filter-item"><label for="hourRange">区间</label><select id="hourRange" class="mis-select"><option value="00">00:00-01:00</option><option value="01">01:00-02:00</option></select></div></body><script src="./require.js"></script>//这边也可以直接设置data-main作为入口,举个栗子,test作为入口的话,把config设置放进去,然后直接require moment跟jquery,定义回调函数就可以,<script >require.config({paths:{heatForm:"./test",moment:"./moment",jquery:"./jquery"}});require(['heatForm'],function(heatForm){heatForm.bindEvent();})</script></html>
//test.jsdefine('heatForm', ['jquery','moment'], function($,moment) {var o = {};var target = $("#target"); //指标var productLine = $("#productLine");var particleSize = $("#particleSize");var hourRange = $("#hourRange");var searchBtn = $("#searchBtn");function generateRangeHtml(size) {var totalMinutesOneDay = 1440,durationsArr = [],intervals;if (size === 0) {size = 1;intervals = moment().diff(moment({h: 0,m: 0}), 'm');} elseintervals = totalMinutesOneDay / size;for (var i = 0; i < intervals; i++) {var durations = i * size;var h = Math.floor(durations / 60);var m = durations - h * 60;var startTime = moment({hour: h,minute: m});var endTime = moment({hour: h,minute: m}).add(size, 'm');durationsArr.push({startTime: startTime.format('HH:mm'),endTime: endTime.format("HH:mm")});}var newOptions = durationsArr.map(function(one) {return `<option value=${one.startTime}>${one.startTime}-${one.endTime}</option>`;}).join("");return newOptions;}function bindEvent() {particleSize.on("change", function(event) {var size = parseInt($(this).val());var newRangeOptions = generateRangeHtml(size);hourRange.html(newRangeOptions);});productLine.on("change", function(event) {var product = $(this).val();if (product === "sfc") {target.html("<option value='call_num'>呼叫量</option>");}});}o.bindEvent = bindEvent;return o;});
实习的过程中最痛心的就是遗留代码的加功能点...之前的fe是用的旧版本的fis中的mod.js,定义了citylist组件,导致页面上要用到这一部分就得使用mod中的require。
然后比较神奇的有这么几点
require函数的大体思想就是,定义一个全局对象,然后把引入的js文件作为一个一个的key,value存进去,每次进require函数,就去找这个对象,如果没有,就添加,如果有,就更新,保证全局唯一,某种程度上避免了不同模块是不同的fe写的,然后重复引用js的问题。但是比较坑爹的是,需要手动加script标签引入...如此反人类...当然也提供了require.async来异步引入,这个函数中它是可以createScript的,也就是说,你只要提供url,它会在head中 append script元素。
还有一点注意的是,require做了部分兼容,就是如果传给他的是一个数组对象,那么它会调用require.async。
关于类比require中的path,mod里面也有resourceMap进行处理,
require.resourceMap({res:{'cityList':{url:'/static/js/app/module_cityList.js'},'echarts':{url:'/static/hotmap/js/libs/echarts/echarts.common.js'},'moment':{url:"/static/hotmap/js/libs/moment/moment.js"},'BMap':{url:'http://api.map.baidu.com/api?v=2.0&ak=8WEtlKYLwwssirUarD5O7ba0.js'},'heatForm':{url:"/static/hotmap/js/components/formRelated.js"},},'HeatmapOverlay':{deps:['BMap'], //**类似于shim**url:'http://api.map.baidu.com/library/Heatmap/2.0/src/Heatmap_min.js'}}});
eg.
var moment = require(['moment']);
并不是我们通常意义上理解的模块、同步、引入。= =
demo如图。
其中,heatform的文件映射见上
define('heatForm',function(require,exports, module){var moment = require(['moment']);var $ = require(["jquery"]);var o = {};var target = $("#target"); //指标var productLine = $("#productLine");var particleSize = $("#particleSize");var hourRange = $("#hourRange");var searchBtn = $("#searchBtn");//对应于size的当前时间的format,如粒度为15时,现在7:35,output:7:30-7:45function getCurStart(size) {if (size == 0) size = 1;var intervals = moment().diff(moment({h: 0,m: 0}), 'm');var result = moment({ h: 0, m: 0 }).add(Math.floor(intervals / size) * size, 'm').format("HH:mm");return result;}//时间段的下拉菜单htmlfunction generateRangeHtml(size) {var totalMinutesOneDay = 1440,durationsArr = [],intervals;if (size === 0) {size = 1;intervals = moment().diff(moment({h: 0,m: 0}), 'm') + 1;} elseintervals = totalMinutesOneDay / size;for (var i = 0; i < intervals; i++) {var durations = i * size;var h = Math.floor(durations / 60);var m = durations - h * 60;var startTime = moment({hour: h,minute: m});var endTime = moment({hour: h,minute: m}).add(size, 'm');durationsArr.push({startTime: startTime.format('HH:mm'),endTime: endTime.format("HH:mm")});}var curTime = getCurStart(size);var newOptions = durationsArr.map(function(one) {if (one.startTime === curTime) {return `<option selected=${"selected"} value=${one.startTime}>${one.startTime}-${one.endTime}</option>`} elsereturn `<option value=${one.startTime}>${one.startTime}-${one.endTime}</option>`;}).join("");return newOptions;}function bindEvent() {particleSize.on("change", function(event) {var size = parseInt($(this).val());var newRangeOptions = generateRangeHtml(size);hourRange.html(newRangeOptions);});productLine.on("change", function(event) {var product = $(this).val();if (product === "sfc") {target.html("<option value='callNum'>呼叫量</option>");}else{target.html(["<option value='needNum'>需求数</option>","<option value='callNum'>呼叫量</option>"].join(""));}});target.on("change",function(event){var $this = $(this);var sfcOption = "<option value='sfc'>顺风车</option>"if(($this).val()==="needNum"){productLine.children(".sfc").remove();}else{if(productLine.children(".sfc")===-1)productLine.append(sfcOption);}});//待加入searchBtn的click事件}o.bindEvent = bindEvent;// return o;module.exports=o;});
//调用require.async('heatForm', function(heatForm) {heatForm.bindEvent();})
一般 require 用于处理页面首屏所需要的模块,require.async 用于处理首屏外的按需模块。
{script type="text/javascript"}// 同步调用 jqueryvar $ = require('common:widget/jquery/jquery.js');$('#btn').click(function() {// 异步调用 respClick 模块require.async(['/widget/ui/respClick/respClick.js'], function() {respClick.hello();});});{/script}
见上
//commonjs// 文件名: foo.jsvar $ = require('jquery');var _ = require('underscore');// methodsfunction a(){}; // 私有方法,因为它没在module.exports中 (见下面)function b(){}; // 公共方法,因为它在module.exports中定义了function c(){}; // 公共方法,因为它在module.exports中定义了// 暴露公共方法module.exports = {b: b,c: c};
此处需要注意exports是module.exports的一个引用,如果直接使用
exports = {...}
暴露模块的输出的话,是无效的。因为此时相当于exports的引用对象更改了,两者并不指向同一个对象。module.exports指向的才是。
//UMD(function (root, factory) {if (typeof define === 'function' && define.amd) {// AMDdefine(['jquery', 'underscore'], factory);} else if (typeof exports === 'object') {// Node, CommonJS之类的module.exports = factory(require('jquery'), require('underscore'));} else {// 浏览器全局变量(root 即 window)root.returnExports = factory(root.jQuery, root._);}}(this, function ($, _) {// 方法function a(){}; // 私有方法,因为它没被返回 (见下面)function b(){}; // 公共方法,因为被返回了function c(){}; // 公共方法,因为被返回了// 暴露公共方法return {b: b,c: c}}));
函数有两个参数,第一个参数是当前运行时环境,第二个参数是模块的定义体。在执行UMD规范时,会优先判断是当前环境是否支持AMD环境,然后再检验是否支持CommonJS环境,否则认为当前环境为浏览器环境( window )。
总结来说,cmd是异步加载、延迟且同步执行、依赖就近
amd是异步加载(下载)、异步执行、依赖前置。
(1)示例
//AMDdefine(["./a", "./b"], function(a, b) {//BEGIN 1if (true) {a.doSomething();} else {b.doSomething();}//END});//CMDdefine(function(require) {// BEGIN 2if(some_condition) {require('./a').doSomething();} else {require('./b').soSomething();}// END});
在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()
seajs.require = function(id) {var mod = Module.get(Module.resolve(id))if (mod.status < STATUS.EXECUTING) {mod.onload()mod.exec()}return mod.exports}
浏览器通常解析script的时候是同步下载、同步阻塞执行。
除非手动设置了async,defer等属性
具体总结来说

defer:
异步下载、最后(document被解析之后)执行,仍然在DOMContentLoaded之前
async:
异步下载后立刻异步执行(执行时可能页面还在解析,不block parse,顺序不定)
且在window的load事件之前执行
不设置:
同步下载、同步执行,在页面继续解析之前,因此会阻塞
2,动态创建script标签并插入==设置为async
1.Asynchronous and deferred JavaScript execution explained
2. script的defer和async
3. 以代码爱好者角度来看AMD与CMD
4. seajs 源码解读