前言

经过昨天的学习,我们大概了解到了requireJS的主要结构,这里先大概的回顾一下

首先从总体结构来说,require这里分为三块:

① newContext之前变量声明或者一些工具函数

② newContext大家伙

③ 解析script标签抽出data-main,并提供几个对外接口

从流程上讲,大概发生了这些事情:

① script标签引入requireJS后,便会初始化一些变量以及函数,并不干实际的事情

② 在主干结构第三步之前会使用req({})调用(并且只会调用一次)newContext方法由此会维护一个newContext的闭包环境,之后的很多变量全在其中

③ 取出script标签中的data-main参数,做第一次简单参数处理(这里有两个全局变量contexts以及cfg比较重要)

在此调用req(cfg),这里便会进行实际的操作了

所以,关键还是第二次调用req(cfg),这里由涉及到很多细节的地方了,这里我们便可以关注requirejs方法的细节了:

① 这次调用首先会取出当前上下文环境,然后调用config方法设置属性,最后调用context.require进行实际操作

  1. context = getOwn(contexts, contextName);
  2. if (!context) {
  3. context = contexts[contextName] = req.s.newContext(contextName);
  4. }
  5. if (config) {
  6. context.configure(config);
  7. }
  8. return context.require(deps, callback, errback);

② 属性配置结束后便执行localRequire方法,这里会慢慢加载模块了,然后使用

  1. requireMod = getModule(makeModuleMap(null, relMap));

真正加载模块,具体里面真的干了什么,这就是我们今天要学习的内容了,于是我们继续吧

requireJS如何加载data-main文件

这里我们先干一件事情,搞清楚requireJS是如何将script标签中data-main对应的文件加载出来的,这一步搞懂了后,才能知道其它文件如何加载

首先,根据前面的学习,我们知道了

  1. req(cfg)
  2. =>
  3. context.require(config)
  4. =>
  5. context.makeRequire()
  6. =>
  7. localRequire()

而localRequire干了很多事情,我们这里暂时是不关注的,于是主要注意力一到了nextTick上面,于是最终进入了这里的核心getModule

getModule

  1. function getModule(depMap) {
  2. var id = depMap.id, mod = getOwn(registry, id);
  3. if (!mod) {
  4. mod = registry[id] = new context.Module(depMap);
  5. }
  6. return mod;
  7. }

我们前面说了registry里面存储着已经加载好了的模块,而一个模块加载后便不会再加载了,这里的唯一标识是makeModuleMap处理的,我们暂时不予关注

这里没有便会创建,而这里的创建module过程便是我们需要了解的主干流程

  1. mod = registry[id] = new context.Module(depMap);

context.Module

我们这里实例化了一个mod作为返回,并且将之存入了registry对象中,初始化过程中并未做什么特别的事情,但是该模块具备了大量实用方法

然后,将该mod返回给了我们的临时变量requireMod,并且调用了其初始化方法

  1. requireMod.init(deps, callback, errback, {
  2. enabled: true
  3. });

requireMod.init

这里开始了我们实际的模块初始化逻辑了

① 如果该模块已经初始化便不再执行下面逻辑

② 然后他会将依赖映射取出,这里为了保存原始数组的完整性还做了其它操作

  1. //Do a copy of the dependency array, so that
  2. //source inputs are not modified. For example
  3. //"shim" deps are passed in here directly, and
  4. //doing a direct modification of the depMaps array
  5. //would affect that config.
  6. this.depMaps = depMaps && depMaps.slice(0);

最后执行了module的enable方法,enable最后会调用this.check

requireMod.check

调用enable方法时,首先会存一个类似于registry的对象enabledRegistry,这个应该与依赖项什么的相关,比如backbone依赖于underscore,这里就应该先加载underscore

但是这里具体干了什么,我们先放一下,先跑主干流程,我们这里直接进入check

PS:这个enable很关键,我们后面一点详细来看看

进入check流程后,马上又跳至了this.fetch

这里首先记录了当前上下文环境的开始时间(此context是所有mod共享的)

  1. context.startTime = (new Date()).getTime();

然后回调用this.load方法,这个方法有可能要加载标签了,这里也是有点东西的,我们稍候再说

