理解前端模块化

34 阅读3分钟

1. 为什么需要前端模块化?

js在设计之初为了简单化,并没有加入模块化的设计。而随着js有能力构建成大型项目,模块化就是不可或缺的一环了,因为模块化有助于提高项目的可维护性,复用性。直到2015年ES6的发布,官方才推出了模块化的能力。在此之前,社区出现了commonjs,amd,cmd等模块化规范。虽然amd,cmd已经退出历史舞台,但commonjs,依然顽强存在于nodejs中,与esm共存。接下来,将对几种模块化机制做简单介绍。

2. 模块化实现方式

2.1 commonjs

commonjs由于被nodejs运用,得以沿用至今。

基本语法:

需要使用reqire()指定依赖,而使用exports对象定义暴露的api

const moduleB = require('./moduleB')
module.exports={
    stuff: moduleB.doStuff()
}

实现原理:

// require函数的伪代码
function require(path){
  if(该模块有缓存吗){
    return 缓存结果;
  }
  function _run(exports, require, module, __filename, __dirname){
    // 模块代码会放到这里
  }
  
  var module = {
    exports: {}
  }
  
  _run.call(
    module.exports, 
    module.exports, 
    require, 
    module, 
    模块路径, 
    模块所在目录
  );
  
  把 module.exports 加入到缓存;
  return module.exports;
}

此模块化只能用于node环境,不能用于浏览器端。为什么不能用在浏览器端?这是因为

  1. 浏览器要加载JS文件,需要远程从服务器读取,而网络传输的效率远远低于node环境中读取本地文件的效率。由于CommonJS是同步的,这会极大的降低运行性能。
  2. 如果需要读取JS文件内容并把它放入到一个环境中执行,需要浏览器厂商的支持,可是浏览器厂商不愿意提供支持,最大的原因是CommonJS属于社区标准,并非官方标准

commonjs的特点:

  • 实现缓存
  • 同步加载
  • 动态加载模块(模块是否执行,是在运行时确定的)
  • 本质是函数的调用赋值

由于commonjs不能在浏览器端使用,因此,社区提出了AMD,CMD方案,但由于现在被ES6取代了,下面仅简单介绍。

2.2 AMD

AMD,全称Asynchronous Module Definition,异步模块定义,以浏览器为目标执行环境。让模块声明自己的依赖,运行在浏览器中的模块系统按需获取依赖,并在依赖加载完成后执行他们的依赖。它允许在浏览器中异步加载模块。

基本语法

导入和导出模块的代码,都必须放置在define函数中

define([依赖的模块列表], function(模块名称列表){
    //模块内部的代码
    return 导出的内容
})

2.3 CMD

全称是Common Module Definition,公共模块定义规范。为了统一commonjs和amd生态而提出的。可用于创建这两个系统都可以使用的模块代码。

基本语法

在CMD中,导入和导出模块的代码,都必须放置在define函数中

define(function(require, exports, module){
    //模块内部的代码
})

2.4 ESM(ES6 module)

esm 是ECMASCRIPT官方提出的模块化方案,可以说是对之前模块化方案的融合改进。

基本语法:采用importexport关键字

特点

  • 模块只在加载后执行
  • 模块只能加载一次
  • 所有环境均支持:node和浏览器环境
  • 同时支持静态依赖和动态依赖 静态依赖:在代码运行前就要确定依赖关系
  • 依赖是异步加载和执行的
  • 符号绑定:导入位置的符号和导出的符号并非赋值,它们指向的是一个内容

进一步思考:

出现ESM之后,浏览器已经支持模块化了,能否用ESM来解决前端工程化的问题。换句话说,浏览器端能否全部采用ESM进行模块化?

参考资料: