前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >写给前端新手看的一些模块化知识

写给前端新手看的一些模块化知识

作者头像
用户1097444
发布2022-06-29 15:02:52
2930
发布2022-06-29 15:02:52
举报
文章被收录于专栏:腾讯IMWeb前端团队

一、 为什么需要模块化

以前没有模块化时,我们可能会按如下方式划分模块: 通过 <script> 标签引入各个文件,把每个文件看成是一个模块,每个模块的接口通常是暴露在全局作用域下的,也就是定义在 window 对象中。

代码语言:javascript
复制
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>

如果通过这种方式做模块化,当项目变得越来越大时,很容易造成全局变量冲突,项目也会变得越来越难以管理。

因此我们需要找到一种合适的方式来帮助我们模块化,模块化能给我们带来哪些好处呢?具体来说,主要包含以下几个方面:

  1. 减少全局变量污染
  2. 控制依赖
  3. 增强代码的可维护性
  4. 增加代码的复用性
  5. 分治思想的实践

二、模块化标准现状

1. CommonJS

规范
  1. 模块的标识应遵循一定的书写规则。
  2. 定义全局函数 require(dependency),通过传入模块标识来引入其他依赖模块,执行的结果即为别的模块暴漏出来的 API。
  3. 如果被 require 函数引入的模块中也包含外部依赖,则依次加载这些依赖。
  4. 如果引入模块失败,那么 require 函数应该抛出一个异常。
  5. 模块通过变量 exports 来向外暴露 API,exports 只能是一个 Object 对象,暴露的 API 须作为该对象的属性。
使用方式

导出模块:

代码语言:javascript
复制
// math.js
var num = 0;
function add(a, b) {
  return a + b;
}
module.exports = {
  num: num,
  add: add
}

加载模块:

代码语言:javascript
复制
// 引入自定义的模块时,参数包含路径,可省略.js
// 引入核心模块时,不需要带路径,如var http = require("http");
var math = require('./math');
math.add(1, 2) //3
优点
  1. 简单易用。
  2. 解决了模块依赖的问题。
  3. 减少了全局变量污染。
缺点
  1. 无法在浏览器端使用。
  2. 无法非阻塞的并行加载多个模块。

2. AMD(Async Module Definition)

代表作 RequireJS。

规范
  1. 模块的标识遵循 CommonJS Module Identifiers。
  2. 定义全局函数 define(id, dependencies, factory),用于定义模块。dependencies 为依赖的模块数组,在 factory 中需传入形参与之一一对应。
  3. 如果 dependencies 的值中有 require、exports 或 module,则与 CommonJS 中的实现保持一致。
  4. 如果 dependencies 省略不写,则默认为 ['require', 'exports', 'module'],factory 中也会默认传入三者。
  5. 如果 factory 为函数,模块可以通过以下三种方式对外暴漏 API:return 任意类型;exports.XModule = XModule、module.exports = XModule
  6. 如果 factory 为对象,则该对象即为模块的导出值。
使用方式

定义模块

  • 独立模块
代码语言:javascript
复制
define({
    method1: function() {},
    method2: function() {},
});

// 函数的返回值就是输出的模块
define(function () {
    return {
        method1: function() {},
        method2: function() {},
    };
});
代码语言:javascript
复制
有依赖的模块define(['module1', 'module2'], function(m1, m2) {
   ...
});
// module1模块和module2模块指的是,当前目录下的module1.js文件和module2.js文件,等同于写成['./module1', './module2']

需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。

调用模块

代码语言:javascript
复制
require(['foo', 'bar'], function ( foo, bar ) {
        foo.doSomething();
});
优点
  1. 可以用于浏览器。
  2. 异步加载模块。
  3. 可以并行加载多个模块。
缺点
  1. 提高了开发成本。
  2. 不能按需加载,而是提前加载所有的依赖。

* RequireJS 从 2.0 开始,也改成了可以延迟执行。

3. CMD (Common Module Definition)

CMD 是 sea.js 在推广过程中对模块定义的规范化产出,属于 CommonJS 的一种规范。

使用方式

定义模块:

代码语言:javascript
复制
define(function (require, exports, module) {
  var add = function (a, b) {
    return a + b;
  }
  exports.add = add;
})

使用模块:

代码语言:javascript
复制
seajs.use(['math.js'], function (math) {
  var sum = math.add(1, 2);
});
优点
  1. 实现了浏览器端的模块化加载。
  2. 可以按需加载。
  3. 依赖就近,延迟执行。
缺点
  1. 依赖 SPM 打包,模块加载逻辑偏重。

4. UMD (Universal Module Definition)

UMD 是一种 JavaScript 通用模块定义规范,让你的模块能在 JavaScript 所有运行环境中发挥作用。

规定如下:

  1. 优先判断是否存在 exports 方法,如果存在,则采用 CommonJS 方式加载模块;
  2. 其次判断是否存在 define 方法,如果存在,则采用 AMD 方式加载模块;
  3. 最后判断 global 对象上是否定义了所需依赖,如果存在,则直接使用;反之,则抛出异常。