然后控制器又回到了context的load手里

  1. //Delegates to req.load. Broken out as a separate function to
  2. //allow overriding in the optimizer.
  3. load: function (id, url) {
  4. req.load(context, id, url);
  5. },
  6.  
  7. req.load = function (context, moduleName, url) {
  8. var config = (context && context.config) || {},
  9. node;
  10. if (isBrowser) {
  11. //In the browser so use a script tag
  12. node = req.createNode(config, moduleName, url);
  13.  
  14. node.setAttribute('data-requirecontext', context.contextName);
  15. node.setAttribute('data-requiremodule', moduleName);
  16.  
  17. if (node.attachEvent &&
  18. !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
  19. !isOpera) {
  20. useInteractive = true;
  21. node.attachEvent('onreadystatechange', context.onScriptLoad);
  22. } else {
  23. node.addEventListener('load', context.onScriptLoad, false);
  24. node.addEventListener('error', context.onScriptError, false);
  25. }
  26. node.src = url;
  27. currentlyAddingScript = node;
  28. if (baseElement) {
  29. head.insertBefore(node, baseElement);
  30. } else {
  31. head.appendChild(node);
  32. }
  33. currentlyAddingScript = null;
  34.  
  35. return node;
  36. } else if (isWebWorker) {
  37. try {
  38. importScripts(url);
  39. context.completeLoad(moduleName);
  40. } catch (e) {
  41. context.onError(makeError('importscripts',
  42. 'importScripts failed for ' +
  43. moduleName + ' at ' + url,
  44. e,
  45. [moduleName]));
  46. }
  47. }
  48. };

这里做了很多兼容性处理,我们直接抽取主干逻辑即可,这里是重头戏了:

① 使用createNode创建script标签

  1. req.createNode = function (config, moduleName, url) {
  2. var node = config.xhtml ?
  3. document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
  4. document.createElement('script');
  5. node.type = config.scriptType || 'text/javascript';
  6. node.charset = 'utf-8';
  7. node.async = true;
  8. return node;
  9. };

