CommonJS与ESModule

120 阅读5分钟

1.模块化

1-1.什么是模块化:

将一大段代码按照规则拆分成一个个代码模块,模块内部的变量等都是私有的。通过向外暴露一些接口来进行通信。

1-2.没有模块化带来的问题

1-2-1.全局污染

1-2-2.依赖管理混乱

2.CommonJS

2-1.CommonJS基本使用

2-1-1.CommonJS使用环境:

node环境或者webpack打包工具对CommonJS进行支持和转换

2-1-2.exports、module.exports、require

exportsmodule.exports 可以负责对模块中的内容进行导出;

require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;

// main.js
// 使用另一个模块导出的对象,就需要导入 require
// const { name, age, foo } = require("./part.js")
const bar = require("./part.js")
console.log('bar', bar, bar.money)
// foo()
// console.log(name, age)
// part.js
const name = 'zhangsan'
const age = 21
function foo(){
    console.log('执行foo')
}
const money = '¥237847364738984'
//1.导出方案 module.exports
module.exports = {
    name,
    age,
    foo,
    money
}
console.log('modulemodule', module)

2-2.CommonJS实现原理

我们可以在 Commonjs 规范下每一个 js 模块上直接使用符合CommonJS规范的变量,如exportsmodule.exportsrequire__filename 和 __dirname 变量。

  • module 记录当前模块信息。
  • require 引入模块的方法。
  • exportsmodule.exports 当前模块导出的属性

2-2-1.exportsmodule.exports

exports 就是传入到当前模块内的一个对象,本质上就是 module.exportsexports可以理解成 exports = module.exports

但是,当在另外一个模块中导入当前模块中的导出的数据时,require返回的是module.exports,所以,exports={} 直接赋值一个对象时,被另外一个模块导入的不是这个新对象,而是原来的module.exports的值。

当对一个模块中的数据进行导出时,若 exportsmodule.exports 同时存在,则可能出现覆盖的情况。

exports.name = 'alien' // 此时 exports.name 是无效的
// 由于最终导出的值以 module.exports 为准,这里对module.exports进行了重新赋值,而不是修改其属性,所以exports.name 是无效的
module.exports = {
    name:'《React进阶实践指南》',
    author:'我不是外星人',
    say(){
        console.log(666)
    }
}

2-2-2.require

2-2-2-1.require加载的文件种类(按来源):

  1. nodejs 底层的核心模块
  2. 我们编写的文件模块
  3. 我们通过 npm 下载的第三方自定义模块
const fs =      require('fs')      // ①核心模块
const sayName = require('./hello.js')  //② 文件模块
const crypto =  require('crypto-js')   // ③第三方自定义模块
  • require接受参数作为标志符
  • fshttppath 等标识符,会被作为 nodejs核心模块
  • ./ 和 ../ 作为相对路径的文件模块, / 作为绝对路径的文件模块
  • 非路径形式也非核心模块的模块,将作为自定义模块

2-2-2-2.node核心模块处理:

核心模块的优先级仅次于缓存加载,在 Node 源码编译中,核心模块已被编译成二进制代码。

2-2-2-3.路径形式文件模块处理:

以 ./ ,../ 和 / 开始的标识符,会被当作文件模块处理。在首次加载后会产生缓存,后续加载相同模块会直接从缓存中读取。

没有后缀名时,查找顺序:文件X->X.js->X.json->X.node

