js模块加载详解
看着java中各种import加载,在回过头来看看javascript还在自己造轮子,写各种XX的模块加载框架,ECMASCRIPT6不知什么时候能够普及。不过DT归DT,该学的还是要学。
一 同步加载模式(SMD)
同步顾名思义就是按顺序依次加载执行,比如A模块要引用B模块中的某些函数完成事情,那么此时B模块必须是已经存在页面内存中的,A调用顺利完成执行下面的操作。例子就是A模块直接调用document对象,因为document早已存在浏览器内存中。
同步模块代码实现也比较简单,解析模块路径,执行会掉函数,调用模块
定义一个模块
var manger = (function(){
var F = F || {};
F.define = function(str, fn){
var parts = str.split('.'),
old = parent = this,//old保存祖父模块,parent保存当前模块的父模块
i = len = 0;
for(len = parts.length; i < len; i++){
if(typeof parent[parts[i]] === 'undefined'){
parent[parts[i]] = {};
}
old = parent;
parent = parent[parts[i]];
}
if(fn){
old[parts[--i]] = fn();
}
return F;
}
})();
上面代码中,define和module分别是模块的定义和调用,模块被定义在闭包F上,通过getF()调用,这里用了old和parent2个对象是因为要保存当前模块的父级模块和祖父模块缓存,为了在依次添加完模块后,可以执行回调函数到当前模块上。
这里的回调函数也就是一个js文件中要写的关于模块的构建,比如下面代码,包含了构造函数,以及静态方法;
manger.getF().define('dom', function(){
var dom = function(id){
return document.getElementById(id);
}
dom.html = function(html){
return this.innerHTML;
}
return dom;
});
调用一个模块
F.module = function(mod,callback){
var args = [].slice.call(arguments),
fn = args.pop(),
parts = args[0] && args[0] instanceof Array?args[0]:args,
modules = [],
modIds = '',
i = 0,
ilen = parts.length,
parent, j, jlen;
while(i < ilen){
if(typeof parts[i] === 'string'){
parent = this;//this是模块缓存器
modIds = parts[i].replace('/^F\./', '').split('.');//modIds=[]
for(j = 0, jlen = modIds.length; j < jlen; j++){
parent = parent[modIds[j]] || false;
}
modules.push(parent);//将模块对象装入modules中,在回调函数中应用
}else{
modules.push(parts[i]);
}
i++;
}
fn.apply(null, modules);
}
通过将模块对象引入到回调函数中来执行模块的调用,关键是解析模块路径,把需要加载的模块都放入到modules数组中实现。例子如下
manger.getF().module(['dom'],function(dom){
dom('div1');
});
二 异步加载模式
前言
从上面的同步加载模式来看,模块开始就被加载到了内存中,没有异步在“运行时”动态绑定script标签来加载模块,那么问题来了如何引用别人没有写完的js模块文件,这就产生了一个异步加载问题,当前各大模块加载框架都是这么干的
集定义和调用于一身的模块加载器
F.module = function(url, modDeps, modCallback){
var args = [].slice.call(arguments);
var callback = args.pop();
var deps = (args.length && (args[args.length - 1] instanceof Array)) ? args
.pop() : [];
var url = args.length ? args.pop() : null,
params = [], // 依赖模块序列,这个参数比较重要,它存放了模块依赖对象
depsCount = 0, // 依赖模块计数器用来等待加载依赖模块
i = 0;// 依赖模块下标
var len;
if (len = deps.length) {
while(i<len){//依次加载依赖模块
(function(i){//这个函数在定义的时候已经被执行了,所以首次加载一个模块会执行这个函数,而不是setModule
depsCount++;
loadModule(deps[i],function(mod){
params[i] = mod;//构造函数创建的模块对象传递给params【i】
depsCount--;
if(depsCount===0){//等待所有的依赖模块都加载到内存中,才一次性修改该模块的属性
setModule(url,params,callback);
}
});
})(i);
/*depsCount++;
loadModule(deps[i],function(mod){
params[i] = mod;//构造函数创建的模块对象传递给params【i】
depsCount--;
if(depsCount===0){//等待所有的依赖模块都加载到内存中,才一次性修改该模块的属性
setModule(url,params,callback);
}
});*/
i++;
}
}else{//这是定义一个没有依赖的模块,直接执行回调函数
setModule(url,[],callback);//setModule('lib/event',[],fn)
}
}
上面代码中关键的一点就是用到了闭包来保存i,如果不使用闭包,i的值只会是最后依赖模块的数量,而不是每次的结果。关于闭包问题可以看我的博客。
代码中用到了2个重要的函数就是loadModule和setModule,下面对这2个代码一些解释
loadModule = function(name, callback){
var module; if (moduleCache[name]) {//模块已经在内存中
_module = moduleCache[name];
if (_module.status = 'loaded') {//模块加载完成
//如果模块已经加载到页面中,立即执行模块的构造函数,并且将构造函数创建的模块对象传递给params[i]
setTimeout(callback(_module.exports), 0);
} else {//模块加载完,但是还未执行其中的回调函数,此时只是将回调函数放入到onload数组中去,没有执行回调
_module.onload.push(callback);
}
} else {//首次加载该模块,设置模块的状态等等,onload只是一个含有一个元素的数组,存放了回调函数的引用,但是没有执行回调函数
moduleCache[name] = {
name : name,
status : 'loading',
exports : null,
onload : [ callback ]
};
loadScript(getUrl(name));//将js文件加载到内存中
}
for ( var i in moduleCache) {
console.log(moduleCache[i]);
}
};
loadModule函数加载模块的时候分为3种情况分别是,在代码注释中也写了,关键的一点就是,在js模块中,如果js文件的状态是“loaded”那么立即执行callback回调函数,这里使用了settimeout延迟为0毫秒的作用不一定是立即执行,而是等到下一个trick才执行,在前端可以理解为立即执行,并且callback函数给他传递了这个模块的对外接口,这里指的是创建模块时候函数return的对象。
// 修正js文件,并且执行js的回调函数,params是回调函数的参数,也就是所有依赖模块对象
setModule = function(name, params, callback){
var _module, fn;
if (moduleCache[name]) {//模块(要执行的模块)缓存中已经存在了该模块,当前页面中已经加载了js文件
_module = moduleCache[name];
_module.status = 'loaded';
_module.exports = callback ? callback.apply(_module, params) : null;//exports中存放着该模块的方法
while (fn = _module.onload.shift()) {
console.log('模块回调函数:'+fn);
console.log('模块接口:'+_module.exports);
for(var i in _module.exports){
console.log(i);
}
fn(_module.exports);
}
} else {//匿名模块,直接执行回调函数,也就是模块的调用
console.log('匿名模块:'+callback);
for(var i in params){
console.log('参数'+i+':'+params[i]);
if(typeof params[i]==='object'){
for(var j in params[i]){
console.log(j+':'+params[i][j]);
}
}
}
callback && callback.apply(null, params);
}
},
从F.module可以看到,只有在依赖模块都加载到内存中才使用setModule来修正模块,该函数的作用是将模块属性(moduleName,status,exports,onload)都修正为加载完成后的状态,并且执行回调函数。这里执行的回调函数和loadModule执行的回调函数不同,loadModule是要执行依赖模块的会掉,而这里是执行最终模块的回调。
这样,一个完整的模块加载器就实现了,并且上面的异步加载器类似于RequireJS ,因为在有关于依赖的时候是提前定义的,并且是一旦定义就必须加载,不符合就近声明原则,所以有待优化的地方,还望批评指正。
js模块加载详解的更多相关文章
- 转:web前端面试题合集 (Javascript相关)(js异步加载详解)
1. HTTP协议的状态消息都有哪些? 1**:请求收到,继续处理2**:操作成功收到,分析.接受3**:完成此请求必须进一步处理4**:请求包含一个错误语法或不能完成5**:服务器执行一个完全有效请 ...
- Javascript 异步加载详解
Javascript 异步加载详解 本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy ...
- nodejs js模块加载
本文地址:http://www.cnblogs.com/jasonxuli/p/4381747.html nodejs的非核心模块(core module)加载主要使用的就是module.js. 项目 ...
- js模块化/js模块加载器/js模块打包器
之前对这几个概念一直记得很模糊,也无法用自己的语言表达出来,今天看了大神的文章,尝试根据自己的理解总结一下,算是一篇读后感. 大神的文章:http://www.css88.com/archives/7 ...
- js模块加载之AMD和CMD
当我写这篇文章的时候,sea.js已经逐渐退出历史的舞台,详细链接.不过任何新事物的出现都是对旧事物的取其精华,去其糟粕,所以了解一下以前模块的加载也是一件好事. js模块化的原因自不比多说,看看HU ...
- sea.js模块加载工具
seajs的使用 seajs是一个jS模块加载器,由淘宝前端架构师玉伯开发,它可以解决命名空间污染,文件依赖的问题.可以在一个js文件中引入另外一个js.require('a.js') 1.安装 np ...
- 实现简单的 JS 模块加载器
实现简单的 JS 模块加载器 1. 背景介绍 按需加载是前端性能优化的一个重要手段,按需加载的本质是从远程服务器加载一段JS代码(这里主要讨论JS,CSS或者其他资源大同小异),该JS代码就是一个模块 ...
- 彻底搞清楚javascript中的require、import和export(js模块加载规范的前世今生)
为什么有模块概念 理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. 但是,Javascript不是一种模块化编程语言,在es6以前,它是不支持”类”(class),所以也 ...
- 第三课:sea.js模块加载原理
模块加载,其实就是把js分成很多个模块,便于开发和维护.因此加载很多js模块的时候,需要动态的加载,以便提高用户体验. 在介绍模块加载库之前,先介绍一个方法. 动态加载js方法: function l ...
随机推荐
- canvas——随机生成迷宫
先上图. 效果 代码 随机生成迷宫要求任意两点都能够找到相同的路径,也就是说,迷宫是一个连通图.随机生成迷宫可以使用普里姆算法.广度优先算法.深度优先算法等实现.这里将使用普里姆算法通过生成最小数的方 ...
- C语言指针声明探秘
C语言指针声明探秘
- 什么时候该选用Xamarin?
现在开发app也好,桌面程序也好,有很多不同的技术可以选择.那么,什么时候应该选用Xamarin呢? 5 questions to consider before choosing Xamarin 1 ...
- 如何在office2007中插入MathType教学
很多人在安装MathType数学公式编辑器时可能会遇到这个问题,MathType安装好了,可是在office2007的菜单栏中没有MathType这个选项卡,也就是说MathType没有成功加载在of ...
- Linux命令 查看及修改文件属性
chmod [功能说明] 改变文件的访问权限 #Linux中访问权限分为:文件属主(文件的创建者)文件组属主(创建者所处的组)和其他(其他用户) [语法格式] Chmod[参数]mode[文件名或目 ...
- 字符串距离 简单DP
字符串距离 时间限制: 1 Sec 内存限制: 128 MB 题目描述 设有字符串 X,我们称在 X 的头尾及中间插入任意多个空格后构成的新字符串为 X 的扩展串,如字符串 X 为"abc ...
- CSS 中的内联元素、块级元素、display的各个属性的特点
CSS的内联元素和块级元素 块级元素<h1>-<h6>.p.dt是不可以内联块级元素的 1.block和inline这两个概念是简略的说法,完整确切的说应该是 block-le ...
- Linux: 安装NVIDIA显卡驱动
Linux(Fedora25, 64bit)台式机配备了NVIDIA显卡GTX950,但是仅仅使用开源驱动nouveau,无法发挥NVIDIA显卡的性能,所以可以考虑使用官方提供的显卡驱动. # 先安 ...
- CSS(3)实现水平垂直居中效果
CSS实现水平垂直居中对齐 在CSS中实现水平居中,会比较简单.常见的,如果想实现inline元素或者inline-block元素水平居中,可以在其父级块级元素上设置text-align: cente ...
- Petya勒索病毒疫苗出现,分分钟让电脑对病毒免疫
继wannacry之后,Petya勒索软件攻击再次席卷全球,对欧洲.俄罗斯等多国政府.银行.电力系统.通讯系统.企业以及机场造成了不同程度的影响. 研究发现,Petya 会锁定磁盘的 MFT 和 MB ...