写点什么

Nodejs:ESModule 和 commonjs,傻傻分不清

作者:coder2028
  • 2023-02-28
    浙江
  • 本文字数:2582 字

    阅读完需:约 8 分钟

最近写 nodejs 脚本的时候遇到了 commonjs 和 ESModule 的问题,正好之前用得稀里糊涂的,这次好好学习一下。

ES Module

导出

仅导出

  • named exports: 命名导出,每次可以导出一个或者多个。

  • default exports: 默认导出,每次只能存在一个。


以上两者可以混合导出。


    // 命名导出    export const b = 'b'    // 默认导出    export default {      a: 1    };
const c = 'c' export { c }
// 以上内容会合并导出,即导出为: {b:'b', c:'c', default: {a:1}}
复制代码


更多示例可以直接去看 mdn

重导出(re-exporting / aggregating)

算是一个导入再导出的一个语法糖吧。


  export {    default as function1,    function2,  } from 'bar.js';
// 等价于 import { default as function1, function2 } from 'bar.js'; export { function1, function2 };
复制代码


然而这种语法是会报错的:


export DefaultExport from 'bar.js'; // Invalid
复制代码


正确的语法应该是:


export { default as DefaultExport } from 'bar.js'; // valid
复制代码


我猜是因为 export 本身支持的 export xxx 这种语法必须是要导出一个对象,然而 import xxx 可能是任意类型,两者冲突了,所以从编译层面就不让这种语法生效会更好。

嵌入式脚本

嵌入式脚本不可以使用 export。

引入

语法

  • import all exports: import * as allVar,所有导出内容,包含命名导出及默认导出。allVar 会是一个对象,默认导出会作为 allVar 的 key 名为 default 对应的值。

  • import named exports: import {var1, var2},引入命名导出的部分。没找到,对应的值就为 undefined。个人觉得可以看做是"import all exports"的解构语法。

  • import default exports: import defaultVar,引入默认导出的部分。

  • import side effects: import "xxx./js",仅运行这个 js,可能是为了获取其副作用。


    // test.js    export const b = 'b'     // 命名导出    export default {    // 默认导出      a: 1    };
// index.js import { b, default as _defaultModule } from './test.js' import defaultModule from './test.js' import * as allModule from './test.js'
console.log('name export', b) // 'b' console.log('default export', defaultModule) // {a:1} console.log(_defaultModule === defaultModule) // true console.log('all export', allModule) // {b:'b', default: {a:1}}
复制代码


一个之前老记错的 case


    // test.js    export default {    // 默认导出      a: 1    };
// index.js import { a } from './test.js' console.log('name export', a) // undefined
// index.js import defaultModule from './test.js' import * as allModule from './test.js' console.log('default export', defaultModule) // {a:1} console.log('all export', allModule) // {default: {a:1}}
复制代码

嵌入式脚本

嵌入式脚本引入 modules 时,需要在 script 上增加 type="module"。

特点

  • live bindings

  • 通过 export 在 mdn 上的解释,export 导出的是 live bindings,再根据其他文章综合判断,应该是引用的意思。即 export 导出的是引用

  • 模块内的值更新了之后,所有使用 export 导出值的地方都能使用最新值。

  • read-only

  • 通过 import 在 mdn 上的解释,import 使用的是通过 export 导出的不可修改的引用

  • strict-mode

  • 被引入的模块都会以严格模式运行。

  • 静态引入、动态引入

  • import x from这种语法有 syntactic rigid,需要编译时置于顶部且无法做到动态引入加载。如果需要动态引入,则需要import ()语法。有趣的是,在 mdn 上,前者分类到了 Statements & declarations, 后者分类到了 Expressions & operators。这俩是根据什么分类的呢?

  • 示例

commonJS

导出

在 Node.js 模块系统中,每个文件都被视为独立的模块。模块导入导出实际是由 nodejs 的模块封装器实现,通过为module.exports分配新的值来实现导出具体内容。


module.exports有个简写变量exports,其实就是个引用复制。exports 作用域只限于模块文件内部。原理类似于:


// nodejs内部exports = module.exports
console.log(exports, module.exports) // {}, {}console.log(exports === module.exports) // true
复制代码


参考 前端进阶面试题详细解答


注意,nodejs 实际导出的是 module.exports,以下几种经典 case 单独看一下:


case1


// ✅使用exportsexports.a = xxxconsole.log(exports === module.exports) // true
// ✅等价于module.exports.a = xxx
复制代码


case2:


// ✅这么写可以导出,最终导出的是{a:'1'}module.exports = {a:'1'}
console.log(exports, module.exports) // {}, {a:'1'}console.log(exports === module.exports) // false

// ❌不会将{a:'1'}导出,最终导出的是{}exports = {a:'1'}
console.log(exports, module.exports) // {a:'1'}, {}console.log(exports === module.exports) // false
复制代码

引入

通过 require 语法引入:


// a是test.js里module.exports导出的部分const a = require('./test.js')
复制代码


原理伪代码:


function require(/* ... */) {  const module = { exports: {} };  ((module, exports) => {    // Module code here. In this example, define a function.    function someFunc() {}    exports = someFunc;    // At this point, exports is no longer a shortcut to module.exports, and    // this module will still export an empty default object.    module.exports = someFunc;    // At this point, the module will now export someFunc, instead of the    // default object.  })(module, module.exports);  return module.exports;}
复制代码

特点

值拷贝

// test.jslet test = {a:'1'}setTimeout(()=>{  test = {a:'2'}},1000)module.exports = test
// index.jsconst test1 = require('./test.js')console.log(test1) // {a:1}setTimeout(()=>{ console.log(test1) // {a:1}},2000)
复制代码

ES Module 和 commonJS 区别

  1. 语法


exportsmodule.exportsrequireNode.js 模块系统关键字。


exportexport defaultimport 则是 ES6 模块系统的关键字:


  1. 原理


exportsmodule.exports导出的模块为值复制。


exportexport default为引用复制。


  1. 时机


ES Module 静态加载是编译时确定,ES Module 动态加载是运行时确定。


CommonJS 是运行时确定。


用户头像

coder2028

关注

还未添加个人签名 2022-09-08 加入

还未添加个人简介

评论

发布
暂无评论
Nodejs:ESModule和commonjs,傻傻分不清_JavaScript_coder2028_InfoQ写作社区