写在前面:

最近做的一个项目,用的require和backbone,对两者的使用已经很熟悉了,但是一直都有好奇他们怎么实现的,一直寻思着读读源码。现在项目结束,终于有机会好好研究一下。

本文重要解读requirejs的源码,backbone源码分析将会随后给出。

行文思路:

  • requirejs 基本介绍
  • requirejs使用后的几个好奇
  • requirejs源码解读

requirejs基本介绍

由于篇幅有限,这里不会详解requirejs的使用和api,建议读者朋友自己去用几次,再详读api.

简介

简单来说,requirejs是一个遵循 AMD(Asynchronous Module Definition)规范的模块载入框架。

使用requirejs的好处是:异步加载、模块化、依赖管理

使用

引入:

<script data-main="/path/to/init" src="/path/to/require.js"></script>

这里的data-main是整个项目的入口.

定义模块:

  define();
        } : function (fn) { fn(); };

req.onError = defaultOnError;
        req.createNode = function (config, moduleName, url) {};
        req.load = function (context, moduleName, url) {};
        define = function (name, deps, callback) {};
        req.exec = function (text) {};
        req(cfg);
         ? [) &&
                    !isOpera) {
                useInteractive = true;
                node.attachEvent('onreadystatechange', context.onScriptLoad);
            } else {
                node.addEventListener('load', context.onScriptLoad, false);
                node.addEventListener('error', context.onScriptError, false);
            }
            node.src = url;

// 兼容IE6-8, script可能在append之前执行, 所有把noe绑定在currentAddingScript中,防止其他地方改变这个值
            currentlyAddingScript = node;
            if (baseElement) {
                // 这里baseElement是getElementsByName('base'); 现在一般都执行else了。
                head.insertBefore(node, baseElement);
            } else {
                head.appendChild(node);
            }
            currentlyAddingScript = null;
            return node;
        } else if (isWebWorker) {
            // 如果是web worker。。不懂
            try {
                importScripts(url);
                context.completeLoad(moduleName);
            } catch (e) {
                context.onError(makeError('importscripts',
                                'importScripts failed for ' +
                                    moduleName + ' at ' + url,
                                e,
                                [moduleName]));
            }
        }

};

这里可以看出第一个问题的原因了.引入data-*的作用是用来移除匹配用的.或则IE低版本中修正contextName和moduleName. 这里req.createNode和context.onScriptLoad是其他地方定义的,接下来看req.createNope:

  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;

};

这里可以解决最后一个问题,通过appendChild, node.async实现异步加载的。

当node加载完毕后会调用context.onScriptLoad, 看看做了什么:

  onScriptLoad: function (evt) {
        if (evt.type === 'load' ||
                (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
            interactiveScript = null;
            // getScriptData()找evet对应的script, 提取data-requiremodule就知道mod的name了。
            var data = getScriptData(evt);
            context.completeLoad(data.id);
        }

}

再看context.completeLoad:

  completeLoad: function (moduleName) {

var found, args, mod,
            shim = getOwn(config.shim, moduleName) || {},
            shExports = shim.exports;
        // 把globalQueue 转换到 context.defQueue(define收集到的mod集合)
        takeGlobalQueue();
        while (defQueue.length) {
            args = defQueue.shift();
            if (args[0] === null) {
                args[0] = moduleName;
                // 如果当前的defModule是匿名define的(arg[0]=null), 把当前moduleName给他,并标记找到
                if (found) {
                    break;
                }
                found = true;
            } else if (args[0] === moduleName) {
                //  非匿名define
                found = true;
            }
            // callGetModule较长, 作用是实例化一个context.Module对象并初始化, 放入registry数组中表示可用.
            callGetModule(args);
        }

// 获取刚才实例化的Module对象
        mod = getOwn(registry, moduleName);

if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
            if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
                if (hasPathFallback(moduleName)) {
                    return;
                } else {
                    return onError(makeError('nodefine',
                                     'No define call for ' + moduleName,
                                     null,
                                     [moduleName]));
                }
            } else {
                callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
            }
        }
        // 检查loaded情况,超过时间的就remove掉,并加入noLoads数组
        checkLoaded();

}

可以看到,当script加载完毕后,只做了一件事:实例化context.Module对象,并暴露给registry供调用.

require 实现

  
  req = requirejs = function (deps, callback, errback, optional) {

var context, config,
            contextName = defContextName;
        // 第一个参数不是模块依赖表达
        if (!isArray(deps) && typeof deps !== 'string') {
            // deps is a config object
            config = deps;
            if (isArray(callback)) {
                // 如果有依赖模块则调整参数, 第二个参数是deps
                deps = callback;
                callback = errback;
                errback = optional;
            } else {
                deps = [];
            }
        }
        if (config && config.context) {
            contextName = config.context;
        }
        context = getOwn(contexts, contextName);
        if (!context) {
            context = contexts[contextName] = req.s.newContext(contextName);
        }
        if (config) {
            context.configure(config);
        }
        // 以上获取正确的context,contextName
        return context.require(deps, callback, errback);
    }; 

一看,结果什么都没做,做的事还在context.require()里面。 在context对象中:

context.require = context.makeRequire();

我们需要的require结果是context.makeRequire这个函数返回的闭包:

  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)) {
                    // 非法调用
                    return onError(makeError('requireargs', 'Invalid require call'), errback);
                }
                // 如果require的是require|exports|module 则直接调用handlers定义的
                if (relMap && hasProp(handlers, deps)) {
                    return handlers[deps](registry[relMap.id]);
                }
                if (req.get) {
                    return req.get(context, deps, relMap, localRequire);
                }
                // 通过require的模块名Normalize成需要的moduleMap对象
                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([])')));
                }
                // 返回require的模块的返回值。
                return defined[id];
            }

