zoukankan      html  css  js  c++  java
  • 解密javascript模块载入器require.js

    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中定义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 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源代码代码规范、结构等感觉有点乱…
    看过后仅仅想说”呵 呵”

  • 相关阅读:
    Asp.net导出Excel文件
    Ext.Net 使用总结之GridPanel中的选中行
    Ext.Net 使用总结之查询条件中的起始日期
    使用python crontab设置linux定时任务
    JSON Serialization/Deserialization in C#
    redis 基本数据类型-列表(List)
    redis 基本数据类型-字符串(String)
    使同一个server上不同port的django应用可在同一个浏览器上打开
    django server之间通过remote user 相互调用
    使用python实现短信PDU编码
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/5202760.html
Copyright © 2011-2022 走看看