Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RequireJS详解

RequireJS是AMD(Asynchronous Module Definition)规范最流行的实现,也是早期浏览器端模块化开发的重要工具。本章将深入探讨RequireJS的使用方法、配置选项、最佳实践和实际应用。

RequireJS简介

RequireJS是一个JavaScript文件和模块加载器,具有以下特点:

  • 异步加载: 异步加载JavaScript文件,提升页面性能
  • 依赖管理: 自动解析和加载模块依赖
  • 跨浏览器: 支持所有现代浏览器
  • 插件系统: 丰富的插件生态,支持加载各种资源
  • 优化工具: 提供r.js优化器进行代码合并和压缩

安装和基本使用

1. 引入RequireJS

<!DOCTYPE html>
<html>
<head>
    <title>RequireJS Demo</title>
</head>
<body>
    <div id="app"></div>
    
    <!-- 引入RequireJS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
    
    <!-- 或者从本地引入 -->
    <!-- <script src="js/lib/require.js"></script> -->
    
    <!-- 指定主模块 -->
    <script src="js/lib/require.js" data-main="js/app"></script>
</body>
</html>

2. 基本模块定义

// js/utils/math.js
define(function() {
    'use strict';
    
    return {
        add: function(a, b) {
            return a + b;
        },
        
        subtract: function(a, b) {
            return a - b;
        },
        
        multiply: function(a, b) {
            return a * b;
        },
        
        divide: function(a, b) {
            if (b === 0) {
                throw new Error('Division by zero');
            }
            return a / b;
        }
    };
});

3. 使用模块

// js/app.js - 主应用文件
require(['utils/math'], function(math) {
    console.log('2 + 3 =', math.add(2, 3));
    console.log('10 - 4 =', math.subtract(10, 4));
    console.log('5 * 6 =', math.multiply(5, 6));
    console.log('15 / 3 =', math.divide(15, 3));
});

配置系统

1. 基础配置

// js/config.js
require.config({
    // 设置基础路径
    baseUrl: 'js',
    
    // 路径映射
    paths: {
        // 第三方库
        'jquery': 'lib/jquery-3.6.0.min',
        'underscore': 'lib/underscore-1.13.1.min',
        'backbone': 'lib/backbone-1.4.0.min',
        
        // 应用模块
        'app': 'app',
        'models': 'app/models',
        'views': 'app/views',
        'collections': 'app/collections',
        'templates': '../templates'
    },
    
    // 超时设置(秒)
    waitSeconds: 30,
    
    // URL查询参数(用于缓存破坏)
    urlArgs: 'v=' + (new Date()).getTime()
});

// 启动应用
require(['app/main'], function(main) {
    main.init();
});

2. Shim配置(兼容非AMD库)

