xiaoyuer_x

导航

require.js源码分析

写的寥寥草草,博客园的布局怎么弄还没有研究,再保存一份草稿,日后在完善,深度研究

require.js 加载顺序

1:加载html主页,require.js文件
2:脚本执行到html中的script开始使用require.js进行模块化加载。先将文件路径加载保存在map中。在调用require()时并没有进行相应文件的加载
3:require配置,在初始化config中到底做了些什么事情呢(真正对用的初始化函数是function(deps, callback, errback, optional)这里包括了需要定义的config基本属性,回掉函数,错误处理函数,optional)
3.1确定context使用require.js中默认的定义
3.2config基本属性不是数组与字符串(在这里的数组判断挺有意思,没有使用typeof而是使用了Object.prototype.toString.call native方法):{
保存的config基本属性
如果第二个参数数组,则callback默认也是config基本属性的一项
}
3.3config基本属性中是否包含context项
3.4验证是否已经拥有该context,同样使用了Objet.prototype.hasOwnProperty.call native方法。contextName毕竟只是一个临时变量,真正最后要用的还是require.js中的刚开始声明的那些全局变量
3.5最后将config基本属性添加到context上下文本对象中的config属性中,默认的一个config包含了如上几个基本属性
                eachProp(cfg, function (value, prop) {
                    if (objs[prop]) {
                        if (!config[prop]) {
                            config[prop] = {};
                        }
                        mixin(config[prop], value, true, true);
                    } else {
                        config[prop] = value;
                    }
                });
 
    function eachProp(obj, func) {
        var prop;
        for (prop in obj) {
            if (hasProp(obj, prop)) {
                if (func(obj[prop], prop)) {
                    break;
                }
            }
        }
    }
 
    function mixin(target, source, force, deepStringMixin) {
        if (source) {
            eachProp(source, function (value, prop) {
                if (force || !hasProp(target, prop)) {
                    if (deepStringMixin && typeof value === 'object' && value &&
                        !isArray(value) && !isFunction(value) &&
                        !(value instanceof RegExp)) {
 
                        if (!target[prop]) {
                            target[prop] = {};
                        }
                        mixin(target[prop], value, force, deepStringMixin);
                    } else {
                        target[prop] = value;
                    }
                }
            });
        }
        return target;
    }
 
    function each(ary, func) {
        if (ary) {
            var i;
            for (i = 0; i < ary.length; i += 1) {
                if (ary[i] && func(ary[i], i, ary)) {
                    break;
                }
            }
        }
    }
