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代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...
 
随机推荐
- (转)Hadoop之常见错误集锦
			
Hadoop之常见错误集锦 下文中没有特殊说明,环境都是CentOS下Hadoop 2.2.0.1.伪分布模式下执行start-dfs.sh脚本启动HDFS时出现如下错误: ...
 - 磁盘寻道时间算法之----------------SCAN算法和最短寻道时间优先调度算法
			
若干个等待访问磁盘者依次要访问的柱面编号为:80,40,74,14,60,31,61,假设每移动一个柱面需要4毫秒时间,移动到当前位于35号柱面,且当前的移动方向向柱面号增加的方向.请计算: (1)若 ...
 - js中焦点的含义是什么
			
焦点即是光标 焦点是在页面上屏幕中闪动的的小竖线,鼠标点击可获得光标,Tab键可按照设置的Tabindex切换焦点
 - Java   IO整理
			
参考博客:http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html Java IO体系结构 1.要弄清楚其体系结构,先明白 ...
 - 支持持久化的内存数据库-----Redis
			
一.Redis概述 1.1.什么是Redis Redis是一种高级key-value数据库.它跟memcached类似,不过数据 可以持久化,而且支持的数据类型很丰富.有字符串,链表,集 合和有序集合 ...
 - Tasks and Back stack 详解
			
原文地址:http://developer.android.com/guide/components/tasks-and-back-stack.html 一个应用往往包含很多activities.每个 ...
 - vector与ArrayList、hashmap与hashtable区别
			
一.vector与ArrayList区别 首先要说明的是vector和arraylist都是list的实现类,都是代表链表的数据结构. java.util.Vector; 类中 pa ...
 - 安装PIL遇到的问题
			
配置:Win7 64位 不过折腾到最后,没有使用PIL,官方的PIL很久木有更新了,换了Pillow,是PIL的衍生吧,一直有更新,但是两者不可在同一环境共存. 1 Python version 2. ...
 - ZOJ 3930 Dice Notation
			
简单模拟题.一个int写成了char,搞了4个多小时.真垃圾.. #include<stdio.h> #include<string.h> +],s[+]; +]; +]; i ...
 - iOS 添加导航按钮
			
iOS设置导航按钮navigationBar中包含了这几个重要组成部分:leftBarButtonItem, rightBarButtonItem, backBarButtonItem, title. ...