【requireJS源码学习02】data-main加载的实现

时间:2022-08-23 16:15:26

前言

经过昨天的学习,我们大概了解到了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进行实际操作

context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
}
if (config) {
context.configure(config);
}
return context.require(deps, callback, errback);

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

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

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

requireJS如何加载data-main文件

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

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

req(cfg)
=>
context.require(config)
=>
context.makeRequire()
=>
localRequire()

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

getModule

function getModule(depMap) {
var id = depMap.id, mod = getOwn(registry, id);
if (!mod) {
mod = registry[id] = new context.Module(depMap);
}
return mod;
}

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

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

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

context.Module

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

【requireJS源码学习02】data-main加载的实现

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

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

requireMod.init

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

【requireJS源码学习02】data-main加载的实现

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

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

//Do a copy of the dependency array, so that
//source inputs are not modified. For example
//"shim" deps are passed in here directly, and
//doing a direct modification of the depMaps array
//would affect that config.
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共享的)

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

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

【requireJS源码学习02】data-main加载的实现

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

//Delegates to req.load. Broken out as a separate function to
//allow overriding in the optimizer.
load: function (id, url) {
req.load(context, id, url);
}, req.load = function (context, moduleName, url) {
var config = (context && context.config) || {},
node;
if (isBrowser) {
//In the browser so use a script tag
node = req.createNode(config, moduleName, url); node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName); if (node.attachEvent &&
!(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
!isOpera) {
useInteractive = true;
node.attachEvent('onreadystatechange', context.onScriptLoad);
} else {
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
}
node.src = url;
currentlyAddingScript = node;
if (baseElement) {
head.insertBefore(node, baseElement);
} else {
head.appendChild(node);
}
currentlyAddingScript = null; return node;
} else if (isWebWorker) {
try {
importScripts(url);
context.completeLoad(moduleName);
} catch (e) {
context.onError(makeError('importscripts',
'importScripts failed for ' +
moduleName + ' at ' + url,
e,
[moduleName]));
}
}
};

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

① 使用createNode创建script标签

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

【requireJS源码学习02】data-main加载的实现

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

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

然后将该节点插入head中

head.appendChild(node);

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

【requireJS源码学习02】data-main加载的实现

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

并且会将该节点存储起来

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

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

一个简单的例子

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

HTML

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<script src="require.js" type="text/javascript" data-main="main.js"></script>
</body>
</html>

main.js

require.config({
baseUrl: '',
paths: {
'nameDep': 'js/nameDep',
'say': 'js/say',
'name': 'js/name'
},
shim: {
'name': {
deps: ['nameDep']
}
}
});
require(['name', 'say'], function (name, say) {
say(name);
});

name/say

//name
define([''], function () {
return '测试';
}); //say
define([], function () {
return function (name) {
console.log(name);
};
});

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

require.config

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

【requireJS源码学习02】data-main加载的实现

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

【requireJS源码学习02】data-main加载的实现

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

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

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

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

【requireJS源码学习02】data-main加载的实现

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&period;0源码学习之注册自动加载

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

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

    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. tableView显示第一个cell有偏移问题

    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 0 ...

  2. Android中用双缓存技术&comma;加载网络图片

    最近在学校参加一个比赛,写的一个Android应用,里面要加载大量的网络图片,可是用传统的方法图片一多就会造成程序出现内存溢出而崩溃.因为自己也在学习中,所以看了很多博客和视频,然后参照这些大神的写源 ...

  3. Unity3D 使用 UI 的 Grid Layout Group 组件。

    1.首先创建一个容器,用于存放列表项的内容. 这里使用 Panel 来做为容器. 这里要注意! “Grid Layout Group”是要增加在容器的游戏对象里. 同时,只有容器对象的子对象才有排列效 ...

  4. Linux 服务器如何禁止 ping 以及开启 ping

    Linux 默认是允许 ping 响应的,也就是说 ping 是开启的,但 ping 有可能是网络攻击的开始之处,所以关闭 ping 可以提高服务器的安全系数.系统是否允许 ping 由2个因素决定的 ...

  5. BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)

    题目大意:有一些位置.这些位置上能够放若干个数字. 如今有两种操作. 1.在区间l到r上加入一个数字x 2.求出l到r上的第k大的数字是什么 思路:这样的题一看就是树套树,关键是怎么套,怎么写.(话说 ...

  6. 我的django之旅(一)

    我的django之旅(一) 标签(空格分隔):django web 1.检验我们的python和django版本 liao@spring ~ $ python --version Python 2.7 ...

  7. 利用MySQL触发器实现check和assertion

    MySQL虽然输入check语句不会报错,但是实际上并没有check的功能.但是MySQL 依然可以利用触发器来实现相应功能. 本文将根据两个例子简要阐述MySQL实现check和assertion的 ...

  8. 【SVN】SVN初识

    SVN 介绍 SVN是Subversion的简称,是一个开放源代码的版本控制系统,相较于RCS.CVS,它采用了分支管理系统,它的设计目标就是取代CVS.互联网上很多版本控制服务已从CVS迁移到Sub ...

  9. python 数据库连接及操作

    Python DB-API使用流程: 引入API模块. 获取与数据库的连接. 执行SQL语句和存储过程. 关闭数据库连接. def mysql_dbtest(): config = { 'host': ...

  10. C&plus;&plus;中 char &ast;s 和 char s&lbrack;&rsqb; 的区别

    原因 刚好看到给main传递参数,书上(C++Primer)说“ int main(int argc, char *argv[])也可以写成 int main(int argc, char **arg ...