模块化

5/15/2022 node.js

# 模块化

# IIFE

立即执行函数(IIFE)是以前主流的模块化方案,比如 Jquery就使用该方案。

// 定义模块
(function (window) {
    function A() {
        return 'aaa'
    }

    function B() {
        return 'bbb'
    }

    window.myModule = {A, B}
})(window)

// 使用模块
myModule.A()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# AMD

很久以前的一种模块化方案,类似的方案还有CMD。需要额外安装 require.js库,使用 define定义模块,使用 require加载模块。

现在基本不用。

# CommonJS模块

ES模块是目前最主流的两个模块化方案。

// a.js
function getName() {
    return 'Akara'
}
module.exports = getName

// b.js
const getName = require('./a')
getName() // 'Akara'
1
2
3
4
5
6
7
8
9

require可以简单看作包了一层立即执行函数,该立即执行函数返回了那个模块的 module.exports

const getName = require('./a')
// 等价于
const getName = (function () {
    function getName() {
        return 'Akara'
    }

    module.exports = getName

    // 返回module.exports
    return module.exports
})()
1
2
3
4
5
6
7
8
9
10
11
12

模块内部有 moduleexports两个变量,其中 module.exportsexports指向同一片内存。

又因为我们模块实际返回的是 module.exports,所以如果直接对 exports变量重新赋值肯定是错误的操作。

module: {
    id: '.'
	exports: {}
}
1
2
3
4

# UMD

UMD是上述三种模块化方案 IIFEAMDCommonJS的结合,即用来兼容多套模块化系统。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // 如果支持AMD模块化
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // 如果支持CommonJS模块化
        module.exports = factory(require('b'));
    } else {
        // 如果以上两种都不支持,设置全局变量来保存模块内容
        root.returnExports = factory(root.b);
    }
}(this, function (b) {
    // 模块的业务代码放在这
    return {}
}));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# ES模块

通常我们把.mjs文件视为ES模块,或者package.jsontypemodule时该项目下所有.js文件都视为ES模块。ES6模块使用 importexport语法来导入和导出模块。

# export

// a.js
const A = 'akara'
export default A // 等价于 export { name as default }
export function B() { // 等价于 export { getName as getName }
    return name
}
const C = 'akara'
export { C as alias }
1
2
3
4
5
6
7
8

# import

// b.js
import A from './a.js' // 等价于 import { default as name }
import { B } from './a.js' // 等价于 import { getName as getName }
import { alias as C } from './a.js'
console.log(A, B, C)
1
2
3
4
5

除了逐个接口import,我们甚至可以一次性import整个模块

// b.js
import * as myModule from './a.js' 
console.log(myModule)
// [Module: null prototype] {
//     B: [Function: B],
//     alias: 'akara',
//     default: 'akara'
// }
1
2
3
4
5
6
7
8

# import CJS

通常来说我们会使用CommonJS模块引用CommonJS模块,使用ES模块引用ES模块,而我们甚至可以使用ES模块引用CommonJS模块(当然,CommonJS是无法引用ES模块的)

// a.js
module.exports = function A() {
    console.log('common模块')
}

// b.mjs
import * as myModule from './a.js' // 默认导入了a.js文件的module.exports
console.log(myModule)
// [Module: null prototype] { 
//     default: [Function A] 
// }

import A from './a.js'
console.log(A)
// [Function: A]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

由此可见,CommonJS模块只能定义一个default接口,而ES模块除了default接口还可以定义其他自定义接口。从这个角度来看ES模块能引用CommonJS模块是比较科学的,因为ES模块能处理default接口;而CommonJS模块无法引用ES模块是因为CommonJS无法处理ES模块可能暴露的非default接口。

# 对比

CJS模块和 MJS模块存在几大区别

  1. CJS模块会被整体导入,而 MJS可以被部分导入。因此使用 MJS可以 tree shaking

  2. import命令会在其他所有代码执行前就被JavaScript引擎静态分析,可以说它是在编译时加载模块

    所以我们通常只能把 import放在模块的顶层,并且不能放在如 if之类的代码块中。

    并且由于这个特性,我们不能在JS代码执行中根据条件来动态加载模块,而 require可以做到这一点,require运行时加载模块

    好在,我们可以使用 import()来实现运行时加载模块,组件的懒加载通常就是使用 import()搭配代码分割来实现的。

Last Updated: 9/7/2022, 11:32:53 PM