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里面。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java达人

多线程设计模式解读4—Producer-Consumer模式

Producer-Consumer模式可以说是多线程设计模式之王,后期我们要讲的许多模式像Thread-Pool模式,Active Object模式等都是Pro...

13040
来自专栏Java 源码分析

Exectors框架 源码分析

Exectors框架 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,...

28970
来自专栏木子墨的前端日常

easyUI datagrid 清空

最近在做一个管理系统,出于一些需要,经常要将一些datagrid清空。然后easyUI本身并没有自带的方法,然后自己动手丰衣足食吧。

11730
来自专栏向治洪

JSBridge深度剖析

概述 做过混合开发的人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包装一层Native,然后通过Bridge技术的js调用本地的库。 在...

38450
来自专栏坚毅的PHP

[node.js]开放平台接口调用测试

遇到的问题:Node.js JSON parsing error,syntax error unexpect end of input 测试代码 //测试/st...

44260
来自专栏Star先生的专栏

从源码中分析 Hadoop 的 RPC 机制

RPC是Remote Procedure Call(远程过程调用)的简称,这一机制都要面对两个问题:对象调用方式余与序列/反序列化机制。本文给大家介绍从源码中分...

75200
来自专栏技术小黑屋

记一场 Android 技术答疑

之前在Stuq的Android课程中有幸分享了一些关于优化的问题,后期又处理了一些来自网友的问题,这里简单以文字形式做个整理.

13620
来自专栏熊二哥

快速入门系列--WebAPI--04在老版本MVC4下的调整

WebAPI是建立在MVC和WCF的基础上的,原来微软老是喜欢封装的很多,这次终于愿意将http编程模型的相关细节暴露给我们了。在之前的介绍中,基本上都基于.N...

24760
来自专栏Java 源码分析

Exectors框架 源码分析

Exectors框架 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,...

27160
来自专栏Java学习之路

Java中的阻塞队列

一丶什么是阻塞队列 阻塞队列(BlockingQueue)是一个支持两个可以进行阻塞插入和阻塞移除的附加方法的队列。 1)阻塞插入:当队列满后,队列会阻塞(...

37560

扫码关注云+社区

领取腾讯云代金券