Commonjs与Esmodule
为什么会有CommonJs和Es Module
在早期JavaScript模块这一概念,都是通过script标签引入js文件代码,有以下弊端。
- js文件作用域都是顶层,这会造成变量全局污染
- js模块依赖问题,稍微不注意顺序引入错,代码全报错
- 解决变量污染问题,每个文件都是独立的作用域,所以不存在变量污染
- 解决代码维护问题,一个文件里代码非常清晰
- 解决文件依赖问题,一个文件里可以清楚的看到依赖了那些其它文件
二者差异
导出:
CommonJs中使用module.exports或者exports.a导出变量及函数,也可以导出任意类型的值。
// foo.js
//一个一个 导出
module.exports.age = 1
module.exports.foo = function(){}
exports.a = 'hello'
//整体导出
module.exports = { age: 1, a: 'hello', foo:function(){} }
//整体导出不能用`exports` 用exports不能在导入的时候使用
exports = { age: 1, a: 'hello', foo:function(){} }
注意 exports 不能被赋值,可以理解为在模块开始前exports = module.exports, 因为赋值之后exports失去了 对module.exports的引用。
模块导出就是return这个变量的其实跟a = b赋值一样, 基本类型导出的是值, 引用类型导出的是引用地址
exports 和 module.exports 持有相同引用
在Es Module中导出分为两种,单个导出(export)、默认导出(export default)
ES6 module的语法是静态的,export 和 import 只能出现在代码的顶层
使用 import 被导入的变量是与原变量绑定/引用的,可以理解为 import 导入的变量无论是否为基本类型都是引用传递
导入:
CommonJs中使用require语法可以导入,如果想要单个的值,可以通过解构对象来获取。
1. 如果 X 是内置模块(比如 require('http'))
a. 返回该模块。
b. 不再继续执行。
2. 如果 X 是以 '/' 开头、 绝对路径
a. 设置 Y 为 '/',会先查找package.json和index文件,同一个文件会根据后缀名,js、json、node来查找。
3. 如果 X 是以 './' 或 '/' 或 '../' 开头 相对路径
4.第三方模块的话会从node_modules查找 可以在webpack里配置include和exclude排除查找路径
CommonJS 模块加载过程是同步阻塞性地加载,在模块代码被运行前就已经写入了 缓存cache,同一个模块被多次 require 时只会执行一次,重复的 require 得到的是相同的 exports 引用。
循环引用
// a.js
module.exports.a = 1
var b = require('./b')
console.log(b)
module.exports.a = 2
// b.js
module.exports.b = 11
var a = require('./a')
console.log(a)
module.exports.b = 22
//main.js
var a = require('./a')
console.log(a)
运行此段代码结合上面的require demo,分析每一步过程:
执行 node main.js -> 第一行 require(a.js),(node执行也可以理解为调用了require方法,我们省略require(main.js)内容)进入 require(a)方法: 判断缓存(无) -> 初始化一个 module -> 将 module 加入缓存 -> 执行模块 a.js 内容,(需要注意 是先加入缓存, 后执行模块内容)a.js: 第一行导出 a = 1 -> 第二行 require(b.js)(a 只执行了第一行)进入 require(b) 内 同 1 -> 执行模块 b.js 内容b.js: 第一行 b = 11 -> 第二行 require(a.js)require(a) 此时 a.js 是第二次调用 require -> 判断缓存(有)-> cachedModule.exports -> 回到 b.js(因为js对象引用问题 此时的cachedModule.exports = { a: 1 })b.js:第三行 输出 { a: 1 } -> 第四行 修改 b = 22 -> 执行完毕回到 a.jsa.js:第二行 require 完毕 获取到 b -> 第三行 输出 { b: 22 } -> 第四行 导出 a = 2 -> 执行完毕回到 main.jsmain.js:获取 a -> 第二行 输出 { a: 2 } -> 执行完毕
易混淆:
模块语法与解构
import { a } from 'module'
const { a } = require('module')
两者不是同一个东西 ,解构可以理解为浅拷贝,而import可以看作变量绑定
总结
解决循环引用、变量污染、模块依赖的问题,js脚本的引入顺序不同,会报错。
导入:require import
先缓存再执行 , 值的拷贝和变量的绑定 , 查找内置模块、相对路径、绝对路径、第三方模块到node_modules找,前者为动态可以写在if判断里面,运行时加载。
导出:module.export export export.default。
目前浏览器对 ES6 Module 兼容还不太好,我们平时在 Webpack 中使用的 export 和 import,会经过 Babel 转换为 CommonJS 规范。在使用上的差别主要有:
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用,即变量的绑定。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJs 是单个值导出,ES6 Module可以导出多个
- CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层
- CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined
参考https://segmentfault.com/a/1190000017878394