5. ES Module

使用方式

导出模块:

代码语言:javascript
复制
// 导出
exportfunction hello() { };
exportdefault {
  // ...
};

引入模块:

代码语言:javascript
复制
import { readFile } from'fs';
import React from'react';
优点
  1. 语法层面的支持,使用简单。
缺点
  1. 浏览器还没有完全兼容,必须通过工具转换成标准的 ES5 后才能正常运行。

浏览器的支持情况

三、模块化的演变历史

2009 年的时候,Mozilla 的工程师 Kevin Dangoor 与同事们一起制定了一套 JsaveScript 模块化标准,并取名为 ServerJS。ServerJS 最早是用于服务端的,目的是为了在自动化测试的工作中提供模块化导入的功能。之后 ServerJS 更名为了 CommonJS。

同年 9 月,Ryan Dahl 创造了 Node.js,而一开始 Node.js 还没有包管理工具,随后采用 CommonJS 规范的 npm (即 node package manager) 诞生了。随着 Node.js 的快速发展,CommonJS 规范也渐渐进入广大前端开发者的视野。至此,JavaScript 第一个模块化规范,也正式登入历史舞台。

随着 npm 的流行,广大前端开发者也希望引入这种模块化的方案到日常的开发工作中。但是 CommonJS 只能应用于服务端,因此势必需要重新制定规范标准。而此时,关于如何制定新的标准,主要有三大流派:

保守派

CommonJS 已经在服务端成功应用了,那么在浏览器加载模块前,我们先通过工具将模块转换成浏览器能够执行的代码就可以了。该想法与如今的 babel 等工具思路是相似的,通过将高版本的代码转换为低版本的代码,目的都是为了兼容。而 browserify 就是这一观点下的产物。

激进派

浏览器与服务端存在很大的差异,我们应该根据浏览器的特点,放弃 require 的方式而是使用回调的方式引入模块。将同步加载模块更改为异步加载模块。

中间派

CommonJS 中的 require 等规范还是有可取之处,在尽可能与现有的 CommonJS 规范保持一致的前提下,我们也可以引入一些好的特性,比如 exports 可以导出更多类型而不是局限于 Object。

各自进展

激进派

激进派的 James Burke 在 2009 年开发出了 RequireJS 模块加载器。2011 年,在 RequireJS 社区的基础上,诞生了 AMD(Async Module Definition)社区。AMD 是第一个支持浏览器端的 Javascript 模块化解决方案,RequireJS 迅速被广大开发者熟知和采用。

中间派

中间派的故事比较曲折。CommonJS 的主要贡献者之一 Wes Garland 给出了一个名为 BravoJS 的实现。Wes Garland 本人是学院派,理论功底十分强,但写出的作品却不很实用。另一位实战派大佬提出了 Modules/Wrappings 的方案,并给出了一个名为 FlyScript 的实现。而两位大佬对具体的实现发生了一些争论,最后以 FlyScript 的 GitHub 仓库被删除而结束这段恩怨。

2011 年 4 月,阿里巴巴的前端大佬玉伯,因为给 RequireJS 提出的建议被不断拒绝后,在参考了 AMD 和 CommonJS 的方案后,便自己写了一个模块加载器 Sea.JS,同时提出了 CMD 规范。CMD 规范的主要内容与 AMD 相似,但是保留了 CommonJS 中延迟加载和就近声明的特性。

UMD

2014 年 9 月,美籍华裔 Homa Wong 提交了 UMD 第一个版本的代码。UMD 即 Universal Module Definition 的缩写,它本质上并不是一个真正的模块化方案,而是将 CommonJS 和 AMD 相结合。

ES Module

2016 年 5 月,经过了两年的讨论,ECMAScript 6.0 终于正式通过决议,成为了国际标准。在这一标准中,首次引入了 import 和 export 两个 JavaScript 关键字,并提供了被称为 ES Module 的模块化方案。2017 年 9 月, Chrome 的 61.0 版本首次在浏览器端支持了 ES Module。目前已经有很多浏览器原生支持了 ES Module。而伴随着 ES Module 的兴起,也出现了像 Vite 这样的构建工具。

紧追技术前沿,深挖专业领域

扫码关注我们吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 为什么需要模块化
  • 二、模块化标准现状
    • 1. CommonJS
      • 规范
      • 使用方式
      • 优点
      • 缺点
    • 2. AMD(Async Module Definition)
      • 规范
      • 使用方式
      • 优点
      • 缺点
    • 3. CMD (Common Module Definition)
      • 使用方式
      • 优点
      • 缺点
    • 4. UMD (Universal Module Definition)
      • 5. ES Module
        • 使用方式
        • 优点
        • 缺点
        • 保守派
        • 激进派
        • 中间派
    • 三、模块化的演变历史
      • 各自进展
        • 激进派
        • 中间派
        • UMD
        • ES Module
    相关产品与服务
    命令行工具
    腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档