requireJS源码分析

目录

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区别

requireJS源码分析_第1张图片


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();
});

三、模块加载器来历

requireJS源码分析_第2张图片

web2.0的到来,页面的交互越来越强烈,逐渐变成“互联网应用程序”,js代码越来越庞大,越来越复杂,诞生很多库和框架,相互间存在依赖关系和加载顺序要求。面对错综复杂代码,攻城师不堪重负,迫切渴望解放代码各种关系。


3.1模块代码的演变过程

requireJS源码分析_第3张图片


四、模块加载器的利与弊

requireJS源码分析_第4张图片


五、模块加载器的适用场景

个人观点

requireJS比较适合独立APP型项目,能够做到统一构建的那种
seajs比较适合多个项目团队协议的工程,不能做到统一构建的那种


六、requireJS源码剖析

源码结构体:三部分


1、requireJS基本介绍
2、requireJS加载原理
3、requireJS整体框架流程图
4、requireJS源码解读(举一个define方法)
5、requireJS使用后几个思考

6.0.1 源码第一个部分组织结构

requireJS源码分析_第5张图片

6.0.2 第二部分代码

requireJS源码分析_第6张图片

6.0.3 第三部分代码结构

requireJS源码分析_第7张图片

6.1:基本介绍

中文API参考地址:http://requirejs.cn/
     特点:异步加载、模块化、依赖管理
     a) 程序入口-脚本引入:
     调试器显示

requireJS源码分析_第8张图片

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:定义模块和引用模块

requireJS源码分析_第9张图片

requireJS源码分析_第10张图片

6.1.3:config配置

requireJS源码分析_第11张图片

requireJS源码分析_第12张图片

6.2 requireJS加载原理

1、载入模块
2、通过模块名解析出模块信息,以及计算出URL
3、通过创建SCRIPT的形式把模块加载到页面中。
4、判断被加载的脚本,如果发现它有依赖就去加载依赖模块。如果不依赖其它模块,就直接执行factory方法
5、等所有脚本都被加载完毕就执行加载完成之后的回调函数。

6.3 requireJS整体框架流程图

requireJS源码分析_第13张图片

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函数主结构体


requireJS源码分析_第14张图片


6.4.2 requireJS源码解读 define函数源码

requireJS源码分析_第15张图片

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,创建、移除脚本标签用到

requireJS源码分析_第16张图片

6.5.2:data-main入口的具体实现

requireJS源码分析_第17张图片


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校验->总结






你可能感兴趣的:(前端架构)