本站消息

站长简介/公众号


站长简介:高级软件工程师,曾在阿里云,每日优鲜从事全栈开发工作,利用周末时间开发出本站,欢迎关注我的微信公众号:程序员总部,程序员的家,探索程序员的人生之路!分享IT最新技术,关注行业最新动向,让你永不落伍。了解同行们的工资,生活工作中的酸甜苦辣,谋求程序员的最终出路!

 价值13000svip视频教程,前端大神匠心打造,零基础前端开发工程师视频教程全套,基础+进阶+项目实战,包含课件和源码

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2021-03(417)

2021-04(177)

2021-05(161)

2021-06(101)

2021-07(7)

JS 模块化(CommonJS,AMD,CMD,ESM,UMD)

发布于2021-03-07 22:27     阅读(269)     评论(0)     点赞(21)     收藏(1)


早期的 JS 编码并没有模块化这个概念,写出来的代码就像“挂面”一样,有时候单个页面的 JS 代码就很长,维护起来十分麻烦,而且还存在“变量污染”、“命名冲突”和“缺少文件依赖”等问题。随着Node.js的出现和前后端分离开发模式的愈益流行,JS模块化技术也越来越成熟,其主要思想是利用“闭包”和“异步加载JS”来解决以上的问题。其实JS模块化的发展历程也是比较曲折的,这里给出一个其描述比较详细的链接:https://www.cnblogs.com/lvdabao/p/js-modules-develop.html

JS模块化的方式大致分为这么几种:CommonJSAMDCMDESM,UMD

CommonJS

CommonJS 是 Node.js 的模块化规范,在 Node.js 中每个 JS 文件就是一个模块。每个 JS 文件都有一个 module 对象,它又有一个 exprots 属性,顾名思义它就是JS文件默认导出的对象。

而且每个 JS 文件都有自己的私有作用域,其中的变量、常量、函数和类等对其他的 JS 文件隐藏,如果其他JS文件想要访问这个 JS 文件中的变量、常量、函数和类等,需要我们把变量、常量、函数和类等添加到 module.exports 对象上,然后通过 require 方法来引入该文件(实际上就是引入了该JS文件的 module.exports 对象)进行访问。

有一点需要注意的是:直接给 exports 赋值(exports = xxx)是起不到任何作用的,你可以通过 module.exports = xxx 来改变引用。

  1. // ./test.js
  2. // 这种方式起不到任何作用,它也不会被挂载到 global 上..
  3. exports = {a:1};
  4. console.log(module.exports) // {}
  5. exports.a = 2;
  6. console.log(module.exports) // { a: 2 }
  7. module.exports = {a:3};
  8. console.log(module.exports); // { a: 2 }
  9. b = '我是所有模块共享的变量'
  10. console.log(global.b) // 我是所有模块共享的变量
  11. exports = {a:1}; // 它不会被挂载到 global 上
  12. console.log(global.exports); // undefined
  1. // ./main.js
  2. var o = require('./test');
  3. console.log(o); // { a: 3 }

AMD

AMD (Asynchronous Module Definition),即异步模块定义,是客户端实现 JS 模块化的一种方式,由 Require.js 实现。

它借鉴了 CommonJS 的思想,使用了 CommonJS requiremoduleexports 特性,其主要方法有 configdefinerequire 。

  • config 用于全局配置,它可以配置一个 paths 属性,异步加载指定链接的 JS ,如果该 JS 执行的结果不能通过全局对象访问,那么使用的时候就需要把它定义为 RequireJS 的一个模块。
  • define 的作用是定义一个模块,当然它还可以引入其他的模块依赖,这里不作详细介绍。
  • require 的作用是消费定义的模块,即引入模块并使用它。