没有找到当以上文件时,将X作为一个目录,查找目录中的index文件(index.js->index.json->index.node

2-2-2-4.第三方自定义模块处理:

第三方自定义模块的查找遵循以下规则: 在当前目录下的node_module目录下查找,若没找到,则在父级目录的node_module文件目录下查找若没找到,依次向上查找,知道找到该模块或者到达根目录的node_module目录。

2-2-2-5.require模块加载与处理

CommonJS模块同步加载并执行模块中的代码,在执行模块代码的过程中分析模块的依赖关系。采用深度优先搜索策略。

例如:

main.js文件中,require('./a.js')

a.js文件中,require('./b.js')

b.js文件中,require('./a.js')

此时执行main.js文件,那么b.js文件中的代码会先执行完,但是此时b.js不能使用a.js文件导出的数据等。

3.ESModule

3-1.ESModule基本使用

<!-- html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 注意1. -->
    <!-- 当直接使用script标签引入一个js文件时,该js文件不会被当成一个模块,此时不允许该js文件中使用import -->
    <!-- 所以,必须在script标签中写上type属性 -->
    <!-- 注意2. -->
    <!-- 使用模块化代码时,需要使用liveServer来打开页面。 -->
    <!-- 当直接打开本地html文件时,浏览器链接的协议是file,无法正确加载模块 -->
    <!-- 当使用live Server打开文件时,浏览器链接的协议不是file,而是http\https等,此时才能正确加载模块 -->
    <script src="./main.js" type="module"></script>
</body>
</html>
// main.js
import { name ,num } from "./part1.js"
console.log('name', name)
console.log('num', num)
// part1.js
export const name = 'part1'
export const num = 1

3-2.其他使用方法

<!-- html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./main.js" type="module"></script>
</body>
</html>

3-2-1.三种导入方式:普通导入,起别名导入、将导出的所有内容放到一个标识符中

export声明语句导出

// main.js
// 普通导入
import { name ,num, sayName, Part1 } from "./part1.js"
console.log('name', name)
console.log('num', num)
sayName()
// main.js
// 导入方式2:起别名
import { name as name1 ,num as num1, sayName as sayName1 } from './part1'
console.log(name1, num1)
sayName1()
// main.js
// 将导出的所有内容放到一个标识符中
import * as part1 from './part1'
console.log(part1.name)

3-2-2.三种导出方式:起别名导入,声明和export导出分开,导出时起别名

// part1.js
// 导出方式:export 声明语句
export const name = 'part1'
export const num = 1
export function sayName() {
    console.log('My name is part1')
}
export class Part1 {}
// part1.js
// 声明和export导出分开
const name = 'part1'
const num = 1
function sayName() {
    console.log('My name is part1')
}
class Part1 {}
export {
    name,
    num,
    sayName,
    Part1
}
// part1.js
// 导出时起别名
const name = 'part1'
const num = 1
function sayName() {
    console.log('My name is part1')
}
class Part1 {}
export {
    name as part1Name,
    num as part1Num,
    sayName as part1SayName,
    Part1 as part1Part1
}

3-3.结合使用

<!-- html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- utils文件夹中,index.js作为其他所有工具变量的统一出口 -->
    <script src="./main.js" type="module"></script>
</body>
</html>

文件目录:

image.png

// main.js
import { timeFormat, priceFormat } from "./utils/index.js";
import { add, sub } from './utils/index.js'
console.log(timeFormat())
console.log(priceFormat())
console.log(add(2, 1))
console.log(sub(2, 1))
// index.js
// 此文件为utils文件夹下所有工具函数中变量的统一出口

// 第一种导出方式
// import { add, sub } from './math.js'
// import { timeFormat, priceFormat } from './format.js'
// export {
//     add, 
//     sub,
//     timeFormat, 
//     priceFormat
// } 

// 第二种导出方式
// export { add, sub} from './math.js'
// export { timeFormat, priceFormat } from './format.js'

// 第三种导出方式
export * from './math.js'
export * from './format.js'
// format.js
function timeFormat() {
    return '20220104'
}

function priceFormat() {
    return 1122334455667788
}

export {
    timeFormat,
    priceFormat
}
// math.js
function add(num1, num2) {
    return num1 + num2
}

function sub(num1, num2) {
    return num1 - num2 
}

export {
    add,
    sub
}

3-4.default的使用

<!-- html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./main.js" type="module"></script>
</body>
</html>
// main.js
import part1Name from './part1'
import { sayName } from './part1'
console.log(part1Name)
sayName()
// 默认导出default

 const name = 'part1'
 function sayName() {
     console.log('我是part1')
 }

// 方式一:
// export {
//     name as default,
//     sayName
// }

// 方式二:
export default name
export { sayName }

// 注意:
// 1.一个文件中只能有一个默认导出

3-5.import函数

// main.js
// 1.从part1.js中导入了值时,part1会先解析完,再执行当前文件之后的代码
// import { name, sayName } from './part1' 
// console.log('开始执行main中的代码')
// console.log(name)
// sayName()

// 2.import函数:不让导入的文件阻塞当前文件中代码的执行
// import函数的返回值是一个promise,then中的就是part1文件中导出的值
import('./part1').then(res => {
    console.log('res', res)
})

// 3.import有一个mate属性,mate是一个对象,mate.url是当前文件的的路径
console.log(import.mate, import.mate.url)
// part1.js
console.log('开始执行part1中的代码')
const name = 'part1'
function sayName() {
    console.log('我是part1')
}
export { name, sayName}

参考文献:

「万字进阶」深入浅出 Commonjs 和 Es Module