前端模块化是什么?

166 阅读7分钟

什么是模块化

模块化顾名思义就是模块,其目的就是将程序划分成一个个小的结构; 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构;并将暴露导出自己的变量、函数、对象等给其他结构使用。

在阐述如何在Node.js中使用模块化之前,我们先来了解一下普通的JavaScript如何实现模块化。

普通JS实现模块化

这里举个栗子,一共有三个 .js 文件, 需要导入到index.html 中使用;

第一个文件 bar.js

通常我们会使用常见的 立即执行函数(IIFE) 实现块级作用域来模拟模块化

 var moduleBar = (function () {
   var name = "巫师";
   console.log('bar中的name:' + name);
   return {
     name
   }
 })()

定义了一个 moduleBar 的变量, 里面封装了一个立即执行函数,并且声明了name与age属性,最后将他们返回

第二个文件 foo.js

 var name = "骑士";
 console.log('foo中的name:' + name);
 ​

立即执行函数,声明一个name变量,值为 "骑士"

第三个文件 baz.js 单独的打印了 namemoduleBar.name

 console.log(name);
 console.log(moduleBar.name);

最后导入index.html,在 index.html 文件中通过 <script>标签 将他们分别引入:

 // index.html
   <script src="./bar.js"></script>
   <script src="./foo.js"></script>
   <script src="./baz.js"></script>

最后打印结果为:

 `bar.js` bar中的name:巫师
 `foo.js` foo中的name:骑士
 `baz.js` 骑士
 `baz.js` 巫师

由上可见,我相信有写同学已经意识到单独引用的问题了,如果index.html中将baz.js个文件位置换了呢,那 moduleBar是肯定无法访问得到的;

缺点

因此总结了以下3个缺点:

  1. 必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用;
  2. 代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写;
  3. 在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况;

因此,即使实现了模块化,但是过于简单且没有规范,我们需要有一种核心功能包括可以让模块本身可以导出暴露的属性,并且模块又可以导入自己需要的属性的一种规范,即 CommonJs 规范

CommonJS 规范

我们需要知道CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS,平时我们也会简称为 CJS;而NodeJS是CommonJS在服务器端一个具有代表性的实现,所以,Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发:

  • 在Node.js中,每一个 .js 文件就是一个单独的模块
  • 这个模块中包括 CommonJS 规范的核心变量:exportsmodule.exportsrequire
  • 我们可以使用这些变量来方便的进行模块化开发

使用CommonJS规范进行模块化开发

首先我们简单的实现一个导入导出

我们需要两个文件 a.jsb.js

  • a.js 文件中使用上述所说的 export 进行导出指定数据

     const name = "shrimpsss";
     const age = 22;
     ​
     // 使用 exports 将 name 属性与 age 属性暴露出去
     exports.name = name;
     exports.age = age;
    

    exports是一个对象,我们可以在这个对象中添加需要导出的属性

  • b.js 文件中使用 require 来接收数据

     // 使用 require 导入当前目录下的 a.js 文件中暴露的数据
     const data = require('./a.js')
     ​
     data //  { name: 'shrimpsss', age: 22 }
    

    当然,通常我们使用解构赋值来接收

     const { name, age } =  require('./a');
     name // shrimpsss
     age // 22
     ​
     // *注意:属性名要与所接收的属性一致,否则接收不到, 如下
     // 错误示范
     const { name, ages } =  require('./a');
     name // shrimpsss
     ages // undefined
    

现在我们就简单的了使用CommonJs规范进行模块化开发了

CommonJS规范缺点

但其实CommonJS规范也是有缺点的;

因为CommonJS加载模块是同步的,同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;虽然这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;但是如果在浏览器上就不一样了,因为浏览器加载 js 文件需要先从服务器将文件下载下来之后再加载运行,那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作;

因为它会将我们的代码转成浏览器可以直接执行的代码等等,所以在浏览器中我们通常不使用CommonJS规范:

在早期为了可以在浏览器中使用模块化,通常会采用 AMD 或 CMD ,而现在主要使用的是ES Modules,简单列一下各自的特性与区别

AMD 规范与 CMD 规范

AMD(Asynchronous Module Definition) 主要是应用于浏览器的一种模块化规范,它采用的是异步加载模块;

AMD实现的比较常用的库是 RequireJS 是一个 JavaScript 文件和模块加载器curl.js 一个小型且非常快速的 AMD 兼容 异步执行器

CMD(Common Module Definition) 规范也是应用于浏览器的一种模块化规范,它也采用了异步加载模块;

较好的实现方案:Sea.js 追求简单、自然的代码书写和组织方式

感兴趣的话可以自行去了解一下,这里不花太多时间讲述

ES Module规范

ES Modules(ESM)是用于处理模块的ECMAScript标准 ,它和CommonJS的模块化有一些不同之处:

一方面它采用编译期的静态分析,并且也加入了动态引用的方式,另一方面采用ES Module将自动采用严格模式;并且ES Module模块采用 export(将模块内的内容导出)和 import(从其他模块导入内容)关键字来实现模块化

ES Module 与 CommonJS 的区别

环境的区别: CommonJS 模块是 Node.js 专用的,与 ES Module 模块不兼容,但ES Module模块在浏览器和Node.js中都可以用

语法的区别: CommonJS 模块使用 require()module.exports,ES Module模块使用importexport

文件的区别: 在NodeJS使用模块化,需要将 CommonJS 脚本的后缀名都改成.cjs,ES Module模块采用.mjs后缀文件名或者修改package.json里面的文件,type字段为modulecommonjs

导入的区别: CommonJS 模块的 require() 是同步加载模块,而 ES Module 模块的import命令是异步加载,它有一个独立模块依赖的解析阶段

输出的区别:CommonJS 模块输出的是一个值的拷贝,其他模块内部的变化就影响不到这个值;而ES Module模块输出的是值的引用,不会缓存值,所以模块里面的变量绑定其所在的模块

CommonJs 与 ES Module 的加载过程

CommonJS模块加载js文件的过程是运行时加载的,并且是同步的:

同步则意味着: js 引擎在执行 js 代码的过程中加载的模块,并且一个文件没有加载结束之前,后面的代码都不会执行;

ES Module模块加载js文件的过程是编译时加载的,并且是异步的

异步则意味着:JS引擎在遇到import时会去获取这个js文件,但是这个获取的过程是异步的,并不会阻塞主线程继续执行;也就是说设置了 type=module 的代码,相当于在script标签上也加上了 async 属性;如果我们后面有普通的script标签以及对应的代码,那么ES Module对应的js文件和代码不会阻塞它们的执行;

即 ES Module可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高;但因为是在编译时完成加载,就导致了没法引用 ES Module 模块本身,因为那时它不是对象,因此它也被称为是静态编译的

并且大多数场景是可以使用ES Module 搭配 CommonJS的 ,因为 ES Module在加载CommonJS时,会将其module.exports导出的内容作为default导出方式来使用,但在Node中却无法使用ES Module

总结

普通情况下建议使用 ES Module 规范进行模块化开发,若是在Node中也可以使用CommonJS规范去进行开发的,但如果你硬要在浏览器中使用 CommonJS 规范的话,可以参考 阮一峰:浏览器加载 CommonJS 模块的原理与实现

最后如果本文对于本文有疑惑,还请指导勘正 (●'◡'●)