require.config({
    baseUrl: 'js',
    
    paths: {
        'jquery': 'lib/jquery-3.6.0.min',
        'underscore': 'lib/underscore-1.13.1.min',
        'backbone': 'lib/backbone-1.4.0.min',
        'bootstrap': 'lib/bootstrap-4.6.0.min',
        'd3': 'lib/d3-7.3.0.min'
    },
    
    // 为非AMD库配置依赖和导出
    shim: {
        'underscore': {
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'bootstrap': {
            deps: ['jquery']
        },
        'd3': {
            exports: 'd3'
        }
    }
});

3. 高级配置选项

require.config({
    baseUrl: 'js',
    
    // 映射配置 - 为不同上下文使用不同版本
    map: {
        '*': {
            'jquery': 'jquery-3.6.0'
        },
        'legacy-module': {
            'jquery': 'jquery-1.12.4'
        }
    },
    
    // 包配置
    packages: [
        {
            name: 'dojo',
            location: 'lib/dojo',
            main: 'main'
        }
    ],
    
    // 配置文件
    config: {
        'app/config': {
            apiUrl: 'https://api.example.com',
            debug: true
        }
    },
    
    // 强制定义
    enforceDefine: true,
    
    // Node.js适配
    nodeIdCompat: true
});

插件系统

1. text插件 - 加载文本文件

// 安装:下载text.js到js/lib/目录

// 配置
require.config({
    paths: {
        'text': 'lib/text'
    }
});

// 使用text插件加载HTML模板
define(['text!templates/user.html'], function(userTemplate) {
    return {
        render: function(user) {
            var html = userTemplate
                .replace('{{name}}', user.name)
                .replace('{{email}}', user.email);
            return html;
        }
    };
});

2. domReady插件 - DOM就绪

// 使用domReady插件
require(['domReady!'], function() {
    // DOM已经准备就绪
    console.log('DOM is ready');
    
    // 可以安全地操作DOM
    document.getElementById('app').innerHTML = 'Hello, World!';
});

// 或者在模块中使用
define(['domReady!', 'jquery'], function(doc, $) {
    // DOM和jQuery都已准备就绪
    return {
        init: function() {
            $('#app').text('Application initialized');
        }
    };
});

3. i18n插件 - 国际化

// 配置i18n
require.config({
    config: {
        i18n: {
            locale: 'zh-cn' // 设置默认语言
        }
    }
});

// 创建语言文件
// nls/messages.js
define({
    root: {
        greeting: 'Hello',
        goodbye: 'Goodbye'
    },
    'zh-cn': true,
    'en-us': true
});

// nls/zh-cn/messages.js
define({
    greeting: '你好',
    goodbye: '再见'
});

// 使用i18n
define(['i18n!nls/messages'], function(messages) {
    return {
        greet: function() {
            return messages.greeting;
        }
    };
});

4. css插件 - 加载CSS

// 使用css插件
define(['css!styles/main.css'], function() {
    // CSS已经加载
    return {
        init: function() {
            console.log('Styles loaded');
        }
    };
});

5. 自定义插件

// plugins/json.js - 加载JSON文件的插件
define(function() {
    return {
        load: function(name, req, onload, config) {
            if (config.isBuild) {
                // 构建时的处理
                onload(null);
                return;
            }
            
            var xhr = new XMLHttpRequest();
            xhr.open('GET', name + '.json', true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        try {
                            var data = JSON.parse(xhr.responseText);
                            onload(data);
                        } catch (e) {
                            onload.error(e);
                        }
                    } else {
                        onload.error(new Error('HTTP ' + xhr.status));
                    }
                }
            };
            xhr.send();
        }
    };
});

// 使用自定义插件
define(['json!data/config'], function(config) {
    console.log('Loaded config:', config);
});

实际项目应用

1. 大型单页应用架构

// js/app/main.js - 应用入口
define([
    'jquery',
    'backbone',
    'app/router',
    'app/models/user',
    'app/views/layout'
], function($, Backbone, Router, User, LayoutView) {
    
    var App = {
        Models: {},
        Views: {},
        Collections: {},
        Router: null,
        
        init: function() {
            // 初始化布局
            this.layout = new LayoutView({
                el: '#app'
            });
            
            // 初始化路由
            this.router = new Router();
            
            // 启动应用
            this.start();
        },
        
        start: function() {
            // 渲染布局
            this.layout.render();
            
            // 启动Backbone路由
            Backbone.history.start({
                pushState: true,
                root: '/'
            });
            
            console.log('Application started');
        }
    };
    
    return App;
});
// js/app/router.js - 路由器
define([
    'backbone',
    'app/views/home',
    'app/views/users',
    'app/views/profile'
], function(Backbone, HomeView, UsersView, ProfileView) {
    
    var Router = Backbone.Router.extend({
        routes: {
            '': 'home',
            'users': 'users',
            'users/:id': 'profile',
            '*notfound': 'notFound'
        },
        
        home: function() {
            var view = new HomeView();
            this.showView(view);
        },
        
        users: function() {
            var view = new UsersView();
            this.showView(view);
        },
        
        profile: function(id) {
            var view = new ProfileView({ userId: id });
            this.showView(view);
        },
        
        notFound: function() {
            console.log('404 - Page not found');
        },
        
        showView: function(view) {
            // 清理之前的视图
            if (this.currentView) {
                this.currentView.remove();
            }
            
            // 显示新视图
            this.currentView = view;
            view.render();
        }
    });
    
    return Router;
});

2. 模块化组件系统

// js/components/base-component.js - 基础组件
define(['jquery'], function($) {
    
    function BaseComponent(options) {
        this.options = $.extend(true, {}, this.defaults, options);
        this.element = null;
        this.initialized = false;
        
        this.init();
    }
    
    BaseComponent.prototype = {
        defaults: {},
        
        init: function() {
            this.createElement();
            this.bindEvents();
            this.initialized = true;
        },
        
        createElement: function() {
            throw new Error('createElement must be implemented');
        },
        
        bindEvents: function() {
            // 子类可以重写
        },
        
        render: function() {
            throw new Error('render must be implemented');
        },
        
        destroy: function() {
            if (this.element) {
                this.element.remove();
            }
            this.initialized = false;
        }
    };
    
    return BaseComponent;
});

// js/components/data-table.js - 数据表组件
define([
    'components/base-component',
    'text!templates/data-table.html'
], function(BaseComponent, tableTemplate) {
    
    function DataTable(options) {
        BaseComponent.call(this, options);
    }
    
    // 继承BaseComponent
    DataTable.prototype = Object.create(BaseComponent.prototype);
    DataTable.prototype.constructor = DataTable;
    
    DataTable.prototype.defaults = {
        columns: [],
        data: [],
        sortable: true,
        pageable: true,
        pageSize: 10
    };
    
    DataTable.prototype.createElement = function() {
        this.element = $(tableTemplate);
    };
    
    DataTable.prototype.bindEvents = function() {
        var self = this;
        
        // 排序事件
        if (this.options.sortable) {
            this.element.on('click', 'th[data-sortable]', function() {
                var column = $(this).data('column');
                self.sort(column);
            });
        }
        
        // 分页事件
        if (this.options.pageable) {
            this.element.on('click', '.pagination a', function(e) {
                e.preventDefault();
                var page = $(this).data('page');
                self.goToPage(page);
            });
        }
    };
    
    DataTable.prototype.render = function() {
        this.renderHeader();
        this.renderBody();
        this.renderPagination();
        return this;
    };
    
    DataTable.prototype.renderHeader = function() {
        var headerHtml = '<tr>';
        this.options.columns.forEach(function(column) {
            var sortable = column.sortable !== false ? 'data-sortable data-column="' + column.field + '"' : '';
            headerHtml += '<th ' + sortable + '>' + column.title + '</th>';
        });
        headerHtml += '</tr>';
        
        this.element.find('thead').html(headerHtml);
    };
    
    DataTable.prototype.renderBody = function() {
        var self = this;
        var bodyHtml = '';
        
        var data = this.getCurrentPageData();
        data.forEach(function(row) {
            bodyHtml += '<tr>';
            self.options.columns.forEach(function(column) {
                var value = row[column.field];
                if (column.formatter) {
                    value = column.formatter(value, row);
                }
                bodyHtml += '<td>' + value + '</td>';
            });
            bodyHtml += '</tr>';
        });
        
        this.element.find('tbody').html(bodyHtml);
    };
    
    DataTable.prototype.getCurrentPageData = function() {
        if (!this.options.pageable) {
            return this.options.data;
        }
        
        var start = (this.currentPage - 1) * this.options.pageSize;
        var end = start + this.options.pageSize;
        return this.options.data.slice(start, end);
    };
    
    DataTable.prototype.sort = function(column) {
        // 排序逻辑
        console.log('Sorting by:', column);
    };
    
    DataTable.prototype.goToPage = function(page) {
        this.currentPage = page;
        this.render();
    };
    
    return DataTable;
});

