js与AMD模块加载
目的:
了解AMD规范与CMD规范,写一个模块加载器雏形。
基本概念:
AMD是异步模块定义规范,而CMD是通用模块定义规范。其他的还有CommonJS Modules规范。
对于具体的规范,可以参考:
https://github.com/amdjs/amdjs-api/wiki/AMD AMD规范
https://github.com/seajs/seajs/issues/242 CMD规范
http://www.zhihu.com/question/20351507/answer/14859415 玉伯对于AMD和CMD规范的回答
无论是哪一种规范,都是为了解决前端模块依赖与加载的问题。
与后台语言不同的地方是,js并无法做到阻塞的模块/文件加载,这是我们需要这些模块加载、依赖管理工具的重要原因之一。
需求分析:
根据规范,我们需要实现require和define两个接口,define接口用于定义模块(并加载依赖),require接口用来加载模块并回调。
我们将实现简化的接口:
define
define(deps, definition):依赖数组 与 模块定义函数
define(definition):模块定义函数(模块无依赖)
require
require(deps, callback):依赖数组 与 完成回调
require(moduleId):传入模块id,获得模块实例(模块需提前通过define或require完成加载)
接下来我们进一步分析下任务:
代码(模块)状态: 未加载 -> 加载中 -> 加载完毕,等待依赖解决 -> 依赖完成,模块加载完成 ( -> 初次使用,执行模块定义生成模块实例) 非模块代码无依赖,加载后直接跳到完成状态,并且无模块定义无法生成模块实例 模块的加载时间线: 开始加载,添加加载完成的回调 -> 加载完成,执行define函数,注册模块依赖与模块定义 -> 执行代码加载完成回调(从这里拿到代码的url,或者说是模块ID),解析模块 我们需要在几个情况下分析依赖是否满足: 代码加载完成时以及模块依赖满足,状态为完成时 依赖满足的条件: 所有依赖的模块状态为完成
对于js的加载,我们创建script标签,在其上保存模块的id,并添加加载完成的时间侦听:
var scriptNode = document.createElement('script');
scriptNode.async = true;
scriptNode.moduleId = mId;
scriptNode.addEventListener('load', onScriptLoaded);
scriptNode.src = mId + '.js';
document.body.appendChild(scriptNode);
最终代码:
load.js:
(function (global) {
var modules = {}, // 模块列表
_modulesToLoad = [], // 从依赖中获得的要加载的js列表
MODULE_STATUS = { // 定义模块状态
LOADING: 1,
PENDING: 2,
DONE: 3
},
_modulesToHandler = [], // define函数运行时存入这个数组(在这里每次只有一个被存入),在onScriptLoaded中处理
_requireCallbacks = []; // 待满足的依赖,每一项格式为[deps数组,回调函数]
// 处理依赖,得到要加载的模块数组,并建立LOADING的模块状态
function addDepsToModules (deps) {
if (!deps) { return; }
deps.forEach(function (mId) {
if (!modules[mId]) {
modules[mId] = {
id: mId,
status: MODULE_STATUS.LOADING,
deps: null,
defi: null,
instance: null
}
_modulesToLoad.push(mId);
}
});
}
// 加载代码,在script标签节点上保存模块id
function loadScripts () {
var mId;
while(mId = _modulesToLoad.pop()) {
var scriptNode = document.createElement('script');
scriptNode.async = true;
scriptNode.moduleId = mId;
scriptNode.addEventListener('load', onScriptLoaded);
scriptNode.src = mId + '.js';
document.body.appendChild(scriptNode);
}
}
function onScriptLoaded (evt) {
if (evt.type === 'load') {
var scriptNode = evt.currentTarget,
mId = scriptNode.moduleId;
if (modules[mId]) { // 不处理作为data-main的入口代码
if (_modulesToHandler.length === 0) { // 加载的并非模块代码(没有调用define),立即修改状态为DONE
modules[mId].status = MODULE_STATUS.DONE;
} else {
var pair = _modulesToHandler.pop(),
deps = pair[0],
defi = pair[1];
modules[mId].status = MODULE_STATUS.PENDING; // 修改状态问PENDING
modules[mId].defi = defi; // 保存模块定义
_requireCallbacks.push([deps, function () { // 建立待满足的依赖
modules[mId].status = MODULE_STATUS.DONE; // 依赖满足后修改状态为DONE
setTimeout(resolveRequireCallbacks, 0); // 并重新寻找已满足的依赖
}]);
addDepsToModules(deps); // 处理这个模块的依赖
loadScripts(); // 加载依赖的js
}
resolveRequireCallbacks(); // 寻找已满足的依赖
}
}
}
// 寻找并处理已满足的依赖
function resolveRequireCallbacks () {
_requireCallbacks = _requireCallbacks.filter(function (pair) {
var deps = pair[0],
callback = pair[1],
allLoaded = true;
// 判断依赖是否都已满足
deps && deps.some(function (mId) {
if (modules[mId].status !== MODULE_STATUS.DONE) {
allLoaded = false;
return true;
}
});
if (allLoaded) { // 如果满足执行回调,并剔除出待满足的依赖列表
callback();
return false;
} else {
return true;
}
});
}
// 定义require函数
global.require = function (deps, callback) {
if (typeof deps === 'string') { // 传入moduleId时返回模块实例
if (modules[deps] && modules[deps].status === MODULE_STATUS.DONE) {
if (!modules[deps].instance) { // 模块实例在第一次require(mId)时通过模块定义创建
modules[deps].instance = modules[deps].defi();
}
return modules[deps].instance;
} else {
throw 'Module ['+deps+'] not loaded yet.';
}
}
_requireCallbacks.push([deps, callback]); // 添加回调函数到待解决的依赖
addDepsToModules(deps); // 寻找要加载的js
loadScripts(); // 加载js
resolveRequireCallbacks(); // 处理待满足的依赖
}
// 定义define函数
global.define = function (deps, defi) {
if (!defi) {
defi = deps;
deps = null;
}
// 将依赖、模块定义存入待处理数组,由onScriptLoaded处理
_modulesToHandler.push([deps, defi]);
}
// 获得并加载main入口js
var scripts = document.getElementsByTagName('script'),
selfScript = scripts[scripts.length - 1], // 当前代码,也就是指向load.js的这个
mainSrc = selfScript.getAttribute('data-main');
global.onload = function () { // 等待body完成创建,应当在window.onload前就执行,这里就直接用window.onload了
_modulesToLoad.push(mainSrc);
loadScripts();
}
})(window);
html:
<script data-main="main" src="load.js"></script>
模块文件:
// main.js
require(['d', 'd0'], function() {
console.log('ready', require('d').mId, require('d0').mId);
});
// d.js
define(['d1', 'd2'], function () {
return {mId: 'd' + require('d1').token};
});
// d0.js
define(['d1', 'd2'], function () {
return {mId: 'd0' + require('d1').token};
});
// d1.js
define(function () {
return {token: '|xyz123'};
});
// d2.js
console.log('d2');
可以做的改进:
1. 依赖锁死的情况,比如模块依赖自身,模块循环依赖的情况,改如何处理。
2. 单个文件中多个define的调用,调用形式为define(模块id,模块依赖,模块定义),这样做是为了使得多个模块代码可以合并到单文件中。
3. 更智能的模块依赖分析。
对于这个第三点,我们来做个小尝试:
function func (jquery, underscore) {
var async = require('async');
}
func.toString();
我们可以看到利用function的toString方法,通过“分析代码”,我们可以从模块定义函数“智能”的分析出依赖,无论是angular的依赖注入还是sea.js的CMD编写格式,就都可以做到了。
扩展:
除了require.js之外,browserify和webpack都是采用了CommonJS规范,这样的好处是与nodejs以及ES6的模块规范保持一致。
browserify:http://browserify.org/
webpack:http://webpack.github.io/
与require不同,这两种工具都要求代码提前通过编译转换,特别是browserify,生成的文件添加的代码相对较少,加之本身丰富的功能,很适合单页面应用和多页面的项目,但这两者的定位并非解决动态加载和相应的模块依赖。
对于相对小型或简单的项目,webpack是很好的选择,一是配置简单,二是代码配重小。但对于大型的单页面应用,当我们需要动态加载大量模块时,require可以压缩“静态依赖”/框架代码,并动态加载模块代码,因此仍然是非常好的选择之一。
js与AMD模块加载的更多相关文章
- 一个简单的AMD模块加载器
一个简单的AMD模块加载器 参考 https://github.com/JsAaron/NodeJs-Demo/tree/master/require PS Aaron大大的比我的完整 PS 这不是一 ...
- javascript Date 日期格式化 formatDate, require.js 模块 支持全局js引入 / amd方式加载
* 引入AMD加载方式: require.js CDN https://cdn.bootcss.com/require.js/2.3.5/require.js * 创建模块文件./js/util/d ...
- JavaScript AMD 模块加载器原理与实现
关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...
- 对于requirejs AMD模块加载的理解
个人人为使用模块化加载的优点有三: 1,以我的项目为例:90%为图表展示,使用的是echarts,此文件较大,requirejs可以在同一个版本号(urlArgs)之下缓存文件,那么我就可以在访问登陆 ...
- 【模块化编程】理解requireJS-实现一个简单的模块加载器
在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题: ① 解决单文件变量命名冲突问题 ② 解决前端多人协作问题 ③ 解决文件依赖问题 ④ 按需加载(这个说法其实很假了) ⑤ ..... ...
- Dojo初探之1:AMD规范,编写符合AMD规范(异步模块加载机制)的模块化JS(其中dojo采用1.11.2版本)
一.AMD规范探索 1.AMD规范(即异步模块加载机制) 我们在接触js的时候,一般都是通过各种function来定义一些方法,让它们帮我们做一些事情,一个js可以包含很多个js,而这些functio ...
- js模块加载之AMD和CMD
当我写这篇文章的时候,sea.js已经逐渐退出历史的舞台,详细链接.不过任何新事物的出现都是对旧事物的取其精华,去其糟粕,所以了解一下以前模块的加载也是一件好事. js模块化的原因自不比多说,看看HU ...
- js模块定义——支持CMD&AMD&直接加载
/* animate */ //直接加载 (function() { var animate = {} //balabala window.animate = animate; })(); //AMD ...
- 如何使用 require.js ,实现js文件的异步加载,避免网页失去响应,管理模块之间的依赖性,便于代码的编写和维护。
一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...
随机推荐
- apk反汇编之smali语法
类型 Dalvik的字节码中拥有两个主要的类型:基类和引用类型.引用类型 引用类型是对象和数组,其他的一切都是基类 基类被一个简单的字符描述.我没有提出这些缩写词———他们实际以字符串的形式存储于 ...
- bootstrap中可用的图标集
- Email:2017
Hi, 2017,我对自己有一个小小的期望:写写文字,安安心. Enjoy a simple life. 如我所愿吧! 明年再来问候你.
- Hibernate-----关系映射 重点!!!
1. 关系, 指的是对象之间的关系, 并不是数据库之间的关系. 2. 简化问题: a) 怎么写annotation b) 增删改查CRUD怎么写 对象处于下列关系之一 (一对一, 一对多, 多对多)时 ...
- php CI 实战教程:如何去掉index.php目录
Windows下自由创建.htaccess文件的N种方法 .htaccess是apache的访问控制文件,apache中httpd.conf的选项配合此文件,完美实现了目录.站点的访问控制,当然最多的 ...
- HDU 5650 so easy
n不为1的时候输出a[1],否则输出0 #include<cstdio> #include<cstring> #include<cmath> #include< ...
- 升级apache
升级Apache到最新版本,本来并不复杂,但是因为涉及到不能停止现有的Apache实例的运行,因此要小心翼翼的做. 大致分成三步: 编译新的Apache, 配置新的Apache, 替换旧的Apache ...
- 2016年最全面的VR资源盘点,不只有VR视频播放器还有具体到步骤的VR资源
2016年过去了,有多少人开始使用VR来观看我们喜欢的视频资源呢?比传统视频更高的沉浸感,甚至在VR眼镜的视角中,自己仿佛化生成视频中的主角一般.然而,这种体验只有VR眼镜还是不行的,还需要有一个VR ...
- 学习wcf
链接请看下面 第一部分:http://boytnt.blog.51cto.com/966121/796884 第二部分:http://boytnt.blog.51cto.com/966121/7969 ...
- iOS开发——关于开发者账号引发的血案
这里不介绍怎么申请开发者账号,那个网上的教程太多了.这里讲点有意思的. 如果你们公司比较,怎么说呢,呵呵?管理层不懂开发,不管事,申请开发者账号的人员又比较小白,或者别有用心,用私人邮箱来申请,申请的 ...