聊一聊js模块化

177 阅读4分钟

image.png

提纲

image.png

js 模块化的方案

说到打包不得不提的就是 js 的语法缺陷:在 es6 出来之前,js 语言原生是不支持模块化的。当然了,js 语言的设计者 Bredan Eich 的初衷是 js 只是一个小脚本语言,花了10天就开发出来了。估计他也没有想到 js 会有这么一天。所以后面也演化出了各种 js 模块化解决方案

CommonJS 规范

当 node 出现的时候,js 就可以运行在服务器上了,也具备了编写大型复杂应用的基础。所以就需要一个模块化方案。而 node 中的模块化就是对 CommonJS 规范的实现

在 CommonJS 规范中,文件是模块的最小单位,通过 module.exports 导出模块(可以通过 exports 直接导出,但不推荐),通过 require 来引入模块,它还有如下特点:

  • 模块的引入是同步的
  • 模块引入具有缓存机制,第一次引入时,会依次顺序执行模块内容,后续再引用时,则直接使用缓存
  • module 代表当前模块,每个模块内部都存在
  • __dirname 代表模块当前的路径
  • __filename 代表模块当前所在的文件路径+文件名
  • global 是全局变量
  • 模块中定义的普通变量的运行在模块作用域中,不会污染全局作用域

一个符合 CommonJS 规范的模块导入导出例子如下:

// ================ 导出对象 ========================
// module.js
module.exports = {
  a: '1',
  b: '2'
};

console.log(module);
console.log(__dirname);
console.log(__filename);

// index.js
const {a, b} = require('./module'); // a = '1', b = '2';

// ================ 导出函数等其他形式 ================
// module.js
function add(a, b) {
	return a + b;
}
module.exports = add;

// index.js
const add = require('./module');

这种同步引入模块的方式对于运行在服务器端的 node 来说并没有什么不妥。但对于浏览器端,为了不阻塞显示,显然是采用异步的方式更加的合适

AMD

AMD 全称 Asynchronous Module Definition,是一种异步加载规范。上文说到 CommonJS 规范中的同步加载的特性并不适合浏览器环境,所以 AMD 规范就应运而生了

在 AMD 规范中,通过 define 定义模块,通过 require 来引入模块,如下:

// math.js
define('math', [add], function () {
	function add(a, b) {
  	return a + b;
  }
  return add;
});

// index.js
require(['math'], function(math) {
  math.add(1, 2);
});

其中 require 接收两个参数:要加载的模块列表、加载成功后的回调函数 callback

注意:AMD 规范目前是有这两个函数库:require.js 和 curl.js,所以在使用 AMD 模块方案的时候,需要引入这两个库文件之一

CMD

CMD 全称 Common Module Definition,和 AMD 类似,也是运行在浏览器端的一种异步加载。不同的是,CMD 推崇的是依赖后置,即浏览器先渲染页面,然后再加载模块。和 AMD 正好相反

CMD 规范中,同样的,一个文件就是一个模块:

  • 使用 define 来定义模块,参数既可以是工厂函数,也可以是对象等其他参数:define(factory) 或者 define({ name: 'a' })。也可以指明依赖模块:define(id?, deps?, factory),id 就是模块标识,deps 则是依赖其他模块的数组
  • define 为工厂函数时,默认有3个参数:require, exports, module。require 来引入其他模块,exports 来定义本模块的对外接口。module 存储了当前模块的一些信息,比如 id,dependencies 等
  • 依赖就近原则,不需要提前申明用到的模块,哪里用到就直接通过 require 来引入该模块
// math.js 模块。依赖 module1.js 和 module.js 模块
// 这里根据依赖就近原则,直接使用 require 来加载外部模块
define(function(require, exports, module) {
  // 依赖外部模块 module1.js,并且同步引入外部模块
  var module1 = require('./module1.js');
  // 依赖外部模块 module.js,并且异步引入外部模块
  require.async('./module.js', function(m2) {});
  // 本模块导出的
  export.add = function(a, b) {return a + b};
});

// main.js
sea.use(['math.js'], function(math) {
	math.add(1, 2);
});

注意:CMD 规范由 sea.js 实现,所以使用前需要引入 sea.js 文件

综上:CMD 和 AMD 最大的不同就是依赖就近原则了

UMD

UMD 全称 Universal Module Definition,即通用模块定义规范,旨在解决跨平台模块方案(即兼容 CommonJS、AMD、CMD 等模块方案)。它的实现方式比较简单,核心就是根据当前所处的环境来使用对应的模块导出、导入方案。如下:

(function(root, factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        console.log('是commonjs模块规范,nodejs环境')
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        console.log('是AMD模块规范,如require.js')
        define(factory())
    } else if (typeof define === 'function' && define.cmd) {
        console.log('是CMD模块规范,如sea.js')
        define(function(require, exports, module) {
            module.exports = factory()
        })
    } else {
        console.log('没有模块环境,直接挂载在全局对象上')
        root.umdModule = factory();
    }
}(this, function() {
    return {
        name: '我是一个umd模块'
    }
}))

ES6Module

ES6 在 js 语言层面上实现了模块化的功能,旨在为浏览器和服务器使用。ES6 模块化规范如下:

  • 使用 export 导出模块
  • 使用 import 导入模块

注意:和 CommonJS 不同的是,ES6 是动态引入的,不会缓存值

参考

【前端工程化:CommonJS、AMD、CMD、UMD和ES Modules的区别】blog.csdn.net/leelxp/arti…

【Javascript 模块化编程(二):AMD 规范】www.ruanyifeng.com/blog/2012/1…

【AMD 规范实例】www.cnblogs.com/fsg6/p/1314…

【CMD 模块定义】blog.csdn.net/weixin_3816…

【可能是最详细的UMD模块入门指南】www.jianshu.com/p/9f5a0351a…