3. API集成

// js/services/api.js - API服务
define(['jquery'], function($) {
    
    var Api = {
        baseUrl: '/api/v1',
        
        request: function(options) {
            var defaults = {
                url: '',
                method: 'GET',
                data: null,
                headers: {
                    'Content-Type': 'application/json'
                },
                timeout: 30000
            };
            
            var config = $.extend(true, {}, defaults, options);
            config.url = this.baseUrl + config.url;
            
            // 添加认证头
            var token = this.getAuthToken();
            if (token) {
                config.headers['Authorization'] = 'Bearer ' + token;
            }
            
            return $.ajax(config).fail(function(xhr, status, error) {
                console.error('API request failed:', error);
                
                // 处理认证失败
                if (xhr.status === 401) {
                    Api.handleAuthError();
                }
            });
        },
        
        get: function(url, params) {
            return this.request({
                url: url,
                method: 'GET',
                data: params
            });
        },
        
        post: function(url, data) {
            return this.request({
                url: url,
                method: 'POST',
                data: JSON.stringify(data)
            });
        },
        
        put: function(url, data) {
            return this.request({
                url: url,
                method: 'PUT',
                data: JSON.stringify(data)
            });
        },
        
        delete: function(url) {
            return this.request({
                url: url,
                method: 'DELETE'
            });
        },
        
        getAuthToken: function() {
            return localStorage.getItem('auth_token');
        },
        
        setAuthToken: function(token) {
            localStorage.setItem('auth_token', token);
        },
        
        clearAuthToken: function() {
            localStorage.removeItem('auth_token');
        },
        
        handleAuthError: function() {
            this.clearAuthToken();
            window.location.href = '/login';
        }
    };
    
    return Api;
});

// js/models/user.js - 用户模型
define(['services/api'], function(Api) {
    
    function User(data) {
        this.id = data.id || null;
        this.name = data.name || '';
        this.email = data.email || '';
        this.role = data.role || 'user';
        this.createdAt = data.createdAt || null;
    }
    
    User.prototype.save = function() {
        var url = this.id ? '/users/' + this.id : '/users';
        var method = this.id ? 'put' : 'post';
        
        return Api[method](url, this.toJSON()).then(function(response) {
            if (!this.id) {
                this.id = response.id;
            }
            return this;
        }.bind(this));
    };
    
    User.prototype.destroy = function() {
        if (!this.id) {
            return Promise.reject(new Error('Cannot delete user without ID'));
        }
        
        return Api.delete('/users/' + this.id);
    };
    
    User.prototype.toJSON = function() {
        return {
            id: this.id,
            name: this.name,
            email: this.email,
            role: this.role
        };
    };
    
    // 静态方法
    User.findById = function(id) {
        return Api.get('/users/' + id).then(function(data) {
            return new User(data);
        });
    };
    
    User.findAll = function(params) {
        return Api.get('/users', params).then(function(data) {
            return data.map(function(userData) {
                return new User(userData);
            });
        });
    };
    
    return User;
});

性能优化

1. r.js构建优化

// build.js - r.js构建配置
({
    appDir: '../src',
    baseUrl: 'js',
    dir: '../dist',
    
    // 模块配置
    modules: [
        {
            name: 'app/main',
            include: [
                'app/router',
                'app/models/user',
                'app/views/layout'
            ]
        }
    ],
    
    // 路径配置
    paths: {
        'jquery': 'lib/jquery-3.6.0.min',
        'underscore': 'lib/underscore-1.13.1.min',
        'backbone': 'lib/backbone-1.4.0.min'
    },
    
    // 文件排除
    fileExclusionRegExp: /^(r|build)\.js$/,
    
    // 优化选项
    optimize: 'uglify2',
    preserveLicenseComments: false,
    generateSourceMaps: true,
    
    // 移除console.log
    uglify2: {
        output: {
            beautify: false
        },
        compress: {
            drop_console: true
        }
    }
})

