requirejs源碼解析之運行流程

說明:
內容主要包括三部分:
1.按源碼的結構順序 對 全部的變量及方法的說明
2.requirejs運行流程
三、流程相關圖片node

1、源碼的結構

爲了方便比對源碼,按源碼的結構順序展現。數組

var requirejs, require, define;
(function (global, setTimeout) {
    var req, s, head, baseElement, dataMain, src,
        interactiveScript, currentlyAddingScript, mainScript, subPath,
        version = '2.3.5',
        commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/mg,            //去除註釋
        cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,//提取require函數的arguments
        jsSuffixRegExp = /\.js$/,
        currDirRegExp = /^\.\//,
        op = Object.prototype,
        ostring = op.toString,
        hasOwn = op.hasOwnProperty,
        isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document),
        isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
        readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
                      /^complete$/ : /^(complete|loaded)$/,
        defContextName = '_',
        isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
        contexts = {},
        cfg = {},
        globalDefQueue = [],
        useInteractive = false;
    //返回singlePrefix或空
    function commentReplace(match, singlePrefix) {}
    //判斷函數
    function isFunction(it) {}
    //判斷數組
    function isArray(it) {}
    //執行函數func(ary[i], i, ary);返回真值,跳出循環
    function each(ary, func) {}
    //與each序列反
    function eachReverse(ary, func) {}
    //判斷obj是否有prop
    function hasProp(obj, prop) {}
    //返回obj上的prop
    function getOwn(obj, prop) {}
    //循環調用func(obj[prop], prop);返回真值,跳出循環
    function eachProp(obj, func) {}
    //混合source屬性值(target沒有同名的)到target
    //force爲真,target同名覆蓋,deepStringMixin爲真,深混合
    function mixin(target, source, force, deepStringMixin) {}
    //返回逆名函數,執行爲obj調用fn函數
    function bind(obj, fn) {}
    //返回script元素的集合
    function scripts() {}
    //throw err;
    function defaultOnError(err) {}
    //例getGlobal("aa.bb");爲global.aa.bb
    function getGlobal(value) {}
    //生成一個錯誤
    function makeError(id, msg, err, requireModules) {}
    if (typeof define !== 'undefined') {
        return;
    }
    if (typeof requirejs !== 'undefined') {
        if (isFunction(requirejs)) {
            return;
        }
        cfg = requirejs;
        requirejs = undefined;
    }
    if (typeof require !== 'undefined' && !isFunction(require)) {
        cfg = require;
        require = undefined;
    }
    function newContext(contextName) {
        var inCheckLoaded, Module, context, handlers,
            checkLoadedTimeoutId,
            config = {
                waitSeconds: 7,
                baseUrl: './',
                paths: {},
                bundles: {},
                pkgs: {},
                shim: {},
                config: {}
            },
            registry = {},
            enabledRegistry = {},
            undefEvents = {},
            defQueue = [],
            defined = {},
            urlFetched = {},
            bundlesMap = {},
            requireCounter = 1,
            unnormalizedCounter = 1;
        //ary中.刪除此項;..刪此項和前一項除(i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1]==='..')
        function trimDots(ary) {}
        //路徑處理.config.pkgs有name值優先,無值按相對路徑轉化,apply是否啓用地圖配置
        function normalize(name, baseName, applyMap) {}
        //刪除data-requiremoduley爲name和data-requirecontext === context.contextName的script
        function removeScript(name) {}
        //先移除再加載模塊;
        function hasPathFallback(id) {}
        //第一個"!"分離的先後數據 return [prefix, name];
        function splitPrefix(name) {}
        //返回模塊的屬性對象
        // return {
        //         prefix: prefix,
        //         name: normalizedName,
        //         parentMap: parentModuleMap,
        //         unnormalized: !!suffix,
        //         url: url,
        //         originalName: originalName,
        //         isDefine: isDefine,
        //         id: (prefix ?
        //                 prefix + '!' + normalizedName :
        //                 normalizedName) + suffix
        //     };
        function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {}
        //registry[id]有值取值,沒值生成新的context.Module對象,並賦給registry[id]
        function getModule(depMap) {}
        //模塊加載完且name爲defined 或 加載出錯且name爲error ? 執行fn : 模塊綁定事件 
        function on(depMap, name, fn) {}
        //errback ? 執行errback(err) : mod.emit('error', err)執行刪除操做
        function onError(err, errback) {}
        //將globalDefQueue推入defQueue
        function takeGlobalQueue() {}
        //commonjs風格
        handlers = {
            //mod.require ? 返回mod.require : localRequire
            'require': function (mod) {},
            'exports': function (mod) {},
            'module': function (mod) {}
        };
        //清除registry[id]、enabledRegistry[id]
        function cleanRegistry(id) {}
        //遞歸mod.depMaps,執行mod.check();
        function breakCycle(mod, traced, processed) {}
        //檢查加載狀態,不一樣狀態執行不一樣操做
        function checkLoaded() {}
        Module = function (map) {
            this.events = getOwn(undefEvents, map.id) || {};
            this.map = map;
            this.shim = getOwn(config.shim, map.id);
            this.depExports = [];
            this.depMaps = [];
            this.depMatched = [];
            this.pluginMaps = {};
            this.depCount = 0;
        };
        Module.prototype = {
            //初始化,根據options.enabled ? this.enable() : this.check()
            init: function (depMaps, factory, errback, options) {},
            //經過this.depCount判斷依賴是否加載完成
            defineDep: function (i, depExports) {},
            // map.prefix ? this.callPlugin() : this.load();
            fetch: function () {},
            //經過context.load調req.load加載js文件
            load: function () {},
            //define模塊調用
            check: function () {},
            //加載依賴
            callPlugin: function () {},
            //data-main上的模塊調用,define模塊調用
            enable: function () {},
            //將cb推入this.events[name]
            on: function (name, cb) {},
            //name === 'error'刪this.events[name];不然循環this.events[name]執行cb(evt);
            emit: function (name, evt) {}
        };
        //module.init內執行check()非enable();
        function callGetModule(args) {}
        //移除監聽事件
        function removeListener(node, func, name, ieName) {}
        //移除監聽事件,返回節點
        function getScriptData(evt) {}
        // 獲取並加載defQueue中的模塊 
        function intakeDefines() {}
        context = {
            config: config,
            contextName: contextName,
            registry: registry,
            defined: defined,
            urlFetched: urlFetched,
            defQueue: defQueue,
            defQueueMap: {},
            Module: Module,
            makeModuleMap: makeModuleMap,
            nextTick: req.nextTick,
            onError: onError,
            //配置參數 調用context.require(cfg.deps || [], cfg.callback);
            configure: function (cfg) {},
            //返回閉包接口供調用
            makeShimExports: function (value) {},
            //返回閉包接口供調用
            makeRequire: function (relMap, options) {
                //makeRequire的實際執行函數,生成宏任務;
                function localRequire(deps, callback, errback) {
                    return localRequire;
                }
                mixin(localRequire, {
                    isBrowser: isBrowser,
                    toUrl: function (moduleNamePlusExt) {},
                    defined: function (id) {},
                    specified: function (id) {}
                });
                if (!relMap) {
                    localRequire.undef = function (id) {};
                }
                return localRequire;
            },
            //調用 module的enable()
            enable: function (depMap) {},
            //完成加載後
            completeLoad: function (moduleName) {},
            //根據moduleName獲取url
            nameToUrl: function (moduleName, ext, skipExt) {},
            //調用req.load()
            load: function (id, url) {},
            //return callback.apply(exports, args);
            execCb: function (name, callback, args, exports) {},
            //加載完成後
            onScriptLoad: function (evt) {},
            //加載錯誤
            onScriptError: function (evt) {}
        };
        context.require = context.makeRequire();
        return context;
    }
    //入口函數
    req = requirejs = function (deps, callback, errback, optional) {};
    //return req(config);
    req.config = function (config) {};
    //宏任務
    req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
        setTimeout(fn, 4);
    } : function (fn) { fn(); };
    //req賦值給require
    if (!require) {
        require = req;
    }
    req.version = version;
    req.jsExtRegExp = /^\/|:|\?|\.js$/;
    req.isBrowser = isBrowser;
    s = req.s = {
        contexts: contexts,
        newContext: newContext
    };
    //初始調用
    req({});
    each([
        'toUrl',
        'undef',
        'defined',
        'specified'
    ], function (prop) {
        req[prop] = function () {
            var ctx = contexts[defContextName];
            return ctx.require[prop].apply(ctx, arguments);
        };
    });
    if (isBrowser) {
        head = s.head = document.getElementsByTagName('head')[0];
        baseElement = document.getElementsByTagName('base')[0];
        if (baseElement) {
            head = s.head = baseElement.parentNode;
        }
    }
    req.onError = defaultOnError;
    //建立節點
    req.createNode = function (config, moduleName, url) {};
    //節點綁定事件,添加到頭部,並返回節點
    req.load = function (context, moduleName, url) {};
    //返回狀態爲interactive的節點
    function getInteractiveScript() {}
    //data-main上的值解析賦給cfg
    if (isBrowser && !cfg.skipDataMain) {
        eachReverse(scripts(), function (script) {
            if (!head) {
                head = script.parentNode;
            }
            dataMain = script.getAttribute('data-main');
            if (dataMain) {
                mainScript = dataMain;
                if (!cfg.baseUrl && mainScript.indexOf('!') === -1) {
                    src = mainScript.split('/');
                    mainScript = src.pop();
                    subPath = src.length ? src.join('/')  + '/' : './';

                    cfg.baseUrl = subPath;
                }
                mainScript = mainScript.replace(jsSuffixRegExp, '');
                if (req.jsExtRegExp.test(mainScript)) {
                    mainScript = dataMain;
                }
                cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
                return true;
            }
        });
    }
    //定義模塊的函數
    define = function (name, deps, callback) {};
    define.amd = {
        jQuery: true
    };
    req.exec = function (text) {};
    //將data-main的值解析代入req函數;
    req(cfg);
}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));

