requirejs源码分析

发布于 | 分类于 源码分析

RequireJS 是一个实现了 AMD (Asynchronous Module Definition) 规范的 JavaScript 模块加载器,由于目前参与的项目使用了类似于requirejs的运行时微前端架构,需要了解一下整体的原理。

本文将从原理、架构和代码实现三个层面深入剖析 RequireJS 2.3.7 版本的源码。

核心原理 ​

AMD 规范 ​

RequireJS 实现了 AMD 规范,其核心思想是:

  • 异步加载:模块以异步方式加载,不阻塞页面渲染
  • 依赖前置:在模块定义时声明依赖,加载器负责解析和加载
  • 模块化封装:每个模块有独立的作用域,通过 define 定义,通过 require 使用

模块加载流程 ​

mermaid
sequenceDiagram
    participant User as 用户代码
    participant Req as require()
    participant Ctx as Context
    participant Mod as Module
    participant Loader as Script Loader
    participant Script as 脚本文件
    
    User->>Req: require(['moduleA'], callback)
    Req->>Ctx: context.require(deps, callback)
    Ctx->>Mod: getModule(moduleMap)
    Mod->>Mod: init(deps, factory)
    Mod->>Mod: enable()
    
    loop 每个依赖
        Mod->>Ctx: enable(depMap)
        Ctx->>Mod: 创建依赖模块
        Mod->>Mod: fetch()
        Mod->>Loader: load(id, url)
        Loader->>Script: 创建 script 标签
        Script-->>Loader: onload 事件
        Loader->>Script: define(name, deps, factory)
        Script->>Ctx: defQueue.push([name, deps, factory])
    end
    
    Ctx->>Ctx: intakeDefines()
    Ctx->>Mod: check()
    
    alt 依赖全部加载
        Mod->>Mod: execCb(factory, depExports)
        Mod->>Ctx: defined[id] = exports
        Mod->>Mod: emit('defined', exports)
        Mod->>User: callback(exports)
    else 依赖未完成
        Mod->>Mod: 等待依赖
    end

循环依赖处理 ​

RequireJS 通过 exports 提前暴露机制解决循环依赖:

  • 模块 A 依赖模块 B,模块 B 依赖模块 A
  • 当 A 开始加载时,创建 exports 对象
  • B 加载时可以获取到 A 的 exports 引用(虽然此时可能未完全初始化)
  • 通过 breakCycle 函数检测并打破循环

循环依赖处理流程 ​

mermaid
graph TD
    A[模块 A 开始加载] --> B[创建 A 的 exports 对象]
    B --> C[A 依赖模块 B]
    C --> D[开始加载模块 B]
    D --> E[B 依赖模块 A]
    E --> F{A 是否在 traced 中?}
    F -->|是| G[检测到循环依赖]
    F -->|否| H[继续递归检查]
    G --> I[使用 A 的 exports]
    I --> J[B 完成定义]
    J --> K[A 获取 B 的 exports]
    K --> L[A 完成定义]
    
    style G fill:#f96
    style I fill:#9f6

架构设计 ​

整体架构 ​

RequireJS 采用闭包模式,整个库包裹在一个 IIFE 中:

javascript
(function (global, setTimeout) {
    var requirejs, require, define;
    // 核心实现
}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));

架构层次图 ​

mermaid
graph TB
    subgraph "全局 API"
        A[requirejs/require]
        B[define]
        C[requirejs.config]
    end
    
    subgraph "Context 层"
        D[Context Manager]
        E[Default Context]
        F[Custom Context]
    end
    
    subgraph "模块管理层"
        G[Module Registry]
        H[Enabled Registry]
        I[Defined Modules]
        J[DefQueue]
    end
    
    subgraph "模块实例层"
        K[Module Instance]
        L[ModuleMap]
        M[Dependencies]
    end
    
    subgraph "加载层"
        N[Script Loader]
        O[Plugin System]
        P[Event System]
    end
    
    A --> D
    B --> D
    C --> D
    D --> E
    D --> F
    E --> G
    E --> H
    E --> I
    E --> J
    G --> K
    K --> L
    K --> M
    K --> N
    K --> O
    K --> P

核心组件 ​

Context(上下文) ​

每个 Context 是一个独立的模块加载环境,包含:

  • config: 配置对象(baseUrl, paths, shim 等)
  • registry: 所有模块的注册表
  • defined: 已定义完成的模块
  • enabledRegistry: 已启用的模块
  • defQueue: 定义队列

多个 Context 可以共存,通过 contexts 对象管理:

javascript
contexts = {
    '_': defaultContext,  // 默认上下文
    'custom': customContext
}

Context 结构图 ​

mermaid
classDiagram
    class Context {
        +Object config
        +Object registry
        +Object defined
        +Object enabledRegistry
        +Array defQueue
        +Object defQueueMap
        +configure(cfg)
        +require(deps, callback, errback)
        +makeRequire(relMap, options)
        +enable(depMap, mod)
        +completeLoad(moduleName)
        +nameToUrl(moduleName, ext)
        +load(id, url)
    }
    
    class Module {
        +Object map
        +Array depMaps
        +Array depExports
        +Function factory
        +Boolean enabled
        +Boolean defined
        +init(depMaps, factory, errback)
        +enable()
        +fetch()
        +check()
        +callPlugin()
    }
    
    class ModuleMap {
        +String prefix
        +String name
        +String id
        +String url
        +Boolean isDefine
        +Object parentMap
    }
    
    Context "1" --> "*" Module : manages
    Module "1" --> "1" ModuleMap : has
    Module "1" --> "*" Module : depends on

Module(模块) ​

Module 是模块的抽象表示,核心属性和方法:

javascript
Module.prototype = {
    init: function(depMaps, factory, errback, options) {},
    enable: function() {},
    fetch: function() {},
    check: function() {},
    callPlugin: function() {}
};

ModuleMap(模块映射) ​

存储模块的元信息,包括插件前缀、模块名、URL 等。

配置系统 ​

javascript
config = {
    baseUrl: './',           // 基础路径
    paths: {},               // 路径映射
    bundles: {},             // 打包配置
    shim: {},                // 非 AMD 模块配置
    map: {},                 // 模块映射
    config: {},              // 模块特定配置
    waitSeconds: 7,          // 超时时间
    packages: []             // 包配置
}

配置处理流程 ​

mermaid
flowchart TD
    A[requirejs.config] --> B{配置类型}
    B -->|baseUrl| C[规范化 baseUrl<br/>确保以 / 结尾]
    B -->|paths| D[路径映射配置<br/>支持数组 fallback]
    B -->|shim| E[非 AMD 模块配置<br/>deps + exports]
    B -->|map| F[模块映射配置<br/>不同模块使用不同版本]
    B -->|bundles| G[打包配置<br/>多模块合并]
    B -->|packages| H[包配置<br/>main 入口]
    
    C --> I[合并到 context.config]
    D --> I
    E --> I
    F --> I
    G --> I
    H --> I
    
    I --> J[更新已注册模块的 map]
    J --> K[配置生效]

核心代码实现 ​

模块名称规范化 ​

normalize 函数负责将相对路径转换为绝对路径:

javascript
function normalize(name, baseName, applyMap) {
    // 1. 处理相对路径 (./ 和 ../)
    if (name && name[0] === '.') {
        // 基于 baseName 解析相对路径
        normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
        name = normalizedBaseParts.concat(name);
    }
    
    // 2. 去除路径中的 . 和 ..
    trimDots(name);
    
    // 3. 应用 map 配置
    if (applyMap && map) {
        // 查找最长匹配的映射规则
    }
    
    // 4. 处理 packages 配置
    pkgMain = getOwn(config.pkgs, name);
    return pkgMain ? pkgMain : name;
}

模块加载机制 ​

脚本加载(浏览器环境) ​