2. 运行时优化

// 懒加载策略
define(['jquery'], function($) {
    
    var LazyLoader = {
        cache: {},
        
        loadModule: function(moduleName) {
            if (this.cache[moduleName]) {
                return Promise.resolve(this.cache[moduleName]);
            }
            
            return new Promise(function(resolve, reject) {
                require([moduleName], function(module) {
                    this.cache[moduleName] = module;
                    resolve(module);
                }.bind(this), reject);
            }.bind(this));
        },
        
        preloadModules: function(moduleNames) {
            var promises = moduleNames.map(function(name) {
                return this.loadModule(name);
            }.bind(this));
            
            return Promise.all(promises);
        }
    };
    
    return LazyLoader;
});

// 代码分割
require.config({
    bundles: {
        'bundle-admin': ['app/admin/dashboard', 'app/admin/users'],
        'bundle-public': ['app/public/home', 'app/public/about']
    }
});

调试和测试

1. 开发环境配置

// js/config-dev.js
require.config({
    baseUrl: 'js',
    
    paths: {
        'jquery': 'lib/jquery-3.6.0',  // 使用未压缩版本
        'underscore': 'lib/underscore-1.13.1',
        'backbone': 'lib/backbone-1.4.0'
    },
    
    // 禁用缓存
    urlArgs: 'bust=' + (new Date()).getTime(),
    
    // 启用详细错误
    enforceDefine: true,
    
    // 缩短超时以快速发现问题
    waitSeconds: 7
});

// 全局错误处理
requirejs.onError = function(err) {
    console.error('RequireJS Error:', err);
    console.error('Error type:', err.requireType);
    console.error('Error modules:', err.requireModules);
    
    if (err.requireType === 'timeout') {
        console.error('Modules that timed out:', err.requireModules);
    }
};

2. 单元测试

// test/test-main.js - 测试配置
require.config({
    baseUrl: '../js',
    
    paths: {
        'jasmine': '../test/lib/jasmine-2.9.1/jasmine',
        'jasmine-html': '../test/lib/jasmine-2.9.1/jasmine-html',
        'jasmine-boot': '../test/lib/jasmine-2.9.1/boot',
        
        // 应用模块
        'utils': 'utils',
        'models': 'app/models'
    },
    
    shim: {
        'jasmine-html': ['jasmine'],
        'jasmine-boot': ['jasmine', 'jasmine-html']
    }
});

// 加载测试
require(['jasmine-boot'], function() {
    require([
        'test/utils/math-test',
        'test/models/user-test'
    ], function() {
        // 启动测试
        window.onload();
    });
});

// test/utils/math-test.js
define(['utils/math'], function(math) {
    describe('Math Utils', function() {
        it('should add two numbers', function() {
            expect(math.add(2, 3)).toBe(5);
        });
        
        it('should subtract two numbers', function() {
            expect(math.subtract(5, 3)).toBe(2);
        });
        
        it('should throw error on division by zero', function() {
            expect(function() {
                math.divide(10, 0);
            }).toThrow();
        });
    });
});

总结

RequireJS作为AMD规范的主要实现,提供了完整的模块化解决方案:

  • 成熟稳定: 经过大量项目验证的可靠工具

  • 功能丰富: 完整的配置系统和插件生态

  • 工具完善: 提供构建优化和调试支持

  • 文档详细: 有详细的文档和社区支持

  • 语法复杂: 相比现代模块系统语法较为复杂

  • 体积较大: 运行时库增加了页面负担

  • 生态衰落: 现代开发更倾向于ES模块和构建工具

虽然RequireJS在现代开发中逐渐被新技术取代,但它为JavaScript模块化发展做出了重要贡献,理解其原理和使用方法对于掌握前端发展历程很有价值。


下一章: UMD通用模块