浅析JavaScript中五种模块系统的使用

 更新时间:2022年11月28日 08:40:23   作者:前端西瓜哥  
模块系统是什么?简单来说,其实就是我们在一个文件里写代码,声明一些可以导出的字段,然后另一个文件可以将其导入并使用。今天我们来聊聊 JavaScript 的模块系统,感兴趣的可以了解一下

模块系统

模块系统是什么?简单来说,其实就是我们在一个文件里写代码,声明一些可以导出的字段,然后另一个文件可以将其导入并使用。

模块化的优点:

  • 文件里声明的变量会被隔离,不会暴露到全局,可以有效解决以往变量污染全局空间的问题;
  • 更容易看出代码之间的依赖关系,看文件头的的导入代码就知道;
  • 方便多人协作,各自开发自己的模块,而不冲突;
  • 不用担心文件引入的顺序;
  • 方便以文件为单位做单元测试;

模块化解决了变量污染、代码维护、依赖顺序问题。

CommonJS

CommonJS,或者叫 CJS,是 nodejs 选择的模块化标准。

导出模块的写法:

// 集中一起导出
module.exports = {
  userName: '前端西瓜哥',
  // ...
}

// 或分散导出
module.exports.userName = '前端西瓜哥';

// 或
exports.userName = '前端西瓜哥';

每个文件都可以访问到一个 module 对象,其下的 exports 属性是一个空对象,你可以给它加上属性,module.exports 将作为可以导出的代码部分。

exports 是一个别名,它指向 module.exports,用它能够少打一点字。

然后是导入模块的写法:

const Setting = require('./user');

// 或用解构写法,直接提取属性
const { userName } = require('./user');

// 或不使用任何导出内容,但希望指定对应模块文件的副作用(如给全局注入变量)
require('./user');

require 方法能够找到对应模块文件,提取出它的 module.exports 对象,引入到当前模块中。关于 require 怎么找到模块的过程,也是一篇长篇大论,有机会另开一篇文章再讲解了。

nodejs 现在最新版也支持用 ES Modules 的方式了,需要在 package.json 上加上  "type": "module"

ES Modules

ES Modules,或者叫 ESM,是 ES6 引入的新特性,是模块系统的真正标准。

在 script 标签下设置 type="module",可以开启模块化加载。

当然实际上生产环境我们是不会这么做的,只是用 ES Modules 写代码,然后打包,用传统的模式运行。

导出模块的写法:

// 集中一起导出
export {
  userName: '前端西瓜哥',
  // ...
}

// 或分散指定
export const userName = '前端西瓜哥';

// 一种特殊的导出:默认指定
export default user;

ES Modules 中,export 不是一个对象,准确来说都不是变量,而是新引入的关键字,用于指定要导出的变量。

然后是导入模块的写法:

// 导入需要的变量
import { userName } from './user.js';

// 导入 export default 对应的变量,这时候不需要花括号
import user from './user.js';

// 在命名冲突时,可以给导入的变量换个名字
import user as otherUser from './user.js';

// 将 export 组装成对象,包括 export default,对应 default 属性名
import * as obj from './user.js'

根据标准,导入的模块名是要提供 .js 后缀名的,因为实际上的 ES Module 是会请求一个 url 的,url 必须精准。

不过我们通常会使用打包工具,可以配置一下将其省略。比如 webpack,我们可以设置 resolve.extensions 配置项来设置后缀不存在时,拼上什么后缀去查找。

AMD

AMD 标准已过时,不必花太多精力学习,简单了解即可。

AMD,是 Asynchronous Module Definition 的缩写。这是一种异步的模块加载方案,是 ES Module 发布前的一种浏览器模块化方案。

CommonJS 不适合浏览器端,因为它的模块加载是同步的,浏览器需要请求模块文件,是异步的。

AMD 的特点是 依赖前置,即所有的依赖模块要在开头指定好。

实现 AMD 规范的典型库是 require.js,require.js 的使用如下。

导出模块写法为:

define(["./cart", "./inventory"], function (cart, inventory) {
  // 从 cart.js 和 inventory.js 拿到对应的导出内容
  // 然后通过返回值指定当前模块的导出内容
  return {
    color: "blue",
    size: "large",
    addToCart: function () {
      inventory.decrement(this);
      cart.add(this);
    },
  };
});

需要用 define 函数,第一参数为依赖的其他模块,然后第二个参数函数可以拿到这些模块导出的内容,然后这个函数的返回值就是当前文件的导出内容。

require 方法为主模块,也就是程序入口。

require(["a", "b"], function(util) {
  // a.js 和 b.js 加载完会执行这个函数
})

CMD

CMD 也过时了,甚至比 AMD 还小众,同样简单了解即可。

CMD,全称 Common Module Definition,也是浏览器模块化的一种方案,和 AMD 是同时代的产物。

但和 AMD 不同的是,它的特点是 依赖就近,在具体的用到某个模块的地方引入即可,接近 CommonJS。

代表库是 sea.js,我们看看语法。

模块写法:

define(function(require, exports, module) {
  // 导入 a 模块
 var a = require('./a');
  a.doSomething();
  
  // 导出当前模块
 module.exports = {
    name: 'b',
    start: function() {};
  };
});