javascript
req.load = function (context, moduleName, url) {
    // 创建 script 标签
    node = req.createNode(config, moduleName, url);
    
    // 设置属性
    node.setAttribute('data-requirecontext', context.contextName);
    node.setAttribute('data-requiremodule', moduleName);
    
    // 绑定事件监听器
    if (node.attachEvent && !isOpera) {
        // IE 6-8 使用 attachEvent
        useInteractive = true;
        node.attachEvent('onreadystatechange', context.onScriptLoad);
    } else {
        // 现代浏览器使用 addEventListener
        node.addEventListener('load', context.onScriptLoad, false);
        node.addEventListener('error', context.onScriptError, false);
    }
    
    node.src = url;
    head.appendChild(node);
};

Web Worker 环境 ​

javascript
if (isWebWorker) {
    setTimeout(function() {}, 0);  // 避免 WebKit GC 问题
    importScripts(url);
    context.completeLoad(moduleName);
}

define 函数实现 ​

define 参数处理流程 ​

mermaid
flowchart TD
    A[define 调用] --> B{参数类型检查}
    B -->|name 非字符串| C[匿名模块<br/>name = null]
    B -->|name 是字符串| D[具名模块]
    
    C --> E{deps 是否为数组?}
    D --> E
    
    E -->|否| F[deps = null<br/>callback = deps]
    E -->|是| G[保持 deps]
    
    F --> H{callback 是函数?}
    G --> I[准备就绪]
    
    H -->|是| J[扫描函数体<br/>提取 require 调用]
    H -->|否| I
    
    J --> K[添加 require/exports/module<br/>到 deps]
    K --> I
    
    I --> L{useInteractive?}
    L -->|是 IE 6-8| M[获取交互式脚本<br/>提取 name 和 context]
    L -->|否| N[使用当前 context]
    
    M --> O[推入 defQueue]
    N --> O
    O --> P[等待 onScriptLoad<br/>触发 intakeDefines]
javascript
define = function (name, deps, callback) {
    // 1. 参数归一化
    if (typeof name !== 'string') {
        callback = deps;
        deps = name;
        name = null;  // 匿名模块
    }
    
    if (!isArray(deps)) {
        callback = deps;
        deps = null;
    }
    
    // 2. CommonJS 风格依赖检测
    if (!deps && isFunction(callback)) {
        deps = [];
        callback.toString()
            .replace(commentRegExp, '')
            .replace(cjsRequireRegExp, function (match, dep) {
                deps.push(dep);
            });
        
        // 添加 require, exports, module
        deps = ['require', 'exports', 'module'].concat(deps);
    }
    
    // 3. IE 6-8 交互式脚本处理
    if (useInteractive) {
        node = currentlyAddingScript || getInteractiveScript();
        if (node) {
            if (!name) {
                name = node.getAttribute('data-requiremodule');
            }
            context = contexts[node.getAttribute('data-requirecontext')];
        }
    }
    
    // 4. 将定义推入队列
    if (context) {
        context.defQueue.push([name, deps, callback]);
        context.defQueueMap[name] = true;
    } else {
        globalDefQueue.push([name, deps, callback]);
    }
};

define.amd = { jQuery: true };  // AMD 标识

require 函数实现 ​

javascript
req = requirejs = function (deps, callback, errback, optional) {
    var context, config, contextName = defContextName;
    
    // 1. 参数解析(支持配置对象)
    if (!isArray(deps) && typeof deps !== 'string') {
        config = deps;
        if (isArray(callback)) {
            deps = callback;
            callback = errback;
            errback = optional;
        } else {
            deps = [];
        }
    }
    
    // 2. 获取或创建 context
    if (config && config.context) {
        contextName = config.context;
    }
    context = getOwn(contexts, contextName);
    if (!context) {
        context = contexts[contextName] = req.s.newContext(contextName);
    }
    
    // 3. 应用配置
    if (config) {
        context.configure(config);
    }
    
    // 4. 执行 require
    return context.require(deps, callback, errback);
};

模块执行流程 ​

Module.check() - 核心执行逻辑 ​