2、詳細流程

一、初始化變量;
二、執行 req({})閉包

req({})
context = contexts[contextName] = req.s.newContext(contextName);

說明:contextName='_',返回context這個東西,context.require = context.makeRequire();=localRequire;調用makeRequire實際調用makeRequire裏的localRequireapp

context.configure(cfg);

說明:cfg=config={},什麼都沒幹函數

return context.require(deps, callback, errback);

說明:調用makeRequire裏的localRequire;deps=[];requirejs

intakeDefines();
takeGlobalQueue();

說明:intakeDefines的子函數,二者什麼都沒執行fetch

context.nextTick(function () {
                        intakeDefines();
                        requireMod = getModule(makeModuleMap(null, relMap));

                        requireMod.skipMap = options.skipMap;
                        requireMod.init(deps, callback, errback, {
                            enabled: true
                        });
                        checkLoaded();
                    });

說明:產生一個 宏任務1 函數;req({})函數完ui

三、執行 req(cfg)
獲取data-main上的值並解析成cfgthis

req(cfg);

說明:cfg={baseUrl:"data-main解析值1",deps:[data-main解析值2]}url

context = getOwn(contexts, contextName);

說明:獲取以前產生的context;

context.configure(cfg);

說明:cfg=config={baseUrl:"data-main解析值1",deps:[data-main解析值2]}