在使用 Require.js 的时候,需要在标签属性上添加 data-main 属性,当 Require.js 加载完后会去加载 data-main 里面指定的 JS。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>Require.js</title>
  5. </head>
  6. <body>
  7. <script data-main="main.js" src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"></script>
  8. </body>
  9. </html>
  1. // ./main.js
  2. /**
  3. * webpack 不支持 require.config,
  4. * 这部分代码编译后为 undefined
  5. */
  6. /**
  7. * 配置模块加载位置,
  8. * Require.js 根据路径去加载对应的 JS,
  9. * 一般是引入第三方 JS 时使用
  10. */
  11. require.config({
  12. paths: {
  13. test: './test'
  14. }
  15. });
  16. /**
  17. * 这种方式 webpack 可以识别,不过文件需存在
  18. *
  19. * 第1种方式:根据 JS 文件路径去加载JS,
  20. * 等 test 对应的 JS 加载并执行完后就执行 callback
  21. */
  22. require(['./test'], function(test) {
  23. console.log('test2', test);
  24. });
  25. /**
  26. * 如果不是以别名或 '/', './' 开头,
  27. * webpack 会使用 nodejs 的 require 方式
  28. * 从 node_modules 一层层的网上找,找不到
  29. * 则报错。所以 wepack 和 require.js 的
  30. * 处理方式不一致,webpack 会因为找不到对
  31. * 应的模块而报错。
  32. */
  33. /**
  34. * 第2种方式:根据 id 去加载JS,
  35. * 等 test 对应的 JS 加载并执行完后就执行 callback
  36. */
  37. require(['test'],function(test){
  38. console.log('test1', test);
  39. });
  40. /**
  41. * 等定义的 uitl1 模块执行完后,调用 callback
  42. */
  43. require(['util1'],function(util){
  44. console.log('util1', util);
  45. });
  46. /**
  47. * webpack 编译后的代码只能导出最后一个,
  48. * 它默认一个文件只能有一个 define ,即
  49. * 一个文件一个模块。
  50. */
  51. /**
  52. * 同一文件中定义一个 util1 模块
  53. */
  54. define('util1', function() {
  55. return {desc: 'util1'};
  56. });
  57. /**
  58. * 同一文件中定义一个 util2 模块
  59. */
  60. define('util2', function() {
  61. return {desc: 'util2'};
  62. });
  1. // ./test.js
  2. // 定义一个模块,实际上也可以依赖其他模块的,这里不做研究
  3. // define({ a: 1, b: 2, c: 3 });
  4. // 还可以这么写
  5. define(function(require, exports, module) {
  6. // 可以这么写
  7. // return {a: 1, b: 2, c: 3};
  8. // 也可以这么些
  9. // exports.a = 1;
  10. // exports.b = 2;
  11. // exports.c = 3;
  12. module.exports = {a: 1, b: 2, c: 3};
  13. });

CMD

CMD(Common Module Definition),即普通模块定义,它看上去像是 AMD CommonJS 的结合体,由 Sea.js 实现。在 CMD 的规范里,一个文件表示一个模块。

它同样使用了 CommonJS 里 requiremoduleexports 特性,常用方法有 configdefinerequire 这三个,这些方法和 AMD 中对应的方法类似,这里就不再赘述了。

另外它还有一个 use 方法,这个是用于加载入口模块的,和 data-mian 的作用类似,用来执行入口 JS 文件。和 AMD 不同的是,CMD 支持使用 require.async(id) 实现懒加载。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>Sea.js</title>
  5. </head>
  6. <body>
  7. <script src="https://cdn.bootcdn.net/ajax/libs/seajs/3.0.3/sea.js"></script>
  8. <script>
  9. /**
  10. * 使用 data-main 只支持加载单模块入口,
  11. * 使用 use 方法支持加载多模块入口
  12. */
  13. seajs.use(['./main'], (a) => {
  14. console.log('exec entry js', JSON.stringify(a)); // exec entry js {"desc":"entry module","data":{"a":1,"b":2,"C":3}}
  15. });
  16. </script>
  17. </body>
  18. </html>
  1. // ./main.js
  2. // webpack 不会“干掉”这段代码
  3. // seajs 的全局配置
  4. seajs.config({
  5. // 配置加载 JS 的基础路径
  6. base: '.',
  7. // 别名
  8. alias: {
  9. 'test': 'test'
  10. },
  11. charset: 'utf-8',
  12. timeout: 20000,
  13. debug: false
  14. });
  15. // 定义个模块,模块标识由文件名决定
  16. define(function(require, exports, module) {
  17. /**
  18. * 引入 test 模块,如果使用 var a = require('test'),
  19. * sea.js 支持这种写法, 但是 webpack 不支持,它会因
  20. * 为找不到模块而报错,其原因和用 webpack 编译 AMD 模
  21. * 块化代码一样,需要换为路径才能解析成功。
  22. */
  23. var a = require('./test');
  24. // 也可以写成
  25. // exports.desc = 'entry module';
  26. // exports.data = a;
  27. // 也可以写成
  28. // module.exports = {desc: 'entry module', data: a};
  29. return {
  30. desc: 'entry module',
  31. data: a
  32. }
  33. });
  1. // ./test.js
  2. // 定义一个模块
  3. define((require, exports, module) => {
  4. // 也可以写成
  5. // exports.a = 1;
  6. // exports.b = 2;
  7. // exports.c = 3;
  8. // 也可以写成
  9. // return {a: 1, b: 2, c: 3}
  10. module.exports = {
  11. a:1,
  12. b:2,
  13. C:3
  14. }
  15. });

