0

0

JavaScript模块化编程的详细介绍(代码示例)

不言

不言

发布时间:2019-03-11 17:07:08

|

3091人浏览过

|

来源于segmentfault

转载

本篇文章给大家带来的内容是关于javascript模块化编程的详细介绍(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

什么是模块化?

模块就是实现特定功能的一组方法,而模块化是将模块的代码创造自己的作用域,只向外部暴露公开的方法和变量,而这些方法之间高度解耦。

写 JS 为什么需要模块化编程?  
当写前端还只是处理网页的一些表单提交,点击交互的时候,还没有强化 JS 模块化的概念,当前端逻辑开始复杂,交互变得更多,数据量越来越庞大时,前端对 JS 模块化编程的需求就越加强烈。

在很多场景中,我们需要考虑模块化:

立即学习Java免费学习笔记(深入)”;

  1. 团队多人协作,需要引用别人的代码
  2. 项目交接,我们在阅读和重构别人的代码
  3. 代码审查时,检验你的代码是否规范,是否存在问题
  4. 写完代码,回顾自己写的代码是否美观:)
  5. 不同的环境,环境变量不同

基于以上场景,所以,当前 JS 模块化主要是这几个目的:

  1. 代码复用性
  2. 功能代码松耦合
  3. 解决命名冲突
  4. 代码可维护性
  5. 代码可阅读性

先给结论:JS 的模块化编程经历了几个阶段:

  1. 命名空间形式的代码封装
  2. 通过立即执行函数(IIFE)创建的命名空间
  3. 服务器端运行时 Nodejs 的 CommonJS 规范
  4. 将模块化运行在浏览器端的 AMD/CMD 规范
  5. 兼容 CMD 和 AMD 的 UMD 规范
  6. 通过语言标准支持的 ES Module

先给结论图:

587332122-5c84b41e458f0_articlex.jpg

一、命名空间

我们知道,在 ES6 之前,JS 是没有块作用域的,私有变量和方法的隔离主要靠函数作用域,公开变量和方法的隔离主要靠对象的属性引用。

封装函数

在 JS 还没有模块化规范的时候,将一些通用的、底层的功能抽象出来,独立成一个个函数来实现模块化:
比方写一个 utils.js 工具函数文件

//  utils.js
function add(x, y) {
    if(typeof x !== "number" || typeof y !== "number") return;
    return x + y;
}

function square(x) {
    if(typeof x !== "number") return;
    return x * x;
}

<script src="./utils.js"></script>
<script>
    add(2, 3);
    square(4);
</script>

通过 js 函数文件划分的方式,此时的公开函数其实是挂载到了全局对象 window 下,当在别人也想定义一个叫 add 函数,或者多个 js 文件合并压缩的时候,会存在命名冲突的问题。

挂载到全局变量下:

后来我们想到通过挂载函数到全局对象字面量下的方式,利用 JAVA 包的概念,希望减轻命名冲突的严重性。

var mathUtils1 = {
    add: function(x, y) {
        return x + y;
    },
}

var mathUtils2 = {
    add: function(x, y, z) {
        return x + y + z;
    },
}

mathUtils.add();

mathUtils.square();

这种方式仍然创建了全局变量,但如果包的路径很长,那么到最后引用方法可能就会以module1.subModule.subSubModule.add 的方式引用代码了。

IIFE  
考虑模块存在私有变量,于是我们利用IIFE(立即执行表达式)创建闭包来封装私有变量:

var module = (function(){
    var count = 0;
    return {
        inc: function(){
            count += 1;
        },
        dec: function(){
            count += -1;
        }
    }
})()

module.inc();
module.dec();

这样私有变量对于外部来说就是不可访问的,那如果模块需要引入其他依赖呢?

var utils = (function ($) {
    var $body = $("body"); 
    var _private = 0;
    var foo = function() {
        ...
    }
    var bar = function () {
        ...
    }
    
    return {
        foo: foo,
        bar: bar
    }
})(jQuery);

以上封装模块的方式叫作:模块模式,在 jQuery 时代,大量使用了模块模式:

<script src="jquery.js"></script>
<script src="underscore.js"></script>
<script src="utils.js"></script>
<script src="base.js"></script>
<script src="main.js"></script>

jQuery 的插件必须在 JQuery.js 文件之后 ,文件的加载顺序被严格限制住,依赖越多,依赖关系越混乱,越容易出错。

二、CommonJS

Nodejs 的出现,让 JavaScript 能够运行在服务端环境中,此时迫切需要建立一个标准来实现统一的模块系统,也就是后来的 CommonJS。

// math.js
exports.add = function(x, y) {
    return x + y;
}

// base.js
var math = require("./math.js");
math.add(2, 3);  // 5

