前端模块化、ESM「ES6模块化」、CMD、AMD、CommonJS「CJS」

1,177

拓展阅读

异步加载js方式

// defer属性规定是否对脚本执行进行延迟,直到页面加载为止
<script type="text/javascript" src="xxx.js" defer="defer"></script>
​
// async属性规定一旦加载脚本可用,则会异步执行
<script type="text/javascript" src="xxx.js" async="async"></script>
​
​
DOM文档加载步骤: 
  解析HTML结构 
  加载外部的脚本和样式文件 
  解析并执行脚本代码 
  加载图片等二进制资源 
  页面加载完毕,执行window.onload

image.png

静态分析

静态程序分析(Static program analysis)是指在不运行程序的条件下,进行程序分析的方法。

静态分析就是指在运行代码之前就可判断出代码内有哪些代码使用到了,哪些没有使用到。

ESM能做静态分析的原因是因为代码在引入前就已经确认了使用哪些库,而 CMD、 AMD、 CommonJS,这些都是可以使用语句去控制的,就是变为动态了引入了,所以就没法静态分析了。

模块化与工程化:webpack

webpack 同时支持 CommonJS、AMD 和 ESM 三种模块化规范的打包。根据不同规范 webpack 会将模块处理成不同的产物。

模块化与工程化:Tree Shaking

Tree shaking 是一个通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)行为的术语。它依赖于 ES2015 中的 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。 Tree Shaking - MDN

简单来说,Tree Shaking 是一种依赖 ESM 模块静态分析实现的功能,它可以在编译时安全的移除代码中未使用的部分(webpack 5 对 CommonJS 也进行了支持)。

什么是模块?

一个具有处理逻辑的js文件,把相关的方法或对象进行导出,经过导入就可以使用

模块化有什么作用?

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性
  • 分治(你会发现当逻辑复杂的时候,可以分得更加细的模块,而且多人同时开发,可见分治是模块化最大的优点。)

ESM(ECMAScript Module)

严格模式: 模块化自动是严格模式

export/module export(导出): 规定模块的对外导出的接口

import(导入) : 规定模块的对外导入的接口

使用场景: ESM 在支持的浏览器环境下可以直接使用,在不支持的端需要编译/打包后使用,通过babel将不被支持的import编译为当前受到广泛支持的 require

加载方式:ESM 加载模块的方式同样取决于所处的环境,Node.js 同步加载,浏览器端异步加载。

优点:支持同步/异步加载、语法简单、支持模块静态分析、支持循环引用

缺点:兼容性不佳,很多第三方库都是不支持esm,可以在库在看esm字段说明支持

注意:导出的是对象的引用

导入导出方式一
function foo(params) {
    console.log("这是模块中的逻辑代码",params); 
}
​
export {
    foo,           // 方式一:将函数暴露出去 
    foo as newFoo  // 方式二: newFoo是foo的引用
    // 所以这里总共导出了 foo 和 newFoo 到外面
}  
​
// 方式一:这种叫做按需导入方法,也是现在主流的方式,方便打包工具做treeShaking
import {foo, newFoo} from 'xx/module-name'// 方式二:将模块中的内容全部引入进来
import * as allFn from 'xx/module-name' 
​
导入导出方式二
export default: 默认导出,它能带来的好处就是一次导出(只能导出一次)
​
export default{ 
    foo, 
}  
​
import allFn from 'xx/module-name' 

CommonJS

CommonJS 主要是 Node.js 使用,通过 require 同步加载模块,exports 导出内容。

exports/module.exports(导出) :暴露需要被外部访问的属性和方法。

记得别和ES6模块搞混了,export这是多个S, export default 也不一样.

require(导入) :规定模块的对外导入的接口

使用场景:CommonJS 主要在服务端(如:Node.js)使用,也可通过打包工具打包之后在浏览器端使用。

加载方式:CommonJS 通过同步的方式加载模块,首次加载会缓存结果,后续加载则是直接读取缓存结果。

优点:可以在任意位置 require 模块、支持循环依赖