mermaid
stateDiagram-v2
    [*] --> 检查状态
    
    检查状态 --> 未初始化: !inited
    检查状态 --> 有错误: error
    检查状态 --> 执行中: defining
    检查状态 --> 准备执行: else
    
    未初始化 --> 触发fetch: !defQueueMap[id]
    未初始化 --> 等待: defQueueMap[id]
    触发fetch --> [*]
    
    有错误 --> 触发error事件
    触发error事件 --> [*]
    
    执行中 --> [*]: 避免重入
    
    准备执行 --> 检查依赖: depCount < 1
    准备执行 --> 等待依赖: depCount >= 1
    等待依赖 --> [*]
    
    检查依赖 --> 执行工厂函数: isFunction(factory)
    检查依赖 --> 使用字面量: else
    
    执行工厂函数 --> 处理返回值
    使用字面量 --> 保存exports
    
    处理返回值 --> 检查返回值: exports === undefined
    检查返回值 --> 使用module.exports: cjsModule
    检查返回值 --> 使用this.exports: usingExports
    检查返回值 --> 保存exports: else
    
    使用module.exports --> 保存exports
    使用this.exports --> 保存exports
    
    保存exports --> 标记已定义
    标记已定义 --> 触发defined事件
    触发defined事件 --> [*]
javascript
check: function () {
    if (!this.enabled || this.enabling) return;
    
    var id = this.map.id,
        depExports = this.depExports,
        exports = this.exports,
        factory = this.factory;
    
    if (!this.inited) {
        // 未初始化,触发 fetch
        if (!hasProp(context.defQueueMap, id)) {
            this.fetch();
        }
    } else if (this.error) {
        // 有错误,触发 error 事件
        this.emit('error', this.error);
    } else if (!this.defining) {
        this.defining = true;
        
        // 依赖全部加载完成
        if (this.depCount < 1 && !this.defined) {
            if (isFunction(factory)) {
                // 执行工厂函数
                try {
                    exports = context.execCb(id, factory, depExports, exports);
                } catch (e) {
                    err = e;
                }
                
                // 处理返回值
                if (this.map.isDefine && exports === undefined) {
                    cjsModule = this.module;
                    if (cjsModule) {
                        exports = cjsModule.exports;
                    } else if (this.usingExports) {
                        exports = this.exports;
                    }
                }
            } else {
                // 字面量值
                exports = factory;
            }
            
            this.exports = exports;
            
            // 标记为已定义
            if (this.map.isDefine && !this.ignore) {
                defined[id] = exports;
            }
            
            cleanRegistry(id);
            this.defined = true;
        }
        
        this.defining = false;
        
        if (this.defined && !this.defineEmitted) {
            this.defineEmitted = true;
            this.emit('defined', this.exports);
        }
    }
}

依赖管理 ​

Module.enable() - 启用模块及其依赖 ​

mermaid
flowchart TD
    A[enable 调用] --> B[标记 enabled = true]
    B --> C[标记 enabling = true]
    C --> D[遍历 depMaps]
    
    D --> E{depMap 类型?}
    E -->|字符串| F[转换为 ModuleMap]
    E -->|对象| G[已是 ModuleMap]
    
    F --> H{是特殊依赖?}
    G --> H
    
    H -->|require/exports/module| I[使用 handlers 处理]
    H -->|普通依赖| J[depCount += 1]
    
    I --> K[直接返回特殊对象]
    K --> D
    
    J --> L[监听 defined 事件]
    L --> M[监听 error 事件]
    M --> N{依赖已注册?}
    
    N -->|是| O{依赖已启用?}
    N -->|否| D
    
    O -->|否| P[递归 enable 依赖]
    O -->|是| D
    
    P --> D
    
    D --> Q[遍历完成]
    Q --> R[启用插件依赖]
    R --> S[enabling = false]
    S --> T[调用 check]
    T --> U[结束]