// 引用核心模块
var http = require('http');
http.createServer(...).listen(3000);

CommonJS 规定每个模块内部,module 代表当前模块,这个模块是一个对象,有 id,filename, loaded,parent, children, exports 等属性,module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。

// utils.js
// 直接赋值给 module.exports 变量
module.exports = function () {
    console.log("I'm utils.js module");
}

// base.js
var util = require("./utils.js")
util();  // I'm utils.js module

或者挂载到 module.exports 对象下
module.exports.say = function () {
    console.log("I'm utils.js module");
}

// base.js
var util = require("./utils.js")
util.say();

为了方便,Node 为每个模块提供一个 exports 自由变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令。

《PHP设计模式指南》中文版
《PHP设计模式指南》中文版

《PHP设计模式》首先介绍了设计模式,讲述了设计模式的使用及重要性,并且详细说明了应用设计模式的场合。接下来,本书通过代码示例介绍了许多设计模式。最后,本书通过全面深入的案例分析说明了如何使用设计模式来计划新的应用程序,如何采用PHP语言编写这些模式,以及如何使用书中介绍的设计模式修正和重构已有的代码块。作者采用专业的、便于使用的格式来介绍相关的概念,自学成才的编程人员与经过更多正规培训的编程人员

下载
var exports = module.exports;

exports 和 module.exports 共享了同个引用地址,如果直接对 exports 赋值会导致两者不再指向同一个内存地址,但最终不会对 module.exports 起效。

// module.exports 可以直接赋值
module.exports = 'Hello world';  

// exports 不能直接赋值
exports = 'Hello world';

CommonJS 总结:  
CommonJS 规范加载模块是同步的,用于服务端,由于 CommonJS 会在启动时把内置模块加载到内存中,也会把加载过的模块放在内存中。所以在 Node 环境中用同步加载的方式不会有很大问题。

另,CommonJS模块加载的是输出值的拷贝。也就是说,外部模块输出值变了,当前模块的导入值不会发生变化。

三、AMD

CommonJS 规范的出现,使得 JS 模块化在 NodeJS 环境中得到了施展机会。但 CommonJS 如果应用在浏览器端,同步加载的机制会使得 JS 阻塞 UI 线程,造成页面卡顿。

利用模块加载后执行回调的机制,有了后面的 RequireJS 模块加载器, 由于加载机制不同,我们称这种模块规范为 AMD(Asynchromous Module Definition 异步模块定义)规范, 异步模块定义诞生于使用 XHR + eval 的开发经验,是 RequireJS 模块加载器对模块定义的规范化产出。

AMD 的模块写法:

// 模块名 utils
// 依赖 jQuery, underscore
// 模块导出 foo, bar 属性
<script data-main="scripts/main" src="scripts/require.js"></script>

// main.js
require.config({
  baseUrl: "script",
  paths: {
    "jquery": "jquery.min",
    "underscore": "underscore.min",
  }
});

// 定义 utils 模块,使用 jQuery 模块
define("utils", ["jQuery", "underscore"], function($, _) {
    var body = $("body");
    var deepClone = _.deepClone({...});
    return {
        foo: "hello",
        bar: "world"
    }
})
</script>

AMD 的特点在于:

  1. 延迟加载
  2. 依赖前置

AMD 支持兼容 CommonJS 写法:

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.sayHi();
  anotherModule.sayBye();

  exports.asplode = function (){
    someModule.eat();
    anotherModule.play();
  };
});

四、CMD

SeaJS 是国内 JS 大神玉伯开发的模块加载器,基于 SeaJS 的模块机制,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范.

CMD 模块的写法:

<script src="scripts/sea.js"></script>
<script>
// seajs 的简单配置
seajs.config({
  base: "./script/",
  alias: {
    "jquery": "script/jquery/3.3.1/jquery.js"
  }
})

// 加载入口模块
seajs.use("./main")
</script>

// 定义模块
// utils.js
define(function(require, exports, module) {
  exports.each = function (arr) {
    // 实现代码 
  };

  exports.log = function (str) {
    // 实现代码
  };
});

// 输出模块
define(function(require, exports, module) {
  var util = require('./util.js');
  
  var a = require('./a'); //在需要时申明,依赖就近
  a.doSomething();
  
  exports.init = function() {
    // 实现代码
    util.log();
  };
});

CMD 和 AMD 规范的区别:  
AMD推崇依赖前置,CMD推崇依赖就近:  
AMD 的依赖需要提前定义,加载完后就会执行。
CMD 依赖可以就近书写,只有在用到某个模块的时候再去执行相应模块。
举个例子:

