浅谈模块化,小白看完也能恍然大明白

1,754 阅读4分钟

这篇文章花了我两天时间,一开始觉得自己会模块化了,写着写着感觉好多地方都有点模糊,然后开始不断的翻笔记,搜资料。

写完之后,感觉还是美好的,学到东西了,略微打通了一些经脉。

image.png

正文开始

什么是模块

  • 将一个复杂的程序按照一定规则封装成几个块,并进行组合在一起;
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;

为什么要使用模块化

  • 私有的作用域,避免命名冲突,减少命名空间污染
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

模块化的发展历史

函数时期

function fn() { }

命名空间时期:模块化的思路雏形初显

var student = {
    name: 'luyi',
    getScore: function() {

    }
}

student.name
student.getScore

闭包时期

// 递增的小栗子
function test() {
  let count = 1
  return function(){
    count++
      console.log(count)
  }
}
var a = test()
a()

解决依赖问题 : 此处已有现代模块化(vue)的感觉了,模块各自独立,模块之间有依赖(global, $),逻辑层操作视图层

(function(global, $) {
    var name = 'Chenluo Notes';
    let data = 'www.baidu.com'
    
    // 操作数据的函数
    function doSth() {
      //用于暴露有函数
      console.log(`foo() ${data}`)
      $('body').css('background', 'red')
    }
    
    function getName() {
        return name
    };

    global.student = { name, doSth, getName };

})(window, jquery);

console.log(window.student)

CJS规范(模块化的一种规范)

此时的模块化已经具备了两个必要条件

  • 隔离变量:每个模块有其独立作用域
  • 相互通信:模块之间通过modules,modules.exports暴露了入口,可以互相通信
var module = {
    exports: {

    }
}

(function(modules, exports, require) {

    const $ = require('../juery.min.js')
    var name = 'Chenluo Notes';

    function getName() {};

    modules.exports = { name, getScore };

    exports.name = name;
    // exports = {name, getScore}

})(modules, modules.exports, require);

// require 函数,以一个路径作为参数,函数的功能是,读取路径指向的 js 文件的地址,
// 然后把文件读出来,以 js 的形式解析。

时代的车轮滚滚向前~~~

函数 => 命名空间 => 闭包 => 解决依赖 => cjs模块化规范

可以看出,技术的发展和迭代都是为了解决人们的一些痛点,然后不断进行创新迭代优化。就如最近大火的ChartGPT, 不也是为了解放劳动力,提高生产力。让资源更集中,查询更便捷,想法落地更快速。

当然,也引发了一些其他的问题~~~ 毕竟,未知的总是让人焦虑。

貌似有点跑偏,让我们接着奏乐接着舞(学模块化)!!!

image.png

什么是模块化规范

模块化规范是为了解决隔离变量相互通信的问题,上文有提到

比如以下场景:

场景一,传统用script标签引入js文件的一些痛点~~~

// 传统引入js模块的方法
<script src="./A.js"></script>
<script src="./B.js"></script>

场景二,没有依赖关系

  • A和B两个js我想B先执行完再执行A
  • 我要拿到B返回的数据,放到A里去执行

场景三,不能按需解析

  • 我要用到A时,A才会被解析(函数调用,接口请求)

基于以上场景,再实际开发和维护过程中,就比较痛苦

所以模块化规范就诞生啦!!!

utils工具库想必有开发经验的小伙伴肯定不陌生,这算是最常见的模块化。

每个项目大家都约定熟成的封装一些公共的工具函数在utils里,方便在各个页面使用,提高开发效率。俗称造轮子

utils在vue项目中一般使用ESM规范导入,为了更好的进行模块之间的依赖,和按需解析。下文中引入的是一个文件,如果utils文件夹下面有多个文件,彼此之间通过ESM进行依赖,做一些逻辑处理,效果会更明显点。

// 引用方式: esm规范
import { isURL } from '@/utils'
console.log(isURL('https://juejin.cn'))

// 引用方式: cjs规范
let {timestampToHMS} = require("../../utils/index.js");
console.log(timestampToHMS(211312))

模块化规范有哪些

AMD, CMD, CJS(common js), ESM(es模块), UMD,一般共五种模块化规范

AMD 是一个已经过时的规范,和CMD一样都是异步的

一,AMD模块化规范, RequireJS


