JavaScript 模块化编程:从原始写法到 AMD 规范与 Require.js

作者:纸飞机时间:2026-10-29 08:19:43浏览:469

本文系统梳理 JavaScript 模块化编程的演进历程,涵盖从原始函数、对象写法到立即执行函数、放大模式等多种基础实现方式。深入解析 CommonJS 与 AMD 两大主流规范的差异,重点介绍 Require.js 在浏览器环境下的异步加载机制、依赖管理及非规范模块处理,帮助开发者掌握前端模块化的核心实践。

JavaScript 模块化编程概述

此前对 JavaScript 的理解尚不深入,近期因项目需求重新学习相关概念。目前尚未完全掌握,先整理笔记以备后续查阅。

JavaScript 模块化基础写法

第一章 JavaScript 模块化编程

(一): 模块的写法

一、原始写法 模块本质上是实现特定功能的一组方法集合。只需将不同的函数(以及记录状态的变量)简单放置在一起,即可构成一个模块。 ```javascript function m1() { // ... } function m2() { // ... } ``` 上述 m1() 和 m2() 函数共同组成一个模块,使用时直接调用即可。 缺点:这种方式会“污染”全局变量,无法确保不与其他模块发生变量名冲突,且模块成员之间的关系不够直观。

二、对象写法


// 把模块写成一个对象,所有的模块成员都放到这个对象里面;
  var module = new Object({
    _count:0,
    m1:function(){
      // ...
    },
    m2:function(){
      // ...
    }
  });
// 上面的函数m1()和m2(),都封装在module对象里;使用时直接调用这个对象的属性;
  module.m1();
// 但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写;
  module._count = 4;

三、立即执行函数写法


  var module = (function(){
    var _count = 0;
    var m1 = function(){
      // ...
    };
    var m2 = function(){

    };
    return {
      m1:m1,
      m2:m2
    };
  })();
// 使用上面的写法,外部代码无法读取内部的_count变量;
  console.info(module._count); // undefined;
// 上面的写法就是JavaScript模块的基本写法;

四、放大模式


// 如果模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式";
  var module = (function(mod){
    mod.m3 = function(){
      // ...
    };
    return mod;
  })(module);
// 上面的代码为module模块添加了一个新方法m3(),然后返回新的module模块;

五、宽放大模式


// 在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载;
// 如果采用上一节的写法,第一个执行的部分有可能加载一个不存在的空对象,这时就要采用"宽放大模式";
  var module = (function(mod){
    // ...
    return mod;
  })(window.module || {});
// 与"放大模式"相比,"宽放大模式"就是"立即执行函数"的参数可以是空对象;

六、输入全局变量


// 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互;
// 为了在模块内部调用全局变量,必须显式地将其他变量输入模块;
  var module = (function($,YAHOO){
    // ...
  })(jQuery,YAHOO);
// 上面的module模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module;
// 这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显;

AMD 规范与 CommonJS 对比

第二章 JavaScript 模块化编程 (二): AMD 规范

一、模块的规范 目前,通行的 JavaScript 模块规范主要有两种:CommonJS 和 AMD。

二、CommonJS Node.js 将 JavaScript 语言应用于服务器端编程,这标志着“JavaScript 模块化编程”的正式诞生。 Node.js 的模块系统是基于 CommonJS 规范实现的。在 CommonJS 中,存在一个全局方法 require(),用于加载模块。 ```javascript var math = require('math'); // 加载模块 math.add(2, 3); // 调用模块方法 => 5 ```

三、浏览器环境 上一节所述的代码在浏览器环境中运行会存在较大问题。 ```javascript var math = require('math'); math.add(2, 3); ``` 问题在于:必须在 require('math') 等待 math.js 加载完成后,才会执行 math.add(2, 3)。 因此,浏览器的模块不能采用“同步加载”,只能采用“异步加载”,即 AMD 规范。

四、AMD AMD (Asynchronous Module Definition) 即异步模块定义。 采用异步加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等加载完成之后,这个回调函数才会运行。 AMD 也采用 require() 语句加载模块,但是它要求两个参数: ```javascript require([module], callback); ``` - module: 是一个数组,里面的成员就是要加载的模块; - callback: 是加载成功之后的回调函数。 ```javascript require(['math'], function(math) { math.add(2, 3); }); ``` math.add() 与 math 模块加载不是同步的,浏览器不会发生假死。因此,AMD 比较适合浏览器环境。

Require.js 的使用与配置

第三章 JavaScript 模块化编程 (三): require.js 的用法 一、为什么使用 require.js 需要依次加载多个 js 文件。 缺点: 1. 加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长; 2. 由于 js 文件之间存在依赖关系,因此必须严格保证加载顺序,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。 所以 require.js 解决了这两个问题: 1. 实现 js 文件的异步加载,避免网页失去响应; 2. 管理模块之间的依赖性,便于代码的编写和维护。

二、require.js 的加载 1. 加载 require.js ```html <script src="js/require.js" defer async="true"></script> ``` async 属性表明这个文件需要异步加载,避免网页失去响应。IE 不支持这个属性,只支持 defer,所以把 defer 也写上。 2. 加载 main.js ```html <script src="js/require.js" data-main="js/main"></script> ``` data-main 属性的作用是,指定网页程序的主模块 => main.js,这个文件会第一个被 require.js 加载。 由于 require.js 默认的文件后缀名是 js,所以可以把 main.js 简写成 main。

三、主模块 main.js 的写法 1. 如果 main.js 不依赖任何其他模块,可以直接写入 JavaScript 代码。 // main.js alert('加载成功!'); 2. 如果 main.js 依赖于模块,这时就要使用 AMD 规范定义的 require() 函数。 // main.js require(['moduleA', 'moduleB', 'moduleC'], function(moduleA, moduleB, moduleC) { // ... }) require() 函数接收两个参数: - 参数一:数组,表示所依赖的模块,即主模块依赖的三个模块; - 参数二:回调函数,当前面指定的模块都加载成功后,它将被调用;加载的模块会以参数形式传入该函数,从而在回调函数内部可以使用这些模块。 require() 异步加载模块,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。 实例: ```javascript require(['jquery', 'underscore', 'backbone'], function($, _, Backbone) { // ... }); ```

四、模块的加载 使用 require.config() 方法,可以对模块的加载行为进行自定义。 require.config() 就写在主模块 (main.js) 的头部。 参数就是一个对象,这个对象的 paths 属性指定各个模块的加载路径。 设定以下三个模块的文件默认和 main.js 在同一个目录: ```javascript require.config({ paths: { "jquery": "jquery.min", "underscore": "underscore.min", "backbone": "backbone.min" } }); ```

// 如果加载的模块和主模块不在同一个目录,就要逐一指定路径: require.config({ paths: { "jquery": "lib/jquery.min", "underscore": "lib/underscore.min", "backbone": "lib/backbone.min" } }); // 或者直接改变基目录 (baseUrl) require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", "underscore": "underscore.min", "backbone": "backbone.min" } });

// 如果模块在另一台主机上,也可以直接指定它的网址 require.config({ paths: { "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min" } }); // require.js 要求,每个模块是一个单独的 js 文件;这样的话,如果加载多个模块,就会发出多次 HTTP 请求,会影响网页的加载速度; // 因此,require.js 提供了一个优化工具,当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少 HTTP 请求数。

五、AMD 模块的写法

// require.js 加载的模块,采用 AMD 规范,也就是说,模块必须按照 AMD 的规定来写; // 具体来说,就是模块必须采用特定的 define() 函数来定义;如果一个模块不依赖其他模块,那么可以直接定义在 define() 函数中; // 在 math.js 中定义 math 模块 // math.js define(function() { var add = function(x, y) { return x + y; }; return { add: add }; }); // 在 main.js 中加载 math 模块 require(['math'], function(math) { alert(math.add(1, 1)); }); // 如果这个模块还依赖其他模块,那么 define() 函数的第一个参数,必须是一个数组,指明该模块的依赖性; // math.js define(['myLib'], function(myLib) { function foo() { myLib.doSomething(); } return { foo: foo }; }); // 当 require() 函数加载上面这个模块的时候,就会先加载 myLib.js 文件。

六、加载非规范的模块

// 加载非规范的模块,在用 require() 加载之前,要先用 require.config() 方法,定义它们的一些特征。 require.config({ shim: { 'underscore': { exports: '_' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' } } }); // require.config() 接收一个配置对象,这个对象除了有前面说过的 paths 属性之外,还有一个 shim 属性,专门用来配置不兼容的模块; // (1). 定义 deps 数组,表明该模块的依赖性; // (2). 定义 exports 值 (输出的变量名),表明这个模块外部调用时的名称; 比如:jQuery 的插件 shim: { 'jquery.scroll': { deps: ['jquery'], exports: 'jQuery.fn.scroll' } };

七、require.js 插件

1. domready: 可以让回调函数在页面 DOM 结构加载完成之后运行。 ```javascript require(['domready!'], function(doc) { // called once the DOM is ready; }) ``` 2. text 和 image: 允许 require.js 加载文本和图片文件。 ```javascript define(['text!review.txt', 'image!cat.jpg'], function(review, cat) { console.log(review); document.body.appendChild(cat); });

作者最新文章