javascript
enable: function () {
    enabledRegistry[this.map.id] = this;
    this.enabled = true;
    this.enabling = true;
    
    // 启用每个依赖
    each(this.depMaps, bind(this, function (depMap, i) {
        var id, mod, handler;
        
        if (typeof depMap === 'string') {
            // 转换为 depMap
            depMap = makeModuleMap(depMap, this.map.parentMap, false, !this.skipMap);
            this.depMaps[i] = depMap;
            
            // 处理特殊依赖(require, exports, module)
            handler = getOwn(handlers, depMap.id);
            if (handler) {
                this.depExports[i] = handler(this);
                return;
            }
            
            this.depCount += 1;
            
            // 监听依赖的 defined 事件
            on(depMap, 'defined', bind(this, function (depExports) {
                if (this.undefed) return;
                this.defineDep(i, depExports);
                this.check();
            }));
            
            // 错误处理
            if (this.errback) {
                on(depMap, 'error', bind(this, this.errback));
            }
        }
        
        id = depMap.id;
        mod = registry[id];
        
        // 递归启用依赖
        if (!hasProp(handlers, id) && mod && !mod.enabled) {
            context.enable(depMap, this);
        }
    }));
    
    this.enabling = false;
    this.check();
}

特殊依赖处理 ​

javascript
handlers = {
    'require': function (mod) {
        return mod.require || (mod.require = context.makeRequire(mod.map));
    },
    'exports': function (mod) {
        mod.usingExports = true;
        if (mod.map.isDefine) {
            return mod.exports || (mod.exports = defined[mod.map.id] = {});
        }
    },
    'module': function (mod) {
        return mod.module || (mod.module = {
            id: mod.map.id,
            uri: mod.map.url,
            config: function () {
                return getOwn(config.config, mod.map.id) || {};
            },
            exports: mod.exports || (mod.exports = {})
        });
    }
};

循环依赖检测 ​

javascript
function breakCycle(mod, traced, processed) {
    var id = mod.map.id;
    
    if (mod.error) {
        mod.emit('error', mod.error);
    } else {
        traced[id] = true;
        
        each(mod.depMaps, function (depMap, i) {
            var depId = depMap.id,
                dep = getOwn(registry, depId);
            
            if (dep && !mod.depMatched[i] && !processed[depId]) {
                if (getOwn(traced, depId)) {
                    // 检测到循环依赖
                    mod.defineDep(i, defined[depId]);
                    mod.check();
                } else {
                    // 递归检查
                    breakCycle(dep, traced, processed);
                }
            }
        });
        
        processed[id] = true;
    }
}

超时检测 ​

javascript
function checkLoaded() {
    var waitInterval = config.waitSeconds * 1000,
        expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
        noLoads = [],
        stillLoading = false;
    
    if (inCheckLoaded) return;
    inCheckLoaded = true;
    
    // 检查所有启用的模块
    eachProp(enabledRegistry, function (mod) {
        if (!mod.enabled) return;
        
        if (!mod.error) {
            if (!mod.inited && expired) {
                // 超时未加载
                if (hasPathFallback(modId)) {
                    stillLoading = true;
                } else {
                    noLoads.push(modId);
                    removeScript(modId);
                }
            } else if (!mod.inited && mod.fetched && map.isDefine) {
                stillLoading = true;
            }
        }
    });
    
    if (expired && noLoads.length) {
        // 抛出超时错误
        err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
        return onError(err);
    }
    
    // 检查循环依赖
    if (needCycleCheck) {
        each(reqCalls, function (mod) {
            breakCycle(mod, {}, {});
        });
    }
    
    // 继续等待
    if ((!expired || usingPathFallback) && stillLoading) {
        if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
            checkLoadedTimeoutId = setTimeout(function () {
                checkLoadedTimeoutId = 0;
                checkLoaded();
            }, 50);
        }
    }
    
    inCheckLoaded = false;
}

插件系统 ​

RequireJS 支持插件扩展,插件通过 ! 分隔符标识:

javascript
// 使用示例
require(['text!template.html'], function(html) {
    // html 是模板内容
});

插件加载流程图 ​

