zoukankan      html  css  js  c++  java
  • RequireJS源码初探

    前两天跟着叶小钗的博客,看了下RequireJS的源码,大体了解了其中的执行过程。不过在何时进行依赖项的加载,以及具体的代码在何处执行,还没有搞透彻,奈何能力不够,只能先记录一下了。

    RequireJS的初探

    看源码从头开始看,肯定是不切实际的。按照叶小钗的方法,是从data-main开始的,所以我们也从那里开始把!

    首先,页面会有一段js标签,会去加载requirejs:

    <script data-main="test.js" src="lib/require.js"></script>
    

    Requirejs中,代码是一个自执行的方法:

    var requirejs,require,define;
    (function(global){
    	
    })(this);
    

    源码中,主要是定义了三个全局的变量——requirejs,require,define,下面是一个自执行的方法。

    那么主要就是看看这个方法里面都干了什么吧!

    RequireJS主体方法

    //定义环境变量
    
    	//定义各种方法
    
    	//检查requirejs,require,define
    
    	//核心部分
    	function newContext(){}//定义核心部分方法
    	
    	req = requirejs = function(){//定义req
    		//...
    		return context.require();
    	};
    
    	req.config = function(){};
    
        req({});//创建默认的上下文
    
        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;
        };
    
        //洋洋洒洒,加载代码
        req.load = function(){
        	node = req.createNode(config, moduleName, url);//创建节点
        	node.addEventListener('load', context.onScriptLoad, false);//添加load事件
        	if (baseElement) {//插入到head里面
                head.insertBefore(node, baseElement);
            } else {
                head.appendChild(node);
            }
        };
    
        if (isBrowser && !cfg.skipDataMain) {
        	//加载main.js
        }
    
        define = function(){};
    
        req.exec =function(){};
    
        req(cfg);//执行配置文件
    

    上面的代码中,关键的方法定义其实只有两个:

    • 定义了newContext()方法,用于配置上下文环境,并且仅会执行一次!后续都是使用同一个context!
    • 定义req,它是后续使用的方法!

    然后在上面的代码中,它做了下面三件事:

    • 1 执行req({}),传入了空的对象,初始化context
    • 2 if(isBrowser && xxxx)....,加载data-main所指向的js,读取配置
    • 3 执行req(cfg),执行刚刚读取的配置,加载目标模块...

    基本上就是这个套路了!

    newContext()

    RequireJS最精彩的部分,就在这个方法里面了!

    function newContext(contextName){
    
        function getModule(depMap) {
            var id = depMap.id,
                mod = getOwn(registry, id);
    
            if (!mod) {
                mod = registry[id] = new context.Module(depMap);
            }
    
            return mod;
        }
    	
        function checkLoaded() {
        	
        }
    
    	context = {
    		//...
    		makeRequire: function (relMap, options) {
    		 	//核心
                function localRequire(deps, callback, errback) {
                	//真正的核心
                	context.nextTick(function () {
                        intakeDefines();
    
                        requireMod = getModule(makeModuleMap(null, relMap));//主要看这里吧
    
                        requireMod.skipMap = options.skipMap;
    
                        requireMod.init(deps, callback, errback, {
                            enabled: true
                        });
    
                        checkLoaded();
                    });
                }
    
                return localRequire;
    		},
    		load: function (id, url) {
                req.load(context, id, url);
            },
            onScriptLoad : function() {
            	context.completeLoad();
            },
            completeLoad : function() {
            	takeGlobalQueue();
            	while(defQueue.length){
    
            	}
            	mod = getOwn(registry, moduleName);
            	checkLoaded();
            }
    		//...
    	}
    
    	Module.prototype = {
    		init : function(depMaps, factory, errback, options){
    			if (options.enabled || this.enabled) {
                    this.enable();
                } else {
                    this.check();
                }
    		},
    		fetch : function(){
    			if (this.shim) {//依赖
                    context.makeRequire(this.map, {
                        enableBuildCallback: true
                    })(this.shim.deps || [], bind(this, function () {
                        return map.prefix ? this.callPlugin() : this.load();
                    }));
                } else {
                    //Regular dependency.
                    return map.prefix ? this.callPlugin() : this.load();//是否包含前缀 text!xxx
                }
    		},
    		load: function () {
                var url = this.map.url;
    
                //Regular dependency.
                if (!urlFetched[url]) {
                    urlFetched[url] = true;
                    context.load(this.map.id, url);
                }
            },
    		check : function(){
    			this.fetch();
    		},
    		enable : function(){
    			this.check();
    		}
    	};
    	context.require = context.makeRequire();//其实是把localRequire赋值给context.require
        return context;
    };
    

    这个newContext()里面定义大量的加载模块、校验、检查等工作。可以看到这个方法,主要是定义了一个context对象和Module方法。

    然后执行这个方法后,会自动调用context对象的makeRequire()方法,这个makeRequire实际上调用的又是内部定义的localRequire()。LocalRequire则是处理加载任务的核心——比如依赖的检查,模块的加载等等。

    执行点

    req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
        setTimeout(fn, 4);
    } : function (fn) { fn(); };
    

    所有的加载都会交由这个nextTick执行,暂时没有搞清楚...

    流程图

    收获

    • 1 原来RequireJS加载模块的时候,是检查data-main属性,然后去加载目标js。
    • 2 加载到目标模块后,会按照它的依赖关系,进行加载,并且每个模块仅会加载一次。
    • 3 加载模块的时候,会绑定一个load事件,当加载完会触发事件,执行该js
    • 4 脚本实际上是通过创建了页面的script元素,然后添加到head里面。
  • 相关阅读:
    关于SharePoint 2010 Beta2的安装
    SharePoint 2010新体验10 文档评分
    SharePoint 2010 新体验3 文档集
    我在SharePoint 2010 Day上做的一个演示课程
    下载 SharePoint Server 2010 Public Beta (包含中文版)
    关于QuickPart的Q&A
    SharePoint服务器连接配置数据库的连接字符串保存在哪里?
    为SharePoint 2010创建Application Page
    Let's talk about SharePoint 2010
    SharePoint 2010 新体验8 内容类型集线器 (Content Type Hub)
  • 原文地址:https://www.cnblogs.com/xing901022/p/5401195.html
Copyright © 2011-2022 走看看