博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
解密javascript模块载入器require.js
阅读量:5858 次
发布时间:2019-06-19

本文共 12398 字,大约阅读时间需要 41 分钟。

require.config

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

// 定义config    req.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; } // 获取默认的context context = 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中定义configureconfigure: 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 bundles if (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;        ...        // 获取默认的context        context = 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中默认的Module                        requireMod = 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.load                    this.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; // 是浏览器还是Webworker if (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的src node.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所对应的Module context.completeLoad(data.id); } }
completeLoad: function (moduleName) {                var found, args, mod,                    shim = getOwn(config.shim, moduleName) || {},                    shExports = shim.exports;                // 将globalDefQueue合并到context.defQueue中                takeGlobalQueue();                // 找到moduleName所对应的的Module                while (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源代码代码规范、结构等感觉有点乱…
看过后仅仅想说”呵 呵”

转载地址:http://fgojx.baihongyu.com/

你可能感兴趣的文章
Oracle 好书 02 ( 安装oracle 10g软件及创建数据库 )
查看>>
CDN技术原理
查看>>
对Navicat Mac 模型进行偏好设置的教程
查看>>
白盒测试:为什么要做白盒测试
查看>>
HPU1460: 杨八方的表面兄弟
查看>>
第八届蓝桥杯第二题:等差素数列
查看>>
新浪微博信息站外同步的完整实现-转自 superfeeling
查看>>
Django注册页面配置设计
查看>>
exec、source以及bash的区别(zz)
查看>>
SenjuFamily项目总结 之 Activiti 学习总结(一)
查看>>
HDU 1019
查看>>
NO33 第6--7关题目讲解
查看>>
DBA的技能图谱
查看>>
Finding Correspondences lec4
查看>>
Jenkins+PowerShell持续集成环境搭建(八)邮件通知
查看>>
java学习笔记(Core Java)4 对象与类
查看>>
Java平台
查看>>
cocos2d-x mac or windows eclipse android ------ Eclipse工程里面还会有许多警告
查看>>
图像像素的获取和操作(第三天)
查看>>
hdu3639 强连通
查看>>