ESM

ESM(ECMA Script Modules),即 ES6 的模块化规范。

  • 通过 export 导出变量、常量、函数或类等,export 支持默认和别名导出
  • 通过 import 引入变量、常量、函数或类等,import 支持解构、别名和星号导入

但是因为目前有些低版本的浏览器还是不支持 ESM ,所以一般需要将 ESM 转成其他的模块化方式,这个过程一般由 TSC 编译器或者 Webpack 等自动化构建工具去转换。

  1. // ./main.js
  2. // 星号别名导出,导出的是整个的模块( Module 的实例)
  3. import * as obj from './test.js';
  4. console.log(obj);
  5. // 解构导入,这里从 Module 实例中解构 a
  6. import {a, b} from './test.js'
  7. console.log(a); // {a: 1}
  8. console.log(b); // {b: 2}
  9. // 导入默认,xxx 不是关键字就行了
  10. // 前提是被导入模块必须有 default 默认导出
  11. import xxx from './test.js';
  12. console.log(xxx); // {desc: "我是默认导出"}
  1. // ./test1.js
  2. // 将 a 、b 和 default 变为 Module 实例的属性,并导出 Module 实例
  3. export var a = { a: 1 }
  4. export var b = { b: 2 };
  5. export default { desc: '我是默认导出' };
  6. // 以上导出相当于下面这一句, 不过这里 default 是关键字,会有错误,
  7. // 所以这里的花括号并不是对象!它只是一种表示形式,类似于解构的反向操作。
  8. // export { a, b , default }

UMD

UMD(Universal Module Definition),即通用模块定义,是 CommonJS 和 AMD 模块化标准的集合,它还兼容 “Root” 模式,支持从全局对象上获取模块。

它不能直接支持 ESM ,但是上面说过 ESM 会变为 UMD 支持的模块化中的一种,也算是间接地支持 ESM 了。

  1. // ./main.js
  2. // UMD
  3. (function(root, factory) {
  4. if (typeof define === 'function' && define.amd) {
  5. console.log('main', 'AMD');
  6. // AMD,定义一个以文件名为模块名的模块,并导入 vue 对应的依赖
  7. define(['./test'], factory);
  8. } else if (typeof exports === 'object') {
  9. console.log('main', 'CommonJS');
  10. // CommonJS
  11. module.exports = factory(require('./test'));
  12. } else {
  13. console.log('main', 'Root');
  14. // 浏览器全局变量(root 即 window)
  15. root.yyy= factory(root.test);
  16. }
  17. }(this, function(a) {
  18. console.log(a);
  19. }));
  1. // ./test.js
  2. // UMD
  3. (function(root, factory) {
  4. if (typeof define === 'function' && define.amd) {
  5. console.log('test', 'AMD');
  6. // AMD,定义一个以文件名为模块名的模块,并导入 vue 对应的依赖
  7. define(factory);
  8. } else if (typeof exports === 'object') {
  9. console.log('test', 'CommonJS');
  10. // CommonJS
  11. module.exports = factory();
  12. } else {
  13. console.log('test', 'Root');
  14. // 浏览器全局变量(root 即 window)
  15. root.test= factory();
  16. }
  17. }(this, function() {
  18. console.log('test');
  19. return {
  20. desc: 'test'
  21. }
  22. }));

可以看出,UMD 没有自己的实现,而是 AMD CommonJS Root 模式的集合。

实践测试

https://github.com/zzp-dog/reception-learn/tree/master/webpack_review/module

今日分享

1.为了寻找你,我搬进了鸟的眼睛,经常盯着路过的风,也忘了听猎人的枪声。

2.与海为邻,住在无尽蓝的隔壁,却无壁可隔,一无所有,却拥有一切。

3.只要想起一生中后悔的事,梅花便落满了南山。

4.每个人都属于有自己的一片森林,我们也许从来不曾去过,但是它一直在那里,总会在那里。迷失的人迷失了,相逢的人,会再相逢。 

原文链接:https://blog.csdn.net/qq_36605165/article/details/114438620




所属网站分类: 技术文章 > 博客

作者:代码搬运工

链接:http://www.qianduanheidong.com/blog/article/33542/31c504d74af8e3849e70/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

21 0
收藏该文
已收藏

评论内容:(最多支持255个字符)