讲讲ES6的模块与CommonJS的模块的异同

995 阅读3分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

前言

之前2篇文章讲了exports与module.exports的异同es6的export和export default指令,今天来讲讲ES6的模块与CommonJS的模块的异同。

异同

  1. ES6的import的是值的引用,它不对值缓存,而是实时去对应的模块取值;

    CommonJS的require是会对基本类型缓存,但是如果你是引用类型,则是对应着同一个栈内存地址,所以如果exports的值后面有改,require也会实时变化。

    因为它require后会在内存中生成一个对象,里面有个属性exports, 这个就是导出的值就存在这里。 类似如下:

    {
      ...
      // 输出值
       exports: { ... }, 
       ...
     }
    
    

    如果多次require这个模块,就会从这个对象的exports直接返回。

    如果你多次import同一个模块,只会执行一次。

    下面通过例子来看看

    ES6代码:

     // test.mjs
     export let name = '答案cp3'
     setTimeout(() => {
       name = 'test'
     }, 500);
     
     
     // index.mjs
     import { name } from './test.mjs'
     console.log(name) // 答案cp3
     setTimeout(() => {
       console.log(name) // test
     }, 1000);
    

    在终端上运行

    node --experimental-modules  index.mjs
    

    先打印了答案cp3,然后再打印了test

    但是,如果你使用的是export default,然后导出一个基本类型的变量,它的表现跟CommonJS的是一样的,因为它也是导出一个叫default的属性,值是这个变量值,所以跟export会有区别。如果是引用类型,则表现跟export一致。

    CommonJS代码:

     // test.js
     let name = '答案cp3'
     exports.name = name
    
     setTimeout(() => {
       name = 'test'
     }, 500)
     
     
     // index.js
     let { name } = require('./test')
     console.log(name) // 答案cp3
     setTimeout(() => {
       console.log(name) //答案cp3
     }, 1000)
    

    两次都是打印了答案cp3

    另外,无论是ES6的import还是CommonJS的require, 一旦导入这个变量,这个变量就是常量,不能重新赋值。当然,如果导入的变量是引用类型,可以对它的属性赋值(但是不建议这样做)。

    import { name } from './test.mjs'
    name = 'test' // 报错
    let { name } = require('./test')
    name = 'test' // 报错
    

    最好就是import或者require后对变量只读,不操作。

  2. ES6的模块是编译时加载,CommonJS是运行时加载。

    怎么理解呢?

    我的理解是, 编译就是在运行之前(即编译时)就通过静态分析确定依赖关系,完成模块加载的,或者通过打包工具webpack或者rollup等打包的过程。

    运行时加载就是在代码执行的时候才加载。

    由于ES6编译时加载,importexport 就得需要处于顶层作用域,不然无法进行静态分析。 然后因为是编译时加载,导致import的变量会变量提升到顶部,但是不能使用表达式和变量。

    CommonJS运行时加载没有这些问题。

    if(true) {
      import { name } from './test.mjs' // 报错
    }
    import {'n' + 'name' } from './test.mjs' // 报错
    
    console.log(name) // 正确,变量提升
    import { name } from './test.mjs' 
    
  3. ES6的模块是异步加载,CommonJS是同步加载。

    也就是说CommonJS如果有多个require,需要一个个排队执行,因为Node依赖的代码和模块一般是在服务器上,所以相当于本地加载,不会耗时很久。

    但是在浏览器就不可以,因为需要请求服务器,网络状态是不稳定的,如果是同步加载,体验就有可能会出现要等很久的情况。所以就出现了AMD,CMD等异步加载的库,以及ES6新增的import

总结

以上就是我总结的ES6的模块与CommonJS的模块的异同。

总结有三点:

  • ES6的import的是值的引用(export和export default的表现不一致),CommonJS的require也要区分是基本类型还是引用类型,基本类型是会缓存,引用类型会实时取值。

  • ES6的模块是编译时加载,CommonJS是运行时加载。

  • ES6的模块是异步加载,CommonJS是同步加载。

感谢你们的阅读。