主模块这样写:

seajs.use(['./a', './b'], function(a, b) {
  a.doSomething();
  b.start();
});

写法很像 Commonjs 了。

ES Modules 和 CommonJS 的区别

  • Commonjs 模块在 运行时 加载,ESM 在 编译时 确定依赖关系;
  • require 可以在代码的任何地方使用,比如在条件语句内,因为它是运行时同步加载的。import 需要写在模块文件最外层,不能在其他任何作用域内,且 import 会做提升;
  • require 永远是同步加载代码。import 一般也是同步的,但也能做动态加载,此时则是异步的。如 import('lang/zh.js').then(module) => { /* 获得语言包对象 */ }
  • CommonJS 的导出是值拷贝,而 ESM的导出是值引用。比如 a.js 导出一个值为 1 的 count,b.js 导入 a.js 拿到 count,然后 a.js 中的定时器修改了 count 的值为 2,然后在 b.js 再打印 count 的值。你会发现 Commonjs 下,count 还是 1,而在 ESM 下则变成了 2(说明编译时做了一些处理)。
  • ESM import 的变量是只读的,等价于用 const 声明;CommonJS 则随意用声明关键字,var、let、const 都可以。
  • CommonJS 可以导入 json 文件,ESM 不可以(实际上我们使用打包工具,通过转换器支持各种文件的导入);

UMD

模块标准这么多,需要一个个构建不同的模块文件可太麻烦了。如果我只希望发布一份代码,就让它运行在不同的模块系统中,有办法吗?

可以考虑用 UMD(Universal Module Definition),它能够同时在 CommonJS、AMD 运行,如果都不是,则会暴露到全局环境中。

下面是 webpack(5.74.0)设置 output.libraryTarget"umd" 后的打包结果的部分代码。

(function (root, factory) {
  // 判断是否为 commonjs
 if(typeof exports === 'object' && typeof module === 'object')
  module.exports = factory();
  // 判断是否为 requirejs
 else if(typeof define === 'function' && define.amd)
  define([], factory);
 else {
  var a = factory();
  for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
 }
})(self, () => {
  // 模块内容
  return {
    user: '前端西瓜哥'
  }
})

原理也不复杂,就是先通过判断不同模块系统提供的全局变量是否存在,且类型符合预期,就能知道是 CommonJS 还是 requirejs,然后使用它们对应的语法。都不匹配,那就暴露到全局。

虽然但是,UMD 无法支持 ES Modules,因为它的 import 不是变量,而是一个关键字,是编程语言层面的语法,在其他模块系统中存在会报错,且 import 只能在模块最外边使用。

结尾

模块系统太多,让我们有不小的心智负担,但正统在 ES Modules,nodejs 现在也完全支持 ES Modules 了。但因为历史问题,我们还是要保留 CommonJS。

到此这篇关于浅析JavaScript中五种模块系统的使用的文章就介绍到这了,更多相关JavaScript模块系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 利用JavaScript实现的10种排序算法总结

    利用JavaScript实现的10种排序算法总结

    这篇文章主要介绍了利用JavaScript实现的十种排序算法,主要介绍了冒泡,选择,插入,希尔,归并,快速,堆排,计数,桶排和基数,有感兴趣的小伙伴可以参考阅读本文
    2023-05-05
  • JavaScript实现定时页面跳转功能示例

    JavaScript实现定时页面跳转功能示例

    这篇文章主要介绍了JavaScript实现定时页面跳转功能,涉及javascript结合时间函数定时触发自定义函数功能操作技巧,需要的朋友可以参考下
    2017-02-02
  • 网页缓存文件批量改名工具

    网页缓存文件批量改名工具

    网页缓存文件批量改名工具...
    2006-10-10
  • JS实现自动变换的菜单效果代码

    JS实现自动变换的菜单效果代码

    这篇文章主要介绍了JS实现自动变换的菜单效果代码,可实现自动变换菜单选中项的技巧,涉及JavaScript定时函数触发页面样式属性变换的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09
  • 小程序animate动画实现直播间点赞

    小程序animate动画实现直播间点赞

    这篇文章主要为大家详细介绍了小程序animate动画实现直播间点赞,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • webpack中的模式(mode)使用详解

    webpack中的模式(mode)使用详解

    这篇文章主要介绍了webpack中的模式(mode)使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • 详解用js代码触发dom事件的实现方案

    详解用js代码触发dom事件的实现方案

    这篇文章主要介绍了详解用js代码触发dom事件的实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 关于导入excel时js转换时间的正确方式

    关于导入excel时js转换时间的正确方式

    这篇文章主要给大家介绍了关于导入excel时js转换时间的正确方式,以及js读取excel中日期格式转换问题的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • 详解使用webpack构建多页面应用

    详解使用webpack构建多页面应用

    这篇文章主要介绍了详解使用webpack构建多页面应用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • JavaScript实现留言板实战案例

    JavaScript实现留言板实战案例

    这篇文章主要给大家介绍了关于JavaScript实现留言板的相关资料,使用JavaScript来编写留言板功能相信大家都不陌生,文中给出了详细的示例代码,需要的朋友可以参考下
    2023-07-07

最新评论