@lizlalala
2016-08-22T10:57:04.000000Z
字数 9113
阅读 1467
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.js
define('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');
} else
intervals = 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:45
function 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;
}
//时间段的下拉菜单html
function generateRangeHtml(size) {
var totalMinutesOneDay = 1440,
durationsArr = [],
intervals;
if (size === 0) {
size = 1;
intervals = moment().diff(moment({
h: 0,
m: 0
}), 'm') + 1;
} else
intervals = 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>`
} else
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='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"}
// 同步调用 jquery
var $ = require('common:widget/jquery/jquery.js');
$('#btn').click(function() {
// 异步调用 respClick 模块
require.async(['/widget/ui/respClick/respClick.js'], function() {
respClick.hello();
});
});
{/script}
见上
//commonjs
// 文件名: foo.js
var $ = require('jquery');
var _ = require('underscore');
// methods
function 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) {
// AMD
define(['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)示例
//AMD
define(["./a", "./b"], function(a, b) {
//BEGIN 1
if (true) {
a.doSomething();
} else {
b.doSomething();
}
//END
});
//CMD
define(function(require) {
// BEGIN 2
if(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 源码解读