目录
1. requireJS概念
2. AMD与CMD区别
3. 模块加载器的来历
4. 模块加载器的利弊
5. 模块加载器的使用场景
6. requireJS源码解读
7.心得总结
一、requireJS概述
概述
RequireJS是一款遵循AMD规范协议的JavaScript模拟加载器。
业界常见模块加载器(labjs、requirejs、seajs)
LABjs的核心是LAB(Loading and Blocking):核心价值是性能优化
至于requirejs和seajs区别,知乎上有专门篇幅讲这个
作者玉伯-支付宝淘宝前端类库KISSY、seajs、arale创始人
地址呈上:http://www.zhihu.com/question/20342350
二、AMD与CMD区别
1.名词解释
AMD:Asynchronous Modules Definition异步模块定义,提供定义模块及异步加载该模块依赖的机制。
CMD:Common Module Definition 通用模块定义,提供模块定义及按需执行模块
2. 提前执行:提前异步并行加载
优点:尽早执行依赖可以尽早发现错误;缺点:容易产生浪费
3. 延迟执行:延迟按需加载
优点:减少资源浪费 缺点:等待时间长、出错时间延后
2.1 AMD与CMD代码模式
AMD代码模式-运行策略
define(['./a', './b'], function(a, b) { //运行至此,a.js和b.js已经下载完成 a模块和b模块已经执行完,直接可用;
a.doing();
// 此处省略500行代码
b.doing();
});
CMD代码模式-运行策略
define(function(require, exports, module) {
var a = require("./a"); //等待a.js下载、执行完
a.doing();
// 此处省略500行代码
var b = require("./b"); //依赖就近书写
b.doing();
});
三、模块加载器来历
web2.0的到来,页面的交互越来越强烈,逐渐变成“互联网应用程序”,js代码越来越庞大,越来越复杂,诞生很多库和框架,相互间存在依赖关系和加载顺序要求。面对错综复杂代码,攻城师不堪重负,迫切渴望解放代码各种关系。
3.1模块代码的演变过程
四、模块加载器的利与弊
五、模块加载器的适用场景
个人观点
requireJS比较适合独立APP型项目,能够做到统一构建的那种
seajs比较适合多个项目团队协议的工程,不能做到统一构建的那种
六、requireJS源码剖析
源码结构体:三部分
1、requireJS基本介绍
2、requireJS加载原理
3、requireJS整体框架流程图
4、requireJS源码解读(举一个define方法)
5、requireJS使用后几个思考
6.0.1 源码第一个部分组织结构
6.0.2 第二部分代码
6.0.3 第三部分代码结构
6.1:基本介绍
中文API参考地址:http://requirejs.cn/
特点:异步加载、模块化、依赖管理
a) 程序入口-脚本引入:
调试器显示
6.1.1:入口代码执行链路
源码执行链路:
if (isBrowser && !cfg.skipDataMain) { ... } // 这段代码的主要功能是找到绑定的data-main,然后往全局的cfg对象添加base路径和main。
=> eachReverse();
=> define = function(...) { ... };
=> define.amd = { ... };
=> req.exec = function(text) {... }
=>req(cfg);
代码几个细节点:
isBrowser:两个感叹号可以做类型转换->转换为布尔类型
!!(typeof window !== 'undefined' && typeof navigator !== 'undefine' && window.document)
cfg.skipDataMain:控制是否引用data-main属性数据
6.1.2:定义模块和引用模块
6.1.3:config配置
6.2 requireJS加载原理
1、载入模块
2、通过模块名解析出模块信息,以及计算出URL
3、通过创建SCRIPT的形式把模块加载到页面中。
4、判断被加载的脚本,如果发现它有依赖就去加载依赖模块。如果不依赖其它模块,就直接执行factory方法
5、等所有脚本都被加载完毕就执行加载完成之后的回调函数。
6.3 requireJS整体框架流程图
6.4 requireJS源码解读(举一个define方法)
define函数的作用
1、globalDefQueue只会有可能在define函数处被压入数据;
注:requireJS有两个队列 a)globalDefQueue 全局队列 b) defQueue: newContext闭包
2、调用依赖时候会根据define进行设置将加载好的标签引入键值对应关系;
3、每一个define模块都会维护一个闭包,而且多数时候这个闭包是无法释放的,面对这个问题,一般采用将大项目拆分,避免首次加载过多资源,防止内存占用过渡问题
4、define方法会根据键值改变对应模块的标识值
6.4.1 requireJS源码解读 define函数主结构体
6.4.2 requireJS源码解读 define函数源码
6.5 requireJS使用后的几个问题
1、使用require后页面引入的js两个data-*属性,有什么用
2、data-main是项目的唯一入口,但是怎么进入这个入口,之后又做了什么?
3、require和define里面怎么执行的,如何管理依赖关系?
4、require和define区别是什么?
5、如何异步加载js文件的?
6.5.1 问题1
第一个问题解答:使用require后页面引入的js两个data-*属性,有什么用
data-*:在浏览器中对脚本进行打tag,创建、移除脚本标签用到
6.5.2:data-main入口的具体实现
6.5.3:require和define怎么执行,如何管理依赖关系?
require方法:
1、检查依赖的模块,根据配置文件,获取js文件的实际路径
2、根据js文件实际路径,在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知。
3、依赖script全部加载完成后,调用回调函数
define方法:
1、每个define模块都会维护一个闭包
2、define几个关键判断点:
a) checkLoadedTimeoutId b) inCheckLoaded c) stillLoading
依赖关系管理:script标签执行onload事件将全局队列的东西载入context.defQueue,根据相关的映射id(由event参数获取),实例化相关模块
6.5.4:require和define的区别
功能区别:
define: 用来定义模块(注册为requirejs中模块)
require:用来加载和使用模块的
实现代码区别:
如果使用require定义模块,不能执行结果返回任何值,这意味着不能使用这个模块到其他模块
6.5.4:如何异步加载js
看一下req.createNode方法
req.createNode = function(config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true; // 异步
return node;
};
当node加载完毕后会调用context.onScriptLoad,做了实例化context.Module对象,并暴露给registry供调用
onScriptLoad: function(evt) {
if (evt.type === 'load' ||
(readyRegExp.test(evt.currentTarget || evt.srcElement).readyState)) {
interactiveScript = null;
// getScriptData()找evt对应的script,提取data-requiremodule的mod名称
var data = getScriptData(evt);
context.completeLoad(data.id);
}
};
七、总结
1、学习一个工具,先用熟练,之后开始思考是怎么实现
2、框架学习有助于学习大牛的代码组织、编码风格、设计思想,对提升帮助很大(就是内功修炼)
3、看源码大概流程 源码如何组织->debug理清调用链->参考别人分析->demo校验->总结