// main.js
define(function(require, exports, module) {
  console.log("I'm main");
  var mod1 = require("./mod1");
  mod1.say();
  var mod2 = require("./mod2");
  mod2.say();

  return {
    hello: function() {
      console.log("hello main");
    }
  };
});

// mod1.js
define(function() {
  console.log("I'm mod1");
  return {
    say: function() {
      console.log("say: I'm mod1");
    }
  };
});

// mod2.js
define(function() {
  console.log("I'm mod2");
  return {
    say: function() {
      console.log("say: I'm mod2");
    }
  };
});

以上代码分别用 Require.js 和 Sea.js 执行,打印结果如下:  
Require.js:  
先执行所有依赖中的代码

I'm mod1
I'm mod2
I'm main
say: I'm mod1
say: I'm mod2

Sea.js:  
用到依赖时,再执行依赖中的代码

I'm main

I'm mod1
say: I'm mod1
I'm mod2
say: I'm mod2

五、UMD

umd(Universal Module Definition) 是 AMD 和 CommonJS 的兼容性处理,提出了跨平台的解决方案。

(function (root, factory) {
    if (typeof exports === 'object') {
        // commonJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // 挂载到全局
        root.eventUtil = factory();
    }
})(this, function () {
    function myFunc(){};

    return {
        foo: myFunc
    };
});

应用 UMD 规范的 JS 文件其实就是一个立即执行函数,通过检验 JS 环境是否支持 CommonJS 或 AMD 再进行模块化定义。

六、ES6 Module

CommonJS 和 AMD 规范都只能在运行时确定依赖。而 ES6 在语言层面提出了模块化方案, ES6 module 模块编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 模块化这种加载称为“编译时加载”或者静态加载。

3929677222-5c84b41e4ee53_articlex.jpg

写法:

// math.js
// 命名导出
export function add(a, b){
    return a + b;
}
export function sub(a, b){
    return a - b;
}
// 命名导入
import { add, sub } from "./math.js";
add(2, 3);
sub(7, 2);

// 默认导出
export default function foo() {
  console.log('foo');
}
// 默认导入
import someModule from "./utils.js";
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

另,在 webpack 对 ES Module 打包, ES Module 会编译成 require/exports 来执行的。

总结

JS 的模块化规范经过了模块模式、CommonJS、AMD/CMD、ES6 的演进,利用现在常用的 gulp、webpack 打包工具,非常方便我们编写模块化代码。掌握这几种模块化规范的区别和联系有助于提高代码的模块化质量,比如,CommonJS 输出的是值拷贝,ES6 Module 在静态代码解析时输出只读接口,AMD 是异步加载,推崇依赖前置,CMD 是依赖就近,延迟执行,在使用到模块时才去加载相应的依赖。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
jquery插件有哪些
jquery插件有哪些

jquery插件有jQuery UI、jQuery Validate、jQuery DataTables、jQuery Slick、jQuery LazyLoad、jQuery Countdown、jQuery Lightbox、jQuery FullCalendar、jQuery Chosen和jQuery EasyUI等。本专题为大家提供jquery插件相关的文章、下载、课程内容,供大家免费下载体验。

156

2023.09.12

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

334

2023.10.13

jquery删除元素的方法
jquery删除元素的方法

jquery可以通过.remove() 方法、 .detach() 方法、.empty() 方法、.unwrap() 方法、.replaceWith() 方法、.html('') 方法和.hide() 方法来删除元素。更多关于jquery相关的问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

406

2023.11.10

jQuery hover()方法的使用
jQuery hover()方法的使用

hover()是jQuery中一个常用的方法,它用于绑定两个事件处理函数,这两个函数将在鼠标指针进入和离开匹配的元素时执行。想了解更多hover()的相关内容,可以阅读本专题下面的文章。

515

2023.12.04

jquery实现分页方法
jquery实现分页方法

在jQuery中实现分页可以使用插件或者自定义实现。想了解更多jquery分页的相关内容,可以阅读本专题下面的文章。

312

2023.12.06

jquery中隐藏元素是什么
jquery中隐藏元素是什么

jquery中隐藏元素是非常重要的一个概念,在使用jquery隐藏元素之前,需要先了解css样式中关于元素隐藏的属性,比如display、visibility、opacity等属性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

128

2024.02.23

jquery中什么是高亮显示
jquery中什么是高亮显示

jquery中高亮显示是指对页面搜索关键词时进行高亮显示,其实现办法:1、先获取要高亮显示的行,获取搜索的内容,再遍历整行内容,最后添加高亮颜色;2、使用“jquery highlight”高亮插件。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

183

2024.02.23

jQuery 正则表达式相关教程
jQuery 正则表达式相关教程

本专题整合了jQuery正则表达式相关教程大全,阅读专题下面的文章了解更多详细内容。

51

2026.01.13

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号