commonjs和ES Module

commonjs导出方法exports和module.exports的区别?

commonjs实际导出的就是exports这个对象,可以把需要导出的变量、方法等绑定在exports这个对象上导出,例如:

//a.js
exports.name = '阿明先森'
console.log(require('./a.js') );

打印:

{
    "name": "amingxiansen"
}

小结:所以用exports导出的都是对象,导出内容包含在对象里。

module.exportsexports具有相同的引用。module.exports可以直接导出函数、变量等。例如:

module.exports  = '阿明先森'

打印:
commonjs和ES Module
小结:当然想要导出对象也没问题:module.exports = {};相比exports,更灵活也更容易理解,推荐使用module.exports进行导出。

如果使用exports = {name: '阿明先森'}导出对象会怎么样呢?
打印:
commonjs和ES Module
打印为空,原因是exports是导出函数传入的参数,exports = {}相当于在函数里面重新声明了一个exports变量,和原来的参数脱离了关系,所以打印出来的是原来的参数,为空。
举个例子:

const myExports  = {}
function Test(myExports) {
  myExports  = {name: 'amingxiansen'} //这里的myExports与参数脱离了关系,相当于在局部声明了let Exports
}
console.log(Test(myExports)) // {}

小结: 所以使用exports导出,必须要把导出内容绑定在exports上,作为它的一个属性导出。

module.exports ={
    name:'amingxiansen'
}
exports.name = 'alien' // 此时 `exports.name` 是无效的

此时exports会被module.exports覆盖
小结: module.exports优先级高于exports,应该避免两种导出混合使用。

commonjs实现原理?

在编译过程中,commonjs经过了收尾的包装,像下面这样:

(function(exports,require,module,__filename,__dirname){
   const sayName = require('./hello.js')
    module.exports = function say(){
        return {
            name:sayName(),
            author:'我不是外星人'
        }
    }
})

Commonjs 规范模块中,会形成一个包装函数,我们写的代码将作为包装函数的执行上下文,使用的 require ,exports ,module 本质上是通过形参的方式传递到包装函数中的。

require加载原理?

每个文件都是一个module,module上除了有模块的基本信息之外,还会有个loaded代表模块是否已经加载过。二次加载会直接使用缓存中的数据。
以nodejs为例,整个系统运行之后,会用 Module 缓存每一个模块加载的信息。
加入缓存的顺序是:先加入缓存再执行
require源码:

 // id 为路径标识符
function require(id) {
   /* 查找  Module 上有没有已经加载的 js  对象*/
   const  cachedModule = Module._cache[id]
   
   /* 如果已经加载了那么直接取走缓存的 exports 对象  */
  if(cachedModule){
    return cachedModule.exports
  }
 
  /* 创建当前模块的 module  */
  const module = { exports: {} ,loaded: false , ...}

  /* 将 module 缓存到  Module 的缓存属性中,路径标识符作为 id */  
  Module._cache[id] = module
  /* 加载文件 */
  runInThisContext(wrapper('module.exports = "123"'))(module.exports, require, module, __filename, __dirname)
  /* 加载完成 *//
  module.loaded = true 
  /* 返回值 */
  return module.exports
}

ES module

对模块化module的支持是从ES6以后开始的。ES module导入导出使用 importexport。可以导出函数、变量、对象等。
export default //默认导出 ===> import temp from './a.js'
export {a, b, c} ===> import {a, b, c} from './a.js'
也可以混合导入导出。

es module的特性

  1. ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层 ,import , export 不能放在块级作用域或条件语句中。
  2. ES6 module 和 Common.js 一样,对于相同的 js 文件,会保存静态属性。
    但是与 Common.js 不同的是 ,CommonJS 模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父。
  3. es module import导入的属性是read only的,直接修改会报错;commonjs require导入的是属性的copy,可以更改导入属性。

总结

Commonjs 特性总结

  • CommonJS 模块由 JS 运行时实现。
  • CommonJs 是单个值导出,本质上导出的就是 exports 属性。
  • CommonJS 是可以动态加载的,对每一个加载都存在缓存,可以有效的解决循环引用问题。
  • CommonJS 模块同步加载并执行模块文件。

ES Module特性总结

  • ES6 Module 静态的,不能放在块级作用域内,代码发生在编译时。
  • ES6 Module 的值是动态绑定的,可以通过导出方法修改,可以直接访问修改结果。
  • ES6 Module 可以导出多个属性和方法,可以单个导入导出,混合导入导出。
  • ES6 模块提前加载并执行模块文件,
  • ES6 Module 的特性可以很容易实现 Tree Shaking 和 Code Splitting。
上一篇:JS的运行环境以及CommonJs和ES6的模块规范初了解


下一篇:CommonJS与ES Module模块化开发导入、导出