define('a', function () {
    console.log('a load')
    return {
      run: function () { console.log('a run') }
    }
  })
  
  define('b', function () {
    console.log('b load')
    return {
      run: function () { console.log('b run') }
    }
  })
  
  require(['a', 'b'], function (a, b) {
    console.log('main run') // 🔥
    a.run()
    b.run()
  })
  
  // 依赖前置,引用的时候就执行方法
  // a load
  // b load
  // main run
  // a run
  // b run

二,CMD模块化规范, sea.js


// CMD sea.js
define('a', function (require, exports, module) {
    console.log('a load')
    exports.run = function () { console.log('a run') }
  })
  
  define('b', function (require, exports, module) {
    console.log('b load')
    exports.run = function () { console.log('b run') }
  })
  
  define('main', function (require, exports, module) {
    console.log('main run')
    var a = require('a')
    a.run()
    var b = require('b')
    b.run()
  })
  
  seajs.use('main')
  
    // 依赖后置
  // main run
  // a load
  // a run
  // b load
  // b run

三,CJS模块化规范,主要用于服务端编程, nodejs 最早实现了它,是同步的

(function(modules, exports, require) {
  // require 函数,以一个路径作为参数,函数的功能是,读取路径指向的 js 文件的地址,
  // 然后把文件读出来,以 js 的形式解析。

  const $ = require('../juery.min.js')
  var name = '张三';

  function getName() {};

  modules.exports = { name, getScore };

  exports.name = name;
  // exports = {name, getScore} 不能这样用,会使引用断了

})(modules, modules.exports, require);

四,ESM模块化规范,现代浏览器规范

import XXX from 'xxx';
export const xxxx;
export default xxxxxxx;
// 或者
// 浏览器中实现了它,ES6规范是浏览器的JS引擎识别JS代码的一种规则
<script type='module'>
    import XXX from 'xxx';
</script>

五,UMD模块化规范, 通用模块定义,配置了多个模块,兼容AMD CMD CJS script浏览器标签

延伸一点点

  • 当一个UMD模块在ESM环境中使用时,可以把它当作普通的JS库来使用,通过script标签引入即可。
  • 而当一个ESM模块在UMD环境中使用时,则需要使用一些兼容策略,例如将ESM模块编译成UMD规范,并将其作为JS库来使用。

模块化和工程化的关系

提到工程化,不得不提webpack 和 rollup。

他们两个都是为了把项目中的高阶语法(ES6写法)打包成浏览器可以识别的低级语法(IIFE),也就是小伙伴们vue项目中dist目录下的文件。

上面的五种模块化规范,webpack 和 rollup都可以进行配置打包。

下面是小白会遇到的三个疑问(不要问我为什么知道)

一,打包后,通过配置生成的这五种规范,浏览器可以解析吗?如果不可以,为什么webpack/rollup中可以这样配置?

ESM规范是浏览器的亲儿子,也是我们上文一直提到的。ESM规范是可以被浏览器解析的。

  • 可以直接在html文件中script type="module"使用improt export
  • 也可以直接在服务器运行的浏览器环境中直接使用es6语法,但是不同的浏览器对模块化规范的支持程度有所不同。

那其他几种规范存在的意义是什么呢

  • CJS是给服务器使用的,主要用于服务端编程,因为同步意味着阻塞可能。如果非要在浏览器中解析,也可以通过Browserify来把CJS的代码转成普通js(不带require)
  • AMD和CMD是为了解决浏览器端异步加载的问题,需要通过模块加载器来让浏览器解析。AMD(requireJS), CMD(seajs)
  • UMD综合了 CJS 和 AMD 规范的优点,兼容各种运行环境,可以在浏览器和 Node.js 环境中运行。UMD 规范支持多种模块加载器,可以满足不同项目的需求,比较灵活。

二,开发过程中,webpack可以解析这五种规范吗?

  • 当然可以,项目中不论是import(ESM),require(CJS)等写法,webpack都可以解析,并且可以打包成你指定的模块化规范。

三,webpack 和 rollup有啥区别呢?

  • Rollup是提供一个充分利用ESM各项特性的高效打包器,更加专注于打包 JavaScript 库和组件。主要优势在于可以生成非常小的包,因为它采用 ES6 模块语法并利用 Tree shaking 技术进行优化。这使得与其生成的包一起使用的代码量最小化。

  • Webpack 的设计目标是支持复杂的应用程序打包。它可以将许多不同类型的代码(包括 JavaScript、CSS、图片等)打包为一个或多个文件,同时还支持功能强大的开发工作流,如热替换、代码分割和动态导入。

完结

这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

image.png