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代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...
随机推荐
- 近期用过的Linux口令备份
最近使用Ubuntu用到的一些口令: rm -rf directory 移除文件夹或者文件touch (create file)mkdirmv(move,rename)chown (change ow ...
- SqlServer批量导入
SQL Server的Bulk Insert语句可以将本地或远程的数据文件批量导入到数据库中,速度非常的快.远程文件必须共享才行,文件路径须使用通用约定(UNC)名称,即"\\服务器名或IP ...
- Qt5:窗口居中显示
QDesktopWidget* desktop = QApplication::desktop(); // =qApp->desktop();也可以move((desktop->width ...
- string数组转化成int数组
string idsStr = "1,2,3,4,5"; int[] ids = idsStr.Split(',').Select(Int32.Parse).ToArray();
- base库插件---form
$().extend('serialize', function () { for (var i = 0; i < this.elements.length; i ++) { var form ...
- mysql优化---优化sql
一.通过show status和应用特点了解各种SQL的执行频率 通过SHOW STATUS可以提供服务器状态信息,也可以使用mysqladmin extended-status命令获得.SHOW S ...
- pcap filter
今天用tshark抓包,本以为wireshark能用的filter,如“mysql”它也应该能用,其实不然:tshark -f只认识pcap filter,用-R的话,说要用-2,加上-2的话又说什么 ...
- 考分鄙视(exam)
考分鄙视(exam) 题目描述 Whence这个学期考了n次试,每一次都有一个0-20000之间的整数分数.Whence本来的状态应该是每一次考试都比前一次多一分(除第一次),但由于他很不稳定,偏差可 ...
- CSS实现三角形方法一--rotate+relative
方法说明:两个正方形,一个小的,一个大的,将大的正方向进行旋转,然后移动到小的正方形的合适位置,覆盖小正方形的一部分,使小正方形剩余部分为三角形,再把大正方形的背景色改为浏览器窗口的颜色. 用到知识: ...
- php中serialize、unserialize与json_encode、json_decode比较
性能比较 同一个变量编码或解码10000次,每个函数执行10000次所需时间 php5.2.13 json : 190 serialize : 257 json_encode : 0.08364200 ...