mermaid
sequenceDiagram
    participant Mod as Module
    participant Plugin as Plugin Module
    participant Loader as Plugin Loader
    participant Resource as Resource
    
    Mod->>Mod: callPlugin()
    Mod->>Plugin: 加载插件模块<br/>(text, i18n 等)
    
    activate Plugin
    Plugin-->>Mod: plugin.normalize(name)
    Mod->>Mod: 创建规范化的 ModuleMap
    
    Mod->>Plugin: plugin.load(name, require, load, config)
    
    Plugin->>Resource: 获取资源<br/>(AJAX, 文件读取等)
    Resource-->>Plugin: 资源内容
    
    alt 动态生成模块
        Plugin->>Loader: load.fromText(code)
        Loader->>Loader: req.exec(code)
        Loader->>Mod: completeLoad()
    else 直接返回
        Plugin->>Loader: load(value)
        Loader->>Mod: init([], function() { return value })
    end
    
    deactivate Plugin
    
    Mod->>Mod: emit('defined', value)

插件加载流程 ​

javascript
callPlugin: function () {
    var map = this.map,
        id = map.id,
        pluginMap = makeModuleMap(map.prefix);
    
    // 监听插件加载完成
    on(pluginMap, 'defined', bind(this, function (plugin) {
        var load, normalizedMap, normalizedMod,
            name = this.map.name,
            parentName = this.map.parentMap ? this.map.parentMap.name : null;
        
        // 如果插件支持 normalize,规范化资源名称
        if (plugin.normalize) {
            name = plugin.normalize(name, function (name) {
                return normalize(name, parentName, true);
            }) || '';
        }
        
        // 创建规范化的模块映射
        normalizedMap = makeModuleMap(map.prefix + '!' + name, 
                                      this.map.parentMap, true);
        
        // load 回调
        load = bind(this, function (value) {
            this.init([], function () { return value; }, null, {
                enabled: true
            });
        });
        
        load.error = bind(this, function (err) {
            this.inited = true;
            this.error = err;
            onError(err);
        });
        
        // load.fromText 支持动态生成模块
        load.fromText = bind(this, function (text, textAlt) {
            var moduleName = map.name,
                moduleMap = makeModuleMap(moduleName);
            
            getModule(moduleMap);
            
            try {
                req.exec(text);  // 执行动态代码
            } catch (e) {
                return onError(makeError('fromtexteval', 
                    'fromText eval for ' + id + ' failed: ' + e, e, [id]));
            }
            
            context.completeLoad(moduleName);
            localRequire([moduleName], load);
        });
        
        // 调用插件的 load 方法
        plugin.load(map.name, localRequire, load, config);
    }));
    
    context.enable(pluginMap, this);
    this.pluginMaps[pluginMap.id] = pluginMap;
}

Shim 配置 ​

Shim 用于加载非 AMD 模块(如传统的全局变量库):