// 把globalQueue 转换到 context.defQueue,并把defQueue的每一个都实例化一个context.Module对象并初始化, 放入registry数组中表示可用.
            intakeDefines();

// nextTick 使用的是setTimeOut.如果没有则是回调函数
            // 本次require结束后把所有deps标记为needing to be loaded.
            context.nextTick(function () {
                intakeDefines();
                requireMod = getModule(makeModuleMap(null, relMap));
                requireMod.skipMap = options.skipMap;
                requireMod.init(deps, callback, errback, {
                    enabled: true
                });
                checkLoaded();
            });
            return localRequire;
        }
        ..
        return localRequire;

}

如果直接require模块, 会返回此模块的返回值;否则会把他加入到context.defQueue, 初始化后等待调用; 比如直接:

var util = require('./path/to/util');

会直接返回util模块返回值; 而如果:

require(['jquery', 'backbone'], function($, Backbone){});

就会执行intakeDefines()nextTick();

总结

花时间读读源码,对以前使用require时的那些做法想通了,知道那样做的原因和结果,以后用着肯定也会顺手多了。

学习框架源码可以让自己用的有谱、大胆, 更多的时候是学习高手的代码组织, 编码风格,设计思想, 对自己提升帮助很大~

总结下自己研读源码方式,希望对读者有所帮助: 项目中用熟 -> 源码如何布局组织-> demo进入源码,log验证猜想-> 看别人分析 -> 总结

玉伯说 RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug, 以后一定研究下seajs,看看如何明显没有bug.

RequireJs 源码解读及思考的更多相关文章

  1. 温故而知新 Volley源码解读与思考

    相比新的网络请求框架Volley真的很落后,一无是处吗,要知道Volley是由google官方推出的,虽然推出的时间很久了,但是其中依然有值得学习的地方.  从命名我们就能看出一些端倪,volley中 ...

  2. BackBone 源码解读及思考

    说明 前段时间略忙,终于找到时间看看backbone代码. 正如知友们说的那样,backbone简单.随性. 代码简单的看一眼,就能知道作者的思路.因为简单,所以随性,可以很自由的和其他类库大搭配使用 ...

  3. Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考

    本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...

  4. 15、Spark Streaming源码解读之No Receivers彻底思考

    在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Appr ...

  5. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  6. underscore 源码解读之 bind 方法的实现

    自从进入七月以来,我的 underscore 源码解读系列 更新缓慢,再这样下去,今年更完的目标似乎要落空,赶紧写一篇压压惊. 前文 跟大家简单介绍了下 ES5 中的 bind 方法以及使用场景(没读 ...

  7. 线程本地变量ThreadLocal源码解读

      一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...

  8. seajs 源码解读

    之前面试时老问一个问题seajs 是怎么加载js 文件的 在网上找一些资料,觉得这个写的不错就转载了,记录一下,也学习一下 seajs 源码解读 seajs 简单介绍 seajs是前端应用模块化开发的 ...

  9. SDWebImage源码解读之SDWebImageDownloader

    SDWebImage源码解读之SDWebImageDownloader 第八篇 前言 SDWebImageDownloader这个类非常简单,作者的设计思路也很清晰,但是我想在这说点题外话. 如果有人 ...

随机推荐

  1. ABAP锁,数据库锁

    原文出自 江正军 技术博客,博客链接:www.cnblogs.com/jiangzhengjun ABAP数据锁定 SM12锁查看与维护 通用加锁与解锁函数 ABAP程序锁定 数据库锁 锁的分类和兼容 ...

  2. width

    position:absolute 其widht:%是想对于最近的已经定位的父元素,如果没有就想对于body widht 是指的内容区的with,设置除了width其他的元素都会使元素变的比width ...

  3. linux练习命令

    任务一:按要求完成以下操作1)显示日期格式2)在/tmp/下新建目录test ,并指定权限6643)显示环境变量path,但将/root加入到$PATH中4)用cat显示/etc/passwd,并打印 ...

  4. 【读书笔记】《Java Web整合开发实践》第3章 JSP

    1. JSP:Java Server Pages 2. JSP注释:<%--注释内容--%> 3. page指令(页面指令):定义JSP页面的全局属性. <%@ page langu ...

  5. Linux的XServer

    Moblin Core是在Gnome Mobile的平台上建立.我以前玩Linux,提交的都和图像没有关系,连Xwindows都不用启动,开机后直接进入文本命令行,所以这方面了解得很少,需要学习一下, ...

  6. wifidog源码分析 - wifidog原理 tiger

    转:http://www.cnblogs.com/tolimit/p/4223644.html wifidog源码分析 - wifidog原理 wifidog是一个用于配合认证服务器实现无线网页认证功 ...

  7. Linux基础四---系统监控&硬盘分区

    ---恢复内容开始--- 一系统分区 1.top [参数] -b 批处理 -c 显示命令完全模式 -I 忽略失效过程 -s 保密模式 -S 累积模式 -i<时间> 设置间隔时间 -u< ...

  8. DNS 转发配置

    DNS 转发配置 我们配置DNS是只能解析我们定义的zone的,我们没有定义的是不能解析的. 配置DNS转发就可以解析其他互联网上的域名了,前提是这个域名在互联网中的企业在使用. 也就是说这个域名已经 ...

  9. MongoDB的Find详解(一)

    1.指定返回的键 db.[documentName].find ({条件},{键指定}) 数据准备persons.json var persons = [{name:"jim",a ...

  10. String和StringBuilder、StringBuffer

    Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder String 只读字符串,这里的只读并不是指String类型变量无法被修改,而是指String类 ...