JS模块:ESM 与 CJM

来自Wikioe
跳到导航 跳到搜索


关于[1]

ESM 与 CJM
ESM CJM
应用场景
  • 用于:浏览器、node.js
  • 用于:node.js
加载方式
模块机制
循环引用 循环引用
  • 不会出现循环引用
  • 应该避免循环引用


“编译时加载”与“运行时加载”

“编译时加载”:编译时就确定模块的依赖关系,以及导入和导出的接口

“运行时加载”:运行时才确定模块的依赖关系,以及导入和导出的对象

CommonJS 示例:

// 代码合法
const name = getName()
console.log(name)
import { getName } from 'module.js'
  • 由于“运行时加载”,import 将会被提升至代码头部,先于 getName() 执行,所以代码是合法的。


ES Module 示例:

// 代码报错:import 中不能使用表达式
import { 'n' + 'ame' } from 'module.js'

// 代码报错:import 中不能使用变量
let module = 'module.js'
import { name } from module
  • 由于“编译时加载”,import 中不能使用变量、表达式

“值引用”与“值拷贝”

“值引用”:被导出的绑定值依然可以在本地进行修改

“值拷贝”:一旦值被导出,模块内部的变化就不再影响该值

CommonJS 示例:

  1. a.js:
    var name = 'morrain'
    var age = 17
    exports.name = name
    exports.age = age
    exports.setAge = function(a){
        age = a
    }
    
  2. b.js:
    var a = require('a.js')
    console.log(a.age)  // 输出:18
    a.setAge(19)
    console.log(a.age)  // 输出:18
    


ES Module 示例:

  1. a.js:
    var name = 'morrain'
    var age = 17
    const setAge = a => age = a
    export { name, age, setAge }
    
  2. b.js:
    import * as a from 'a.js'
    console.log(a.age)  // 输出:18
    a.setAge(19)
    console.log(a.age)  // 输出:19
    

循环引用

CommonJS:模块在返回时可能尚未完成执行,不会出现无限循环。

ES Module:
—— 静态导入:模块在初始化完成之前不能访问 —— 应该避免“循环引用”。
—— 动态导入:由于是异步执行,不会出现无限循环,但可能不能得到正确的内容 —— 应该避免“循环引用”。

CommonJS 示例:

  1. a.js:
    console.log('a starting');
    exports.done = false;
    const b = require('./b.js');
    console.log('in a, b.done = %j', b.done);
    exports.done = true;
    console.log('a done');
    
  2. b.js:
    console.log('b starting');
    exports.done = false;
    const a = require('./a.js');
    console.log('in b, a.done = %j', a.done);
    exports.done = true;
    console.log('b done');
    
  3. main.js:
    console.log('main starting');
    const a = require('./a.js');
    const b = require('./b.js');
    console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
    
  4. 输出:
    $ node main.js
    main starting
    a starting
    b starting
    in b, a.done = false
    b done
    in a, b.done = true
    a done
    in main, a.done = true, b.done = true
    

ES Module “静态导入”示例:

  1. a.mjs:
    import b from './b.mjs'
    
    let done = false;
    console.log('a starting');
    console.log('in a, b.done = %j', b.done);
    done = true;
    console.log('a done');
    export default done
    
  2. b.mjs:
    import a from './a.mjs'
    
    let done = false;
    console.log('b starting');
    console.log('in b, a.done = %j', a.done);
    done = true;
    console.log('b done');
    export default done
    
  3. main.mjs:
    import a from './a.mjs'
    import b from './b.mjs'
    
    console.log('main starting');
    console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
    
  4. 输出:
    PS D:\Documents\VSCode\helloWorld> node "d:\Documents\VSCode\helloWorld\main.mjs"
    b starting
    file:///d:/Documents/VSCode/helloWorld/b.mjs:5
    console.log('in b, a.done = %j', a.done);
                                     ^
    
    ReferenceError: Cannot access 'a' before initialization
        at file:///d:/Documents/VSCode/helloWorld/b.mjs:5:34
        at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
    
    Node.js v18.15.0
    

ES Module “动态导入”示例:

  1. a.mjs:
    let done = false;
    console.log('a starting');
    async function load() {
        let b = await import('./b.mjs');
        console.log('in a, b.done = %j', b.done);
    }
    load()
    done = true;
    console.log('a done');
    export default done
    
  2. b.mjs:
    let done = false;
    console.log('b starting');
    async function load() {
        let a = await import('./a.mjs');
        console.log('in b, a.done = %j', a.done);
    }
    load()
    done = true;
    console.log('b done');
    export default done
    
  3. main.mjs:
    console.log('main starting');
    async function load() {
        let a = await import('./a.mjs');
        let b = await import('./b.mjs');
        console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
    }
    load()
    
  4. 输出:
    PS D:\Documents\VSCode\helloWorld> node "d:\Documents\VSCode\helloWorld\main.mjs"
    main starting
    a starting
    a done
    b starting
    b done
    in a, b.done = undefined
    in main, a.done = undefined, b.done = undefined
    in b, a.done = undefined
    

ES Module:循环引用的处理

💡 利用“函数提升”解决循环引用 ReferenceError: Cannot access 'a' before initialization 的问题;

示例:

  1. a.mjs:
    import * as b from './b.mjs'
    
    console.log('a starting');
    console.log(`in a, b.msg1 = ${b.msg1}, b.msg2 = ${b.msg2}, print:${b.print()}`);
    // var 提升:只会提升声明,不会提升其初始化
    var msg1 = 'msg1-a'
    var msg2 = function(){
        return 'msg2-a';
    }()
    // 函数提升
    function print(){
        return 'hello from a !'
    }
    console.log('a done');
    export {msg1, msg2, print}
    
  2. b.mjs:
    import * as a from './a.mjs'
    
    console.log('b starting');
    console.log(`in b, a.msg1 = ${a.msg1}, a.msg2 = ${a.msg2}, print:${a.print()}`);
    // var 提升:只会提升声明,不会提升其初始化
    var msg1 = 'msg1-b'
    var msg2 = function(){
        return 'msg2-b';
    }()
    // 函数提升
    function print(){
        return 'hello from b !'
    }
    console.log('b done');
    export {msg1, msg2, print}
    
  3. 运行 a.mjs 输出:
    PS D:\Documents\VSCode\demo> node "d:\Documents\VSCode\demo\a.mjs"
    b starting
    in b, a.msg1 = undefined, a.msg2 = undefined, print:hello from a !
    b done
    a starting
    in a, b.msg1 = msg1-b, b.msg2 = msg2-b, print:hello from b !
    a done
    
📜 “变量提升”只能使不报错,但不能得到有效内容

—— 只会提升“声明”,不会提升“初始化” —— 所以,上面变量为 undefined

在 node.js 中[2]

Node.js 有两个模块系统:CommonJS 模块和 ECMAScript 模块。

Node.js 会将以下视为 ES Module:

  1. 扩展名为 .mjs 的文件。
  2. 最近的父“package.json”文件中“type”字段值为“module”时,扩展名为 .js 的文件。
  3. 字符串作为参数传入 --eval,或通过“STDIN”管道传输到 node,带有标志 --input-type=module

Node.js 会将以下视为 CommonJS:

  1. 扩展名为 .cjs 的文件。
  2. 最近的父“package.json”文件中“type”字段值为“commonjs”时,扩展名为 .js 的文件。
  3. 字符串作为参数传入 --eval--print,或通过“STDIN”管道传输到 node,带有标志 --input-type=commonjs
包作者应该设置“type”字段 !

应避免同时使用 ES Module 和 CommonJS !

参考