模块化开发 - CJS、AMD、CMD、ESM

简述模块发展史

凑合的模块系统

利用立即调用函数表达式 (Immediately Invoked Function Expresssion 简称 IIFE)将代码块封装在匿名闭包中,代码块是立即执行的。

// iifeModule是为匿名函数代码块创建的命名空间
var iifeModule = (function () {
    var count = 0
    return {
        increase: function(){
            ++count
        },
        reset: function(){ 
            count = 0 
        }
    }
})()
iifeModule.increase()
iifeModule.reset()

通过传参,使代码块可以有额外的依赖 。

var deps1 = function () {
    console.log('deps1')
}
var deps2 = function () {
    console.log('deps2')
}
var iifeModule = (function (deps1, deps2) {
    var count = 0
    deps1()
    deps2()
    return {
        increase: function(){
            ++count
        },
        reset: function(){ 
            count = 0 
        }
    }
})(deps1, deps2)
iifeModule.increase()
iifeModule.reset()

传统框架还应用了揭示模块模式(revealing module pattern)。这种模式只返回一个对象,属性值是私有成员的数据和引用,使用者无需关注底层实现。

var revealingModule = (function (deps1, deps2) {
    var count = 0
    deps1()
    deps2()
    var publicIncrease = function(){ ++count }
    var publicReset = function(){ count = 0 }
    return { increase: publicIncrease, reset: publicReset }
})(deps1, deps2)

在模块内部也可以定义模块,这样可以实现命名空间嵌套:

var revealingModule = (function (deps1, deps2) {
    var count = 0
    deps1()
    deps2()
    var publicIncrease = function(){ ++count }
    var publicReset = function(){ count = 0 }
    // 这样可以实现命名空间嵌套
    var publicCount = {
            getCount: function (){
                return count
            }
        }
    return { increase: publicIncrease, reset: publicReset, count: publicCount }
})(deps1, deps2)
revealingModule.count.getCount()

但是如果模块间存在依赖,引入文件时会有严格的顺序,否则会产生运行时bug。

随着前端工程的日益庞大,前端模块化也经过了漫长的发展。模块化的基本思想:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。不同的实现和特性,产生了不同的规范。

成熟的模块系统

目前主流的模块化规范是CJS、AMD、CMD、ESM。

CJS -- CommonJS   node.js 指定的标准  服务器端模块规范

// easyExport.js 文件
// 导出
let easyExport = function easyExport(){}
module.exports = { easyExport }
// exports.easyExport = easyExport

/************************************/
// core.js 文件
// 导入
let easyExport = require('./easyExport.js')

通过 module + exports 去对外暴露接口。

通过 require 去调用其他模块。

优点:所有模块同步加载。

缺点:服务器端运行可以,浏览器端就会造成js解析阻塞,页面加载速度缓慢。

AMD -- Asynchronous Module Definition / 异步模块定义

允许定制回调函数 经典实现框架:require.js

通过define定义,使用require一次性引入所有需要的依赖,将依赖前置

// define(id?, [depends]?, callback)
// require([module], callback)

define('amdModule', ['deps1', 'deps2'], (deps1, deps2) => {
    // 业务逻辑
    let count = 0
    const increase = function(){++count}
    deps1()
    deps2()
    return { increase }

})

reuire(['amdModule'], amdModule => {
    amdModule.increase()
})

优点:支持模块异步加载,经典实现框架 require.js

缺点:会有引入成本,没有考虑按需加载;因为是异步加载,加载完之后就执行,可能遇到某个依赖中用到的另一个依赖还没加载到

CMD -- Common Module Definition / 通用模块定义

主要应用框架 sea.js

通过define定义,在需要用到依赖的地方使用require引入,支持按需解析,执行

// math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});

// increment.js
define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});

// program.js
define(function(require, exports, module) {
  var inc = require('increment').increment;
  var a = 1;
  inc(a); // 2

  module.id == "program";
});

优点:异步加载,按需执行,依赖就近,用到的依赖都是确定加载并执行完成的

缺点:依赖于打包;扩大了模块内的体积;加载时需先解析模块路径

很多时候工程中会同时支持多种方式,那么需要如何兼容呢,就出现了UMD(Universal Module Definition)。

// UMD
 (function(global, factory) {
    if (typeof module !== 'undefined' && typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(['moduleName'], factory)
    } else if (typeof define === 'function' && define.cmd) {
        define(function(require, exports, module) {
            module.exports = factory()
        })
    } else {
        global = typeof globalThis !== 'undefined' ? globalThis : global || self 
        global.module = factory();
    }
}(this, function() {
    return {}
}))

ESM -- ES Modules

普通函数定义,通过 export 导出,通过 import 引入

一个文件只能有一个export default ,export可以有多个

// increase.js
let count = 0
export increase = () => { ++count }

// cala.js
import { increase } from 'increase'
const cala = increase()
export default cala

ES11原生支持动态加载
import('deps1').then((deps2) => {
    deps2()
})

优点:通过一种最统一的形态整合了所有JS的模块化

你可能感兴趣的:(es6,前端,javascript)