javascript
requirejs.config({
    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

Shim 处理逻辑 ​

javascript
makeShimExports: function (value) {
    function fn() {
        var ret;
        if (value.init) {
            ret = value.init.apply(global, arguments);
        }
        return ret || (value.exports && getGlobal(value.exports));
    }
    return fn;
}

// 在 completeLoad 中处理 shim
if (shim) {
    context.makeRequire(this.map, {
        enableBuildCallback: true
    })(shim.deps || [], bind(this, function () {
        return map.prefix ? this.callPlugin() : this.load();
    }));
}

路径解析 ​

nameToUrl - 模块名转 URL ​

mermaid
flowchart TD
    A[nameToUrl 调用] --> B{是 package?}
    B -->|是| C[使用 package main]
    B -->|否| D{是 bundle?}
    
    C --> D
    D -->|是| E[使用 bundle ID]
    D -->|否| F{是 URL 格式?}
    
    E --> A
    F -->|是<br/>含 :/ 或 .js| G[直接使用 + ext]
    F -->|否| H[应用 paths 配置]
    
    H --> I[分割模块名为数组]
    I --> J[从长到短匹配 paths]
    J --> K{找到匹配?}
    
    K -->|是| L[替换路径前缀]
    K -->|否| M[保持原样]
    
    L --> N[拼接路径]
    M --> N
    
    N --> O{需要 .js 后缀?}
    O -->|是| P[添加 .js]
    O -->|否| Q[保持原样]
    
    P --> R{需要 baseUrl?}
    Q --> R
    
    R -->|是| S[添加 baseUrl 前缀]
    R -->|否| T[保持原样]
    
    S --> U{有 urlArgs?}
    T --> U
    
    U -->|是| V[添加 urlArgs 参数]
    U -->|否| W[返回 URL]
    
    V --> W
javascript
nameToUrl: function (moduleName, ext, skipExt) {
    var paths, syms, i, parentModule, url, pkgMain;
    
    // 1. 处理 packages
    pkgMain = getOwn(config.pkgs, moduleName);
    if (pkgMain) {
        moduleName = pkgMain;
    }
    
    // 2. 处理 bundles
    bundleId = getOwn(bundlesMap, moduleName);
    if (bundleId) {
        return context.nameToUrl(bundleId, ext, skipExt);
    }
    
    // 3. 如果是 URL 格式,直接返回
    if (req.jsExtRegExp.test(moduleName)) {
        url = moduleName + (ext || '');
    } else {
        // 4. 应用 paths 配置
        paths = config.paths;
        syms = moduleName.split('/');
        
        for (i = syms.length; i > 0; i -= 1) {
            parentModule = syms.slice(0, i).join('/');
            parentPath = getOwn(paths, parentModule);
            
            if (parentPath) {
                if (isArray(parentPath)) {
                    parentPath = parentPath[0];  // 支持 fallback
                }
                syms.splice(0, i, parentPath);
                break;
            }
        }
        
        // 5. 拼接 URL
        url = syms.join('/');
        url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
        url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
    }
    
    // 6. 添加 urlArgs
    return config.urlArgs && !/^blob\:/.test(url) ?
           url + config.urlArgs(moduleName, url) : url;
}

使用示例 ​

基本使用 ​

javascript
// 定义模块
define('moduleA', ['jquery'], function($) {
    return {
        init: function() {
            $('body').append('<div>Module A</div>');
        }
    };
});

// 使用模块
require(['moduleA'], function(moduleA) {
    moduleA.init();
});

配置示例 ​

javascript
requirejs.config({
    baseUrl: '/js/lib',
    paths: {
        'jquery': 'jquery-3.6.0.min',
        'backbone': 'backbone-1.4.0',
        'underscore': 'underscore-1.13.1'
    },
    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    },
    map: {
        '*': {
            'jquery': 'jquery-private'
        },
        'jquery-private': {
            'jquery': 'jquery'
        }
    },
    waitSeconds: 15
});

插件使用 ​

javascript
// 加载文本文件
require(['text!template.html'], function(template) {
    document.body.innerHTML = template;
});

// 国际化
require(['i18n!nls/messages'], function(messages) {
    console.log(messages.greeting);
});

// DOM 就绪
require(['domReady!'], function(doc) {
    console.log('DOM ready');
});

CommonJS 风格 ​

javascript
define(function(require, exports, module) {
    var $ = require('jquery');
    var _ = require('underscore');
    
    exports.helper = function() {
        return _.map([1, 2, 3], function(n) {
            return n * 2;
        });
    };
    
    module.exports = {
        helper: exports.helper
    };
});

总结 ​

尽管 RequireJS 在现代前端开发中使用减少,但其源码仍有很高的学习价值:

  1. 模块化思想:理解模块化的本质和演进
  2. 异步编程:学习异步加载和依赖管理
  3. 兼容性处理:了解如何处理浏览器差异
  4. 架构设计:学习大型库的架构设计思路
  5. 代码质量:精简、高效的代码实现

参考:

你要请我喝一杯奶茶?

版权声明:自由转载-非商用-保持署名和原文链接。

本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。