但是在eachProp中我们也看到了,可以添加自定义属性,mixin config中 ,packages必须是数组,其他都是object对象,
3.6创建上下文对象context的equire属性。至此所有的script中的顺序动作已经完成了
            makeRequire: function (relMap, options) {
                options = options || {};
 
                function localRequire(deps, callback, errback) {
                    var id, map, requireMod;
 
                    if (options.enableBuildCallback && callback && isFunction(callback)) {
                        callback.__requireJsBuild = true;
                    }
 
                    if (typeof deps === 'string') {
                        if (isFunction(callback)) {
                            //Invalid call
                            return onError(makeError('requireargs', 'Invalid require call'), errback);
                        }
 
                        //If require|exports|module are requested, get the
                        //value for them from the special handlers. Caveat:
                        //this only works while module is being defined.
                        if (relMap && hasProp(handlers, deps)) {
                            return handlers[deps](registry[relMap.id]);
                        }
 
                        //Synchronous access to one module. If require.get is
                        //available (as in the Node adapter), prefer that.
                        if (req.get) {
                            return req.get(context, deps, relMap, localRequire);
                        }
 
                        //Normalize module name, if it contains . or ..
                        map = makeModuleMap(deps, relMap, false, true);
                        id = map.id;
 
                        if (!hasProp(defined, id)) {
                            return onError(makeError('notloaded', 'Module name "' +
                                        id +
                                        '" has not been loaded yet for context: ' +
                                        contextName +
                                        (relMap ? '' : '. Use require([])')));
                        }
                        return defined[id];
                    }
 
                    //Grab defines waiting in the global queue.
                    intakeDefines();
 
                    //Mark all the dependencies as needing to be loaded.
                    context.nextTick(function () {
                        //Some defines could have been added since the
                        //require call, collect them.
                        intakeDefines();
 
                        requireMod = getModule(makeModuleMap(null, relMap));
 
                        //Store if map config should be applied to this require
                        //call for dependencies.
                        requireMod.skipMap = options.skipMap;
 
                        requireMod.init(deps, callback, errback, {
                            enabled: true
                        });
 
                        checkLoaded();
                    });
 
                    return localRequire;
                }
 
                mixin(localRequire, {
                    isBrowser: isBrowser,
 
                    /**
                     * Converts a module name + .extension into an URL path.
                     * *Requires* the use of a module name. It does not support using
                     * plain URLs like nameToUrl.
                     */
                    toUrl: function (moduleNamePlusExt) {
                        var ext,
                            index = moduleNamePlusExt.lastIndexOf('.'),
                            segment = moduleNamePlusExt.split('/')[0],
                            isRelative = segment === '.' || segment === '..';
 
                        //Have a file extension alias, and it is not the
                        //dots from a relative path.
                        if (index !== -1 && (!isRelative || index > 1)) {
                            ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
                            moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
                        }
 
                        return context.nameToUrl(normalize(moduleNamePlusExt,
                                                relMap && relMap.id, true), ext,  true);
                    },
 
                    defined: function (id) {
                        return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
                    },
 
                    specified: function (id) {
                        id = makeModuleMap(id, relMap, false, true).id;
                        return hasProp(defined, id) || hasProp(registry, id);
                    }
                });
 
                //Only allow undef on top level require calls
                if (!relMap) {
                    localRequire.undef = function (id) {
                        //Bind any waiting define() calls to this context,
                        //fix for #408
                        takeGlobalQueue();
 
                        var map = makeModuleMap(id, relMap, true),
                            mod = getOwn(registry, id);
 
                        removeScript(id);
 
                        delete defined[id];
                        delete urlFetched[map.url];
                        delete undefEvents[id];
 
                        //Clean queued defines too. Go backwards
                        //in array so that the splices do not
                        //mess up the iteration.
                        eachReverse(defQueue, function(args, i) {
                            if(args[0] === id) {
                                defQueue.splice(i, 1);
                            }
                        });
 
                        if (mod) {
                            //Hold on to listeners in case the
                            //module will be attempted to be reloaded
                            //using a different config.
                            if (mod.events.defined) {
                                undefEvents[id] = mod.events;
                            }
 
                            cleanRegistry(id);
                        }
                    };
                }
 
                return localRequire;
            },
 
 
        function intakeDefines() {
            var args;
 
            //Any defined modules in the global queue, intake them now.
            takeGlobalQueue();
 
            //Make sure any remaining defQueue items get properly processed.
            while (defQueue.length) {
                args = defQueue.shift();
                if (args[0] === null) {
                    return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
                } else {
                    //args are id, deps, factory. Should be normalized by the
                    //define() function.
                    callGetModule(args);
                }
            }
        }
 
但是在require属性中有启用一个setTimeout定时间隔任务
 
    req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
        setTimeout(fn, 4);
    } : function (fn) { fn(); };
 
        function intakeDefines() {
            var args;
 
            //Any defined modules in the global queue, intake them now.
            takeGlobalQueue();
 
            //Make sure any remaining defQueue items get properly processed.
            while (defQueue.length) {
                args = defQueue.shift();
                if (args[0] === null) {
                    return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
                } else {
                    //args are id, deps, factory. Should be normalized by the
                    //define() function.
                    callGetModule(args);
                }
            }
        }
 
        function takeGlobalQueue() {
            //Push all the globalDefQueue items into the context's defQueue
            if (globalDefQueue.length) {
                //Array splice in the values since the context code has a
                //local var ref to defQueue, so cannot just reassign the one
                //on context.
                apsp.apply(defQueue,
                           [defQueue.length, 0].concat(globalDefQueue));
                globalDefQueue = [];
            }
        }
 
获取module
        function getModule(depMap) {
            var id = depMap.id,
                mod = getOwn(registry, id);
 
            if (!mod) {
                mod = registry[id] = new context.Module(depMap);
            }
 
            return mod;
        }
经过几次循环调用后,终于load到了第一个moudle,是在makeModuleMap方法中url等信息的拼装,保存到了depMaps中。同时depCount的计数值也加一了。这也就是require.js最重要的模块化,将所有的要加载文件通过一个map对下保存,每个文件用require.js的moudle进行封装,封装了什么东西呢?
这个封装而不是单独的封装一个module对象,而是先进性了require的对象-》context对象-》module对象
 
 
 