这里创建标签后给其注入了一些自定义属性,并且绑定了一个事件(这里各个浏览器可能不同,我们关注标准的)

  1. node.addEventListener('load', context.onScriptLoad, false);
  1. onScriptLoad: function (evt) {
  2. if (evt.type === 'load' || (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
  3. interactiveScript = null;
  4. var data = getScriptData(evt);
  5. context.completeLoad(data.id);
  6. }
  7. },

然后将该节点插入head中

  1. head.appendChild(node);

于是头部多出了这么一块东西

此段js加载结束后马上执行调用onScriptLoad方法,但是,我们写在main.js中的逻辑会被调用,该函数暂时看来是执行一些资源清理工作

并且会将该节点存储起来

PS:注意啦,这里可能会有一定时序性问题,整个这块逻辑我们还需要整理

看到这里,我们基本明白了,我们第一个文件,main.js是如何加载出来的了

一个简单的例子

为了帮助理解,我们这里以一个简单的例子做说明,再看看源码的实现

HTML

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <title></title>
  4. </head>
  5. <body>
  6. <script src="require.js" type="text/javascript" data-main="main.js"></script>
  7. </body>
  8. </html>

main.js

  1. require.config({
  2. baseUrl: '',
  3. paths: {
  4. 'nameDep': 'js/nameDep',
  5. 'say': 'js/say',
  6. 'name': 'js/name'
  7. },
  8. shim: {
  9. 'name': {
  10. deps: ['nameDep']
  11. }
  12. }
  13. });
  14. require(['name', 'say'], function (name, say) {
  15. say(name);
  16. });

name/say

  1. //name
    define([''], function () {
  2. return '测试';
  3. });
  4.  
  5. //say
  6. define([], function () {
  7. return function (name) {
  8. console.log(name);
  9. };
  10. });

由上面的逻辑来看,当main.js加载结束后于是就该进行下一个逻辑的处理,我们这里看看他是如何处理的呢,这个时候第一个入口便是require.config了

require.config

这个时候会传一些有意义的参数进来了

整个config依旧在做参数设置,这个这个操作实际的意义是为newContext中的config赋值,并在localRequire中使用

接下来顺理成章的再次进入了模块实例化的步骤

  1. requireMod = getModule(makeModuleMap(null, relMap));

最后进入上面提到的初始化环节,但是内部会有不一样的细节处理(我们暂时不予关注,留着下次学习)这里每一次加载会清除前面的registry

config的设置并不会触发script标签的加载,所以其实际加载还是下面的require,这块的逻辑又有点小复杂了......

PS:这里设置断点调试时序上好像有点问题,所以这块暂时就不处理了,应该与其维护的队列有关,留待下次解决吧

结语

通过这两天的学习,我们大概了解了requireJS的一些东西,但是还是那句话,小钗感觉requireJS读起来还是有点小难,不是那么好吸收

关于他的学习,还需要慢慢来哦,感觉里面水有点深......

【requireJS源码学习02】data-main加载的实现的更多相关文章

  1. spring源码学习之bean的加载(二)

    这是接着上篇继续写bean的加载过程,好像是有点太多了,因为bean的加载过程是很复杂的,要处理的情况有很多,继续... 7.创建bean 常规的bean的创建时通过doCreateBean方法来实现 ...

  2. spring源码学习之bean的加载(一)

    对XML文件的解析基本上已经大致的走了一遍,虽然没有能吸收多少,但是脑子中总是有些印象的,接下来看下spring中的bean的加载,这个比xml解析复杂的多.这个加载,在我们使用的时候基本上是:Bea ...

  3. spring源码学习之bean的加载(三)

    接着二中的继续写,那个都超过1000行了,哈,需要重新写一个,要不太长了,我都看不下去了 7.4 初始化bean doCreateBean函数中有这样一行代码:这行代码中initializeBean函 ...

  4. ThinkPHP5.0源码学习之注册自动加载

    ThinkPHP5框架的自动注册加载流程如下:

  5. wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...

  6. Java虚拟机JVM学习02 类的加载概述

    Java虚拟机JVM学习02 类的加载概述 类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对 ...

  7. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

  8. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

  9. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

随机推荐

  1. sizzle编译函数

    一个人去完成一件事情,如果派多个人去做的话,只要配合默契,效率比一个人做肯定要高,效率提高,所需的时间就减少了.如果只能一个人完成,那么必须设法提高自己的劳动效率,这个提高可以是量的改变也可以是质的改 ...

  2. jQuery 2.0.3 源码分析 回调对象 - Callbacks

    源码API:http://api.jquery.com/jQuery.Callbacks/ jQuery.Callbacks()是在版本1.7中新加入的.它是一个多用途的回调函数列表对象,提供了一种强 ...

  3. ES6 - Note1:块级作用域与常量

    在ES6以前,ES不支持块级作用域,只有全局作用域和函数作用域,所有变量的声明都存在变量声明提升. 1.let 关键字 声明一个块级变量,只在一个代码块中有效,如果在块外面访问便会报错,如下所示: { ...

  4. [c++] Class

    也是醉了,一个.h文件就有这么多细节问题: 初始化列表,使用{} 也可以. 类中的引用和const变量,必须立即在初始化列表中提前初始化. 常成员函数,const 放在函数后, 常成员函数即不能改变成 ...

  5. Masonry 当需要把某个控件进行隐藏的时候有警告的解决方案

    //查看全文 [self.moreBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo (self.conten ...

  6. 为什么WebSphere好好的,他就不干活了?

    “修理不好用的WebSphere,有时候要看运气.”这个是我接触过很过有历史的运维工程师经常说的一个梗;研发人员也经常说这个程序在我这里运行好好的,怎么到你那就不灵了?问题是你的,你自己解决. 声明一 ...

  7. java多线程--线程池的使用

    程序启动一个新线程的成本是很高的,因为涉及到要和操作系统进行交互,而使用线程池可以很好的提高性能,尤其是程序中当需要创建大量生存期很短的线程时,应该优先考虑使用线程池. 线程池的每一个线程执行完毕后, ...

  8. 重温JSP学习笔记--El表达式

    el表达式是jsp内置的表达式语言,jsp从jsp2.0开始,就不再提倡使用java脚本,而是用el表达式和动态标签来替代,而el表达式主要替代的是jsp中的<%=....%>,也就是说e ...

  9. Maven工程引入jar包

    Maven项目引入jar包的方法 法一.手动导入:项目右键—>Build Path—>Configure Build Path—>选中Libraries—>点击Add Exte ...

  10. Android测试提升效率批处理脚本(二)

    前言: 前面放出过一次批处理,本次再放出一些比较有用的批处理(获得当前包名.查看APP签名信息等),好长时没来写博客了,简单化,请看正文,更多脚本尽请期待~~~(不定期) 目录 1.[手机录屏(安卓4 ...