config[prop] = value;

說明:屬性值給config

context.require(cfg.deps || [], cfg.callback);

說明:調用makeRequire裏的localRequire;deps=[data-main解析值2];

intakeDefines();
takeGlobalQueue();

說明:什麼都沒執行

context.nextTick(function () {

說明:產生一個 宏任務2 函數;

return context.require(deps, callback, errback);

說明:調用makeRequire裏的localRequire;deps=[];

intakeDefines();
takeGlobalQueue();

說明:什麼都沒執行

context.nextTick(function () {

說明:產生一個 宏任務3 函數;req(cfg);函數完

四、第一個宏任務開始

intakeDefines();
takeGlobalQueue();

說明:什麼都沒執行

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

說明:返回一個對象obj

nameParts = splitPrefix(name);

說明:name="_@r2"

normalizedName = normalize(name, parentName, applyMap);
url = context.nameToUrl(normalizedName);
parentModule = syms.slice(0, i).join('/');

說明:parentModule="_@r2"

getModule(obj)
new context.Module(depMap)

說明:depMap=上面返回的對象obj;getModule返回context.Module實例requireMod

requireMod.init(deps, callback, errback, {enabled: true});
this.enable();
enabledRegistry[this.map.id] = this;
this.check();
cleanRegistry(id);
this.emit('defined', this.exports);
checkLoaded();

說明:什麼都沒執行,第一個宏任務完。

五、第二個宏任務開始

intakeDefines();
takeGlobalQueue();

說明:什麼都沒執行

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

說明:返回一個對象obj

nameParts = splitPrefix(name);

說明:name="_@r3"

normalizedName = normalize(name, parentName, applyMap);
url = context.nameToUrl(normalizedName);
parentModule = syms.slice(0, i).join('/');

說明:parentModule="_@r3"

getModule(obj)
new context.Module(depMap)

說明:depMap=上面返回的對象obj;getModule返回context.Module實例requireMod

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

說明: deps 變爲 data-main解析值2

this.enable();
        enabledRegistry[this.map.id] = this;
        depMap = makeModuleMap(depMap,(this.map.isDefine ? this.map : this.map.parentMap), false,!this.skipMap);
            nameParts = splitPrefix(name);
            normalizedName = normalize(name, parentName, applyMap);                                
            url = context.nameToUrl(normalizedName);                           
"        on(depMap, 'defined', bind(this, function (depExports) {this.defineDep(i, depExports);this.check();}));
                            
                            
                        "
            mod = getModule(depMap);
                mod = registry[id] = new context.Module(depMap);
            mod.on(name, fn);
            getModule(depMap).enable();
        this.check();
checkLoaded();

說明: 宏任務2結束

六、宏任務3同宏任務1

七、執行data-main引入的文件的require函數

require(['./example'],function(example){example.test();});
req = requirejs = function (deps, callback, errback, optional) {
    return context.require(deps, callback, errback);
        intakeDefines();
            takeGlobalQueue();
                context.nextTick(function () {

說明: 產生一個宏任務4函數;require函數結束

onScriptLoad: function (evt) {
    var data = getScriptData(evt);
    context.completeLoad(data.id);
        shim = getOwn(config.shim, moduleName) || {},
        takeGlobalQueue();
        callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);

說明: require函數結束後執行

八、宏任務4同宏任務2
說明: deps變爲 要加載的依賴

3、流程相關圖片

圖片描述

圖片描述

圖片描述

圖片描述

圖片描述

相關文章
相關標籤/搜索