缺点:同步的加载方式不适用于浏览器端使用需要打包、难以支持模块静态分析

导出 module.exports = foo
导入 cosnt foo = require("foo");

1.为什么CommonJS仅仅适用于服务器不适用浏览器?

当我们需要加载一个模块的时候,这时候 CommonJS规范是用了 const foo= require("foo") 如果在服务器完全ok的,因为foo这个模块肯定在服务器,即拿即用,就是用的时候加载也ok,它就是运行的时候foo才是确定的值。然后当我们浏览器用的这个模块的时候还要去请求,所以这种规范不太适用。

2.import 和 require 导入的区别

import是ESM 标准模块是编译时调用,所以必须放在文件开头,是解构过程 。 require 是AMD规范引入方式是运行时调用,所以require理论上可以运用在代码的任何地方,是赋值过程。其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量

3.CommonJs 和 esm的区别

  1. CommonJS 模块输出的是一个值的拷贝, ESM输出的是值的引用。
  2. CommonJS 模块是运行时加载,ESM是编译时加载。

AMD (Asynchronous Module Definition)

异步模块定义。主要用于浏览器,因为浏览器上面加载模块是需要异步加载的,如果使用同步的话,但有些模块加载的时候当用到的时候才加载,就处于等待,浏览器假死,那么这时候就需要用到异步加载。

AMDRequireJS 在推广过程中对模块定义的规范化产物

使用场景:AMD 主要在浏览器端中使用

加载方式:AMD 通过异步的方式加载模块不会堵塞浏览器的渲染,AMD 推崇依赖前置「对于依赖的模块AMD是提前执行」,每加载一个模块实际就是加载对应的 JS 文件。

优点:依赖异步加载,更快的启动速度、支持循环依赖、支持插件

缺点:语法相对复杂、依赖加载器、难以支持模块静态分析

/**
 * define
 * @param id 模块名
 * @param dependencies 依赖列表
 * @param factory 模块的具体内容/具体实现
 */
define(id?: string, dependencies?: string[], factory: Function | Object);
​
​
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
    exports.verb = function() {
        // return beta.verb();
        //Or:
        return require("beta").verb();
    }
});
​
​
require(["alpha"], function (exports) {
  // 依赖前置
  console.log(exports.verb()); 
});

CMD( Common Module Definition )

CMD,即通用模块定义。CMD是Sea.js推广过程中对模块定义的规范化产物,CMD 推崇依赖就近,CMD是延迟执行,主要是浏览器端使用 。

优点:依赖异步加载,更快的启动速度、支持循环依赖、依赖就近

缺点:语法相对复杂、依赖加载器、难以支持模块静态分析

/**
 * define
 * @param id 模块名
 * @param dependencies 依赖列表
 * @param factory 模块的具体内容/具体实现
 */
define(id?: string, dependencies?: string[], factory: Function | Object);
​
define('hello', ['jquery'], function(require, exports, module) {
  // 模块代码
});
​
define(function (require, exports) {
  const hello = require("hello"); // 依赖就近
});

UMD (Universal Module Definition)

UMD,即通用模块定义。UMD 主要为了解决 CommonJS 和 AMD 规范下的代码不通用的问题,同时还支持将模块挂载到全局,是跨平台的解决方案。

AMD 浏览器第一的原则发展 异步加载模块。

CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。

这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。

在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

使用场景:UMD 可同时在服务器端和浏览器端使用。

加载方式:UMD 加载模块的方式取决于所处的环境,Node.js 同步加载,浏览器端异步加载。

优点:跨平台兼容

缺点:代码量稍大

(function (window, factory) {
   if (typeof exports === 'object') {
      module.exports = factory();
   } else if (typeof define === 'function' && define.amd) {
      define(factory);
   } else {
      window.eventUtil = factory();
   }
})(this, function () {
   //module ...
});

各位看官如遇上不理解的地方,或者我文章有不足、错误的地方,欢迎在评论区指出,感谢阅读。

参考