JavaScript模块化开发详解:从基础到CommonJS与AMD/CMD

作者:青柠时间:2026-10-30 00:00:17浏览:331

本文深入解析JavaScript模块化开发的多种实现方式。从基础的函数封装与对象模式出发,探讨其局限性,重点介绍推荐的立即执行函数模式。随后详细阐述服务器端CommonJS规范及其在浏览器端的异步加载问题,并对比分析前端主流异步模块定义方案:AMD(RequireJS)与CMD(Sea.js)在依赖执行时机与加载策略上的核心差异,帮助开发者根据场景选择合适方案。

JavaScript模块化概述与核心价值

模块化是编程领域中一项通用的最佳实践。通过模块化设计,开发者可以更便捷地复用他人代码,按需加载所需功能模块,从而显著提升代码利用率并加快开发进度。

模块的作用类似于积木,借助它们可以构建出功能各异、形态多样的程序。积木的核心特征在于“小巧且简单”。同理,程序中的模块也应遵循这一原则,确保每个函数仅负责单一任务。这种设计使得其他开发者能够轻松调试和修改代码,无需通读全部逻辑即可理解各代码块的功能。只有保持这种小而简单的特性,模块才能发挥其通用价值。

JavaScript模块化的基础实现方法

一、JavaScript模块化的实现方法 1、函数封装 JavaScript的作用域机制基于函数,因此我们可以直接将函数作为模块使用。


function fn1(){
  //code
}

function fn2(){
  //code
}

缺点:这种方式会“污染”全局变量,无法避免与其他模块发生变量名冲突。

2、对象模式


var myModule1 = {
  fn1: function(){
    //code
  },
  fn2: function(){
    //code
  }
}

缺点:该模式会暴露所有模块成员,导致内部状态可能被外部随意改写。

立即执行函数——推荐方案


var myModule = (function(){
  function fn1(){
    //code
  },
  function fn2(){
    //code
  },
  return {
    fn1: fn1,
    fn2: fn2
  };
})();

践行“小而简单”的模块化原则

二、践行“小而简单”原则 为了更好理解这一原则,我们来看一个具体案例:假设我们需要编写一个创建新链接的函数,并为类型为“mailto”的超链接添加特定的class。初始实现可能如下:


function addLink(text, url, parentElement) {
  var newLink = document.createElement('a');//创建a标签
  newLink.setAttribute('href', url);//为a标签设置href属性
  newLink.appendChild(document.createTextNode(text));//为a标签添加文本
  if(url.indexOf("mailto:")==-1){
    newLink.className = 'mail';
  }
  parentElement.appendChild(newLink);//将a标签添加到页面
}

虽然上述写法可以正常运行,但随着功能需求的增加,该函数可能会变得臃肿而不适用。由此可见,函数功能越特殊,其通用性就越低。此处的写法并未达到模块化的核心要求——即一个函数只执行单一职责。因此,我们需要对代码进行重构:


function createLink(text,url) {
  var newLink = document.createElement('a');
  newLink.setAttribute('href', url);
  newLink.appendChild(document.createTextNode(text));
  return newLink;
}

重构后的createLink函数仅专注于创建并返回需要添加到页面的a标签(体现“小而简单”原则)。这样,在任何需要创建超链接的场景下,我们都可以直接调用该函数。

服务器端CommonJS规范解析

三、CommonJS规范 在浏览器环境中,缺乏模块机制并非致命问题,因为网页程序的复杂性通常有限。然而,在服务器端,程序必须通过模块与操作系统及其他应用进行交互,否则无法进行有效编程。尽管JavaScript在Web端发展多年,但首个流行的模块化规范却源自服务器端的JavaScript应用。CommonJS规范由Node.js发扬光大,这标志着JavaScript模块化编程正式进入主流视野。 Node.js的模块系统正是基于CommonJS规范实现的。在CommonJS规范中,提供了一个全局方法require(),用于加载模块。 加载模块的代码示例如下:

var math = require('math'); 调用模块的代码示例如下:

math.add(2,3) CommonJS规范主要适用于服务器端环境,因为它存在一个显著局限:以上例中第二行math.add(2, 3)为例,该代码必须在math.js模块加载完成后才能执行。由于模块通常存储在服务器端,加载过程可能耗时较长,具体等待时间取决于网络速度。

相比之下,CommonJS规范在服务器端运行良好,因为服务端的所有模块均存储于本地硬盘,可以实现同步加载完成,此时的等待时间仅为硬盘读取时间。

前端异步模块定义:AMD与CMD对比

四、模块的定义与加载机制 AMD(Asynchronous Module Definition,异步模块定义) 主要代表库:require.js 设计目的: (1)实现JavaScript文件的异步加载,避免网页失去响应; (2)管理模块之间的依赖关系,便于代码的编写与维护。

1、定义模块


define(["./cart", "./inventory"], function(cart, inventory) {
  //通过[]引入依赖
  return {
    color: "blue",
    size: "large",
    addToCart: function() {
      inventory.decrement(this);
      cart.add(this);
    }
  }
}
);

2、加载模块


require( ["some/module", "my/module", "a.js", "b.js"],
function(someModule,  myModule) {
  //This function will be called when all the dependencies
  //listed above are loaded. Note that this function could
  //be called before the page is loaded.
  //This callback is optional.
}
 );

CMD(Common Module Definition,通用模块定义) CMD规范由中国开发者提出并发展。主要代表库:sea.js

1、定义模块


define(function(require, exports, module) {
 // 通过 require 引入依赖
 var $ = require('jquery');
 var Spinning = require('./spinning');
 // 通过 exports 对外提供接口
 exports.doSomething = ...
 // 或者通过 module.exports 提供整个接口
 module.exports = ...
});

2、加载模块

seajs.use("../static/hello/src/main") AMD与CMD的主要区别:

对于依赖模块的处理,AMD采用提前执行策略,而CMD则采用延迟执行策略。不过,RequireJS从2.0版本开始,也支持延迟执行(具体取决于代码写法)。CMD推崇“尽可能延迟执行”(as lazy as possible)的理念。

在依赖声明上,CMD推崇“依赖就近”原则,而AMD推崇“依赖前置”原则。

以上就是本文的全部内容,希望对大家的学习有所帮助。

作者最新文章