对于module的加载过程中函数的执行顺序?
nextTick循环调用
-intakeDefines
--takeGlobalQueue
-getModule
--makeModuleMap
---splitPrefix
---normalize
----trimDots
---splitPrefix
---nameToUrl
---Module
-Module.init
--Module.enable
---Module.enable
---Module.check
---Module.emit
-checkLoaded
注意:如果通过require.js模块加载的第一个文件加载进来后,因为require.js还没有结束,DOM加载还需要继续,所以还会顺序运行新进来的文件。
 
 
check
fetch
enable  
callPlugin
load 
 
跳回到context
load
跳回到require
load
最后真正实用的还是req.load加载js,那到底是怎么加载的呢?
 
    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);
 
            //Set up load listener. Test attachEvent first because IE9 has
            //a subtle issue in its addEventListener and script onload firings
            //that do not match the behavior of all other browsers with
            //addEventListener support, which fire the onload event for a
            //script right after the script execution. See:
            //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
            //UNFORTUNATELY Opera implements attachEvent but does not follow the script
            //script execution mode.
            if (node.attachEvent &&
                    //Check if node.attachEvent is artificially added by custom script or
                    //natively supported by browser
                    //read https://github.com/jrburke/requirejs/issues/187
                    //if we can NOT find [native code] then it must NOT natively supported.
                    //in IE8, node.attachEvent does not have toString()
                    //Note the test for "[native code" with no closing brace, see:
                    //https://github.com/jrburke/requirejs/issues/273
                    !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
                    !isOpera) {
                //Probably IE. IE (at least 6-8) do not fire
                //script onload right after executing the script, so
                //we cannot tie the anonymous define call to a name.
                //However, IE reports the script as being in 'interactive'
                //readyState at the time of the define call.
                useInteractive = true;
 
                node.attachEvent('onreadystatechange', context.onScriptLoad);
                //It would be great to add an error handler here to catch
                //404s in IE9+. However, onreadystatechange will fire before
                //the error handler, so that does not help. If addEventListener
                //is used, then IE will fire error before load, but we cannot
                //use that pathway given the connect.microsoft.com issue
                //mentioned above about not doing the 'script execute,
                //then fire the script load event listener before execute
                //next script' that other browsers do.
                //Best hope: IE10 fixes the issues,
                //and then destroys all installs of IE 6-9.
                //node.attachEvent('onerror', context.onScriptError);
            } else {
                node.addEventListener('load', context.onScriptLoad, false);
                node.addEventListener('error', context.onScriptError, false);
            }
            node.src = url;
 
            //For some cache cases in IE 6-8, the script executes before the end
            //of the appendChild execution, so to tie an anonymous define
            //call to the module name (which is stored on the node), hold on
            //to a reference to this node, but clear after the DOM insertion.
            currentlyAddingScript = node;
            if (baseElement) {
                head.insertBefore(node, baseElement);
            } else {
                head.appendChild(node);
            }
            currentlyAddingScript = null;
 
            return node;
        } else if (isWebWorker) {
            try {
                //In a web worker, use importScripts. This is not a very
                //efficient use of importScripts, importScripts will block until
                //its script is downloaded and evaluated. However, if web workers
                //are in play, the expectation that a build has been done so that
                //only one script needs to be loaded anyway. This may need to be
                //reevaluated if other use cases become common.
                importScripts(url);
 
                //Account for anonymous modules
                context.completeLoad(moduleName);
            } catch (e) {
                context.onError(makeError('importscripts',
                                'importScripts failed for ' +
                                    moduleName + ' at ' + url,
                                e,
                                [moduleName]));
            }
        }
    };
 
创建一个Node
    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;
    };
继续组装script node 
head = s.head = document.getElementsByTagName('head')[0];
在这里require.Js将script node添加到了原始html中的head标签内
新生成的head,同时浏览器也对js文件进行了加载
 
最后有意思的是require.js在checkLoaded时竟然做了一遍remove,这个还是蛮有意思的,
 





posted on 2015-04-02 17:10  xiaoyuer_x  阅读(1460)  评论(0编辑  收藏  举报