乐趣区

关于node.js:NodejsESModule和commonjs傻傻分不清

最近写 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。这俩是依据什么分类的呢?

      true && import test from "./a.js";
      // SyntaxError: import can only be used in import() or import.meta
      // 这里应该是把 import 当成了动静引入而报错
  • 示例

      // a.js
      const test = {a: 1};
      export default test;
      // 改变模块外部的值
      setTimeout(() => {test.a = 2;}, 1000);
    
      // index.js
      import test from './index.js'
    
      /* live bindings */
      console.log(test) // {a:1}
      setTimeout(()=>{console.log(test) // {a:2}
      }, 2000)
    
      /* read-only */
      test= {a: 3} // 报错, Error: "test" is read-only.
    
      /* syntactically rigid */
      if(true){import test from './index.js' // 报错, SyntaxError: 'import' and 'export' may only appear at the top level}

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

// ✅应用 exports
exports.a = xxx
console.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

参考 nodejs 进阶视频解说:进入学习

引入

通过 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.js
let test = {a:'1'}
setTimeout(()=>{test = {a:'2'}
},1000)
module.exports = test

// index.js
const 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 是运行时确定。

退出移动版