require.config

require.config设置require.js模板载入选项

    // 定义configreq.config = function (config) {return req(config);};
// 载入config配置项
req = requirejs = function (deps, callback, errback, optional) {var context, config,contextName = defContextName;// 假设deps是config对象if (!isArray(deps) && typeof deps !== 'string') {config = deps;if (isArray(callback)) {deps = callback;callback = errback;errback = optional;} else {deps = [];}}if (config && config.context) {contextName = config.context;}// 获取默认的contextcontext = getOwn(contexts, contextName);if (!context) {context = contexts[contextName] = req.s.newContext(contextName);}// 配置模块载入选项if (config) {context.configure(config);}// 当deps为config时,此调用木有实质作用return context.require(deps, callback, errback);};
// newContext中定义configure
configure: function (cfg) {// 确保baseUrl以/结尾if (cfg.baseUrl) {if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {cfg.baseUrl += '/';}}var shim = config.shim,objs = {paths: true,bundles: true,config: true,map: true};// 将cfg中的配置信息合并到默认的context的config中eachProp(cfg, function (value, prop) {if (objs[prop]) {if (!config[prop]) {config[prop] = {};}mixin(config[prop], value, true, true);} else {config[prop] = value;}});//Reverse map the bundlesif (cfg.bundles) {eachProp(cfg.bundles, function (value, prop) {each(value, function (v) {if (v !== prop) {bundlesMap[v] = prop;}});});}// 对shim进行对应处理保存于默认context的config中if (cfg.shim) {eachProp(cfg.shim, function (value, id) {// 规范化shim结构// e.g. jquery-datepicker: ['jquery'] -> jquery-datepicker: {deps: ['jquery']}if (isArray(value)) {value = {deps: value};}// value.exports: 非requirejs定义文件的载入modulename,用于推断是否载入成功// value.init: 是否存在初始化工作if ((value.exports || value.init) && !value.exportsFn) {value.exportsFn = context.makeShimExports(value);}shim[id] = value;});config.shim = shim;}...}

综上:require.config将模块载入选项进行对应处理后,保存于默认的context.config中

require()

require()载入模块文件,
e.g.

require(['jquery'], function() {...
});
req = requirejs = function (deps, callback, errback, optional) {var context, config,contextName = defContextName;...// 获取默认的contextcontext = getOwn(contexts, contextName);if (!context) {context = contexts[contextName] = req.s.newContext(contextName);}...// 通过环境变量载入模块,newContext倒数几行代码中写到context.require = context.makeRequire()return context.require(deps, callback, errback);};
makeRequire: function (relMap, options) {options = options || {};function localRequire(deps, callback, errback) {var id, map, requireMod;...context.nextTick(function () {// 将之前已define()定义的模块载入进来intakeDefines();// 创建require中默认的ModulerequireMod = getModule(makeModuleMap(null, relMap));...// 初始化require信息,载入deps依赖项 requireMod.init(deps, callback, errback, {enabled: true});// 根据config中waitSeconds来检查js载入是否超时。waitSeconds为0,无载入超时机制// checkLoaded採用setTimeout,若未载入完即50ms后检查再次调用checkLoaded推断是否载入完成checkLoaded();});return localRequire;}
// Module中init的定义
init: function (depMaps, factory, errback, options) {...if (options.enabled || this.enabled) {// 将deps依赖项进行处理变为Module,进行载入this.enable();} else {this.check();}}
enable: function () {// 对depMaps的全部依赖项进行处理each(this.depMaps, bind(this, function (depMap, i) {var id, mod, handler;if (typeof depMap === 'string') {...on(depMap, 'defined', bind(this, function (depExports) {if (this.undefed) {return;}// 跟进defineDep能够看到。将dep.exports保存在this.depExports中。用于载入后回调函数的參数传递,这是按顺序来的,回调函数调用详见以下check()中this.defineDep(i, depExports);this.check();}));...// 检查每一个Module是否初始化、载入完模块this.check();}
check: function () {if (!this.enabled || this.enabling) {return;}var err, cjsModule,id = this.map.id,depExports = this.depExports,exports = this.exports,factory = this.factory;if (!this.inited) {// 获取模块所对应的js文件,源代码跟踪fetch()发现实际是调用了context.load即req.loadthis.fetch();} else if (this.error) {this.emit('error', this.error);} else if (!this.defining) {// deps依赖项已载入完成。准备回调if (this.depCount < 1 && !this.defined) {if (isFunction(factory)) {// 通过context.execCb调用回调函数,将刚保存的depExports的參数传入回调函数中。完成模块的依赖注入if ((this.events.error && this.map.isDefine) ||req.onError !== defaultOnError) {try {exports = context.execCb(id, factory, depExports, exports);} catch (e) {err = e;}} else {exports = context.execCb(id, factory, depExports, exports);}    }...
req.load = function (context, moduleName, url) {var config = (context && context.config) || {},node;// 是浏览器还是Webworkerif (isBrowser) {// 通过 document.createElement('script')创建script节点,同一时候设置node.async = true实现异步载入node = req.createNode(config, moduleName, url);node.setAttribute('data-requirecontext', context.contextName);node.setAttribute('data-requiremodule', moduleName);// 根据的浏览器的不同加入script的load、error事件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);}// 设置script的srcnode.src = url;currentlyAddingScript = node;if (baseElement) {head.insertBefore(node, baseElement);} else {head.appendChild(node);}currentlyAddingScript = null;return node;} else if (isWebWorker) {try {// WebWorker採用importScripts载入js文件importScripts(url);context.completeLoad(moduleName);} catch (e) {context.onError(makeError('importscripts','importScripts failed for ' +moduleName + ' at ' + url,e,[moduleName]));}}}

综上:require()就是对须要的依赖模块进行对应的url、是否在path、或存在shim等的处理后转换为Module。

载入js文件有以下两种方式:
Browser:document.createElement创建script,然后通过设置asyn=true。head.appendChild(node)
WebWorker:importScripts(url)

define()

define = function (name, deps, callback) {var node, context;// 对实參不同情况做对应处理...if (!deps && isFunction(callback)) {deps = [];// 通过toString()然后正則表達式将define()定义的依赖项保存在deps中if (callback.length) {callback.toString().replace(commentRegExp, '').replace(cjsRequireRegExp, function (match, dep) {deps.push(dep);});...}}...// 将define定义的信息存放在默认context.defQueue或globalDefQueue中,等定义define()的js文件载入完后。在通过来处理这些define信息// 上面require()中js文件载入过程分析。可知js文件载入后将会调用事件函数onScriptLoad(context ? context.defQueue : globalDefQueue).push([name, deps, callback]);}
onScriptLoad: function (evt) {// 对文件载入的状态进行推断if (evt.type === 'load' ||(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {interactiveScript = null;// 将script的加入的事件函数进行清除,同一时候获取node的信息var data = getScriptData(evt);// 通过默认context来载入该data所对应的Modulecontext.completeLoad(data.id);}}
completeLoad: function (moduleName) {var found, args, mod,shim = getOwn(config.shim, moduleName) || {},shExports = shim.exports;// 将globalDefQueue合并到context.defQueue中takeGlobalQueue();// 找到moduleName所对应的的Modulewhile (defQueue.length) {args = defQueue.shift();if (args[0] === null) {args[0] = moduleName;if (found) {break;}found = true;} else if (args[0] === moduleName) {found = true;}// 获取args模块callGetModule(args);}...}
function callGetModule(args) {// 若该模块没有被载入。即载入该模块,然后调用Module.init(),这个过程在require()已分析,即初始化、载入模块了if (!hasProp(defined, args[0])) {getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);}}

综上:define()模块定义,仅将模块信息载入到默认context.defQueue或者globalDefQueue中,而处理这些信息是在定义define()的js文件载入完后进行的。

总体过程

  • 载入require.js库文件,生产默认的context
  • 查找data-main入口点,require.config()将模块载入选项做对应处理,然后加入默认的context中
  • 载入require()依赖项,通过document.createElement和设置asyn或者importScripts来载入js文件
  • 载入过程中发现define()模块定义。将模块定义信息载入到默认的context.defQueue或者globalDefQueue中,等定义define()的js文件载入后再处理这些模块定义信息

    当中:

  • js文件若未载入成功,採用setTimeout 50ms方式来循环推断是否载入完成

  • define()的依赖项提取採用function.toString() + 正則表達式
  • url的转换、Module的生产等处理过程可详见源代码

require.js源代码解密完成,是不是认为原来就这样实现的= =
事实上非常多事情并没有那么难。仅仅要你去分析它就会清楚非常多

小小吐槽:
require.js源代码代码规范、结构等感觉有点乱…
看过后仅仅想说”呵 呵”

解密javascript模块载入器require.js相关推荐

  1. JavaScript模块打包器rollup

    学习资料:拉勾课程<大前端高薪训练营> 阅读建议:搭配文章的侧边栏目录进行食用,体验会更佳哦. 内容说明:本文不做知识点的搬运工,技术详情请查看官方文档. 一:认识rollup rollu ...

  2. Javascript模块化编程(三):require.js的用法

    这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战. 我采用的是一个非常流行的库require.js. 一.为什么要用require.js? 最早的 ...

  3. [技巧] Javascript模块化编程(三):require.js的用法

    这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战. 我采用的是一个非常流行的库require.js. 一.为什么要用require.js? 最早的 ...

  4. python模块捆绑器组件_让我们学习模块捆绑器如何工作,然后自己编写

    python模块捆绑器组件 by Adam Kelly 通过亚当凯利 让我们学习模块捆绑器如何工作,然后自己编写 (Let's learn how module bundlers work and t ...

  5. Require.js的基本用法详解

    一:什么是require.js ①:require.js是一个js脚本加载器,它遵循AMD(Asynchronous Module Definition)规范,实现js脚本的异步加载,不阻塞页面的渲染 ...

  6. require.js基本用法

    1.require.js的加载 使用require.js的第一步,是先去官方网站下载最新版本. 下载后,假定把它放在js子目录下面,就可以加载了. 1 <script src="js/ ...

  7. require.js的用法

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

  8. require.js用法简介

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

  9. JS模块化编程require.js简介

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

  10. require.js的基本用法

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

最新文章

  1. Java服务端人脸识别实战开发优化
  2. angularjs(1)
  3. ROS教程(3)---静态NAT配置及应用 (
  4. 代码审计——命令执行
  5. 服务器建网站www无法访问,IIS6服务器搭建网站无法访问解决方案总结_DOIT.com.cn...
  6. Hadoop伪分布安装详解(一)
  7. mybatis中的mapper代理模式的数据传参的应用
  8. java string返回_老生常谈Java String字符串(必看篇)
  9. linux下封装命令,linux系统怎么封装
  10. HTML实现三级导航菜单栏
  11. 从零实现一个3D建模软件
  12. 华硕路由域名访问_“618” WiFi6 路由器选购推荐清单_路由器
  13. (玩转zabbix)硬盘硬件健康状态监控,部件寿命监控
  14. forEach、for…in、 for…of 的区别
  15. Qt串口等接口数据协议传输时的字节拼接处理
  16. python queue 查询是否在队列中_Python queue队列
  17. 可扩展标记语言XML之一:XML的概念、作用与示例
  18. 将中文的0-9数字编码转化为ascii
  19. java堆栈、gc、dump文件在线分析
  20. c蔚语言艺术,伊能静的语言艺术,写下老公和好友章子怡,网友称赞

热门文章

  1. Atitit.dart语言的特性  编译时js语言大总结
  2. paip.flex or Silverlight
  3. 资管新政:银行资产管理业务的转型与发展 -中国银行刘东海
  4. 国产Linux二十年揭秘
  5. Apache基金会获得Pineapple基金88.34比特币捐赠
  6. canvas贝塞尔曲线爱心_HTML5 Canvas 绘制贝塞尔曲线 Bezier and quadratic curves
  7. 动态规划: 数字三角形
  8. 中南大学计算机学院伍逸凡,关于公布2017年湖南省大学生力学竞赛等15项学科竞赛结果的通知...
  9. java nextDouble exception_java 控制台输入输出 nextdouble问题
  10. java zip_Java压缩技术(二) ZIP压缩——Java原生实现