【12】ES6:模块化

一、JavaScript 模块化

JavaScript 模块化是一种组织和管理 JavaScript 代码的方法,它将代码分割为独立的模块,每个模块都有自己的作用域,并且可以导出和导入功能。模块化可以提高代码的可维护性、可重用性和可扩展性。

在JavaScript中,有几种常见的模块化规范和工具,包括:

CommonJS:

复制代码
CommonJS 是一种服务器端 JavaScript 模块化规范,主要用于 Node.js。

使用 require() 函数导入模块,使用 module.exports 或 exports 导出模块。

支持同步加载模块,因此在浏览器端可能会阻塞页面渲染。

适用于服务器端开发,模块的加载是在运行时发生的。
javascript 复制代码
// CommonJS模块
let { stat, exists, readfile } = require('fs')

// 等同于
let _fs = require('fs')
let stat = _fs.stat
let exists = _fs.exists
let readfile = _fs.readfile

上面代码的实质是整体加载 fs 模块(即加载 fs 的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为"运行时加载",因为只有运行时才能得到这个对象,导致完全没办法在编译时做"静态优化"。

AMD (Asynchronous Module Definition):

复制代码
AMD 是一种浏览器端 JavaScript 模块化规范,旨在解决异步加载模块的问题。

使用 define() 函数定义模块,并使用 require() 函数异步导入模块。

支持异步加载模块,在运行时按需加载模块,不会阻塞页面渲染。

适用于浏览器端开发,特别是在网络环境较慢的情况下。

UMD (Universal Module Definition):

复制代码
UMD 是一种通用的 JavaScript 模块化规范,旨在兼容 CommonJS 和 AMD 规范。

根据环境判断是否支持 CommonJS、AMD 或全局对象,并采取相应的模块化方式。

可以在服务器端和浏览器端都使用,兼容不同的模块化规范。

ES6 (ES2015) 模块化:

复制代码
ES6 模块化是 ECMAScript 2015 引入的官方标准模块化规范。

使用 import 和 export 关键字来导入和导出模块。

静态分析:可以在编译时进行静态分析,提前处理模块依赖关系。

支持默认导出和命名导出,支持动态导入。

在现代浏览器和Node.js中得到广泛支持。
javascript 复制代码
// ES6模块
import { stat, exists, readFile } from 'fs'

上面代码的实质是从 fs 模块加载 3 个方法,其他方法不加载。这种加载称为"编译时加载"或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。

ES6 模块化是目前被广泛使用的标准模块化规范,它提供了更加简洁、灵活的语法,并且在编译阶段进行静态分析,能够提高性能。大多数现代的 JavaScript 开发都采用 ES6 模块化来组织和管理代码,尤其是在使用构建工具(如Webpack、Rollup等)进行项目构建时。

二、ES6 Module

模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。

1、导出(export)

(1)命名导出(Named Exports): 使用 export 命令指定导出一个变量(var、let、const)、函数(functions)或类(class)。

导出变量

javascript 复制代码
// 导出变量
export const name = 'Amy'
export let age = 20
export const city = 'Beijing'

// 等同于(推荐)
const name = 'Amy'
let age = 20
const city = 'Beijing'
export { name, age, city }
// 使用 as 关键字重命名
export { name as firstName }

// ----------------------------------------
// export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 以下写法报错
export 1 // 直接输出
export name // 通过变量 m,还是直接输出 1

导出函数

javascript 复制代码
export function multiply(x, y) {
	return x * y
}

export function calculateSum(a, b) {
	return a + b
}

// 等同于
function multiply(x, y) {
	return x * y
}
function calculateSum(a, b) {
	return a + b
}
export { multiply, calculateSum }


// 报错
export multiply

导出类

javascript 复制代码
export class Person {
	constructor(name, age) {
		this.name = name
		this.age = age
	}
	
	sayHello() {
		console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)
	}
}

(2)默认导出(Default Exports): 使用 export default 命令默认导出一个变量、函数、类。

默认导出变量

javascript 复制代码
// 默认导出一个值
export default 20 


// 导出一个对象作为默认值
const person = {
	name: 'Amy',
	age: 20,
}
export default person

默认导出函数

javascript 复制代码
// 导出一个默认的加法函数
export default function add(a, b) {
	return a + b
}

默认导出类:

javascript 复制代码
export default class Person {
	constructor(name, age) {
		this.name = name
		this.age = age
	}
	
	sayHello() {
		console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)
	}
}

2、导入(import)

(1)命名导入(Named Imports): 使用 import { xxx } from url 语法导入模块的指定变量、函数、或类。

export 导出的模块,在导入时不能随意取别名,名称必须与模块导出时相同!并且要使用类似于解构赋值的 {} 形式。

javascript 复制代码
// 命名导出
export const name = 'Amy'
export function sayHello() {
	console.log('Hello!')
}

// ----------------------------------------
// 命名导入
import { name, sayHello } from './module.js'

(2)默认导入(Default Imports): 使用 import xxx from url 语法导入模块的默认导出变量、函数、或类。

export default 导出的模块,在导入时可以取任意名称。并且 import 命令后面,不需要使用大括号。

javascript 复制代码
// 默认导出
const age = 18
export default age

// ---------------------------------------------
// 默认导入
import age from './module.js'

(3)整体导入: 用星号 * 指定一个对象,所有输出值都加载在这个对象上面。

将同一文件里的所有模块导入到一个对象中,不仅对 export 有效,同时对 export default 也同样有效。

javascript 复制代码
// 整体导入
import * as Obj from './module.js'
console.log(Obj) // 见图片
console.log(Obj.fn) // ƒ fn() {}
console.log(Obj.className) // class className {}
console.log(Obj.age) // 18
// export default 也同样有效:imObj.default

(4)同时导入

当我们需要分别导入 export default 和 export 时,可以使用同时导入的方式。

javascript 复制代码
// 我们可以分开实现
import { fn, className, age } from './module.js'
import sex from './module.js'
javascript 复制代码
// 更推荐使用同时导入的方式
import sex, { fn, className, age } from './module.js'
// 注意:export default 必须在 export 之前

3、导出导入时起别名

export 导出的变量或 import导入的变量,可以使用 as 关键字重命名。

javascript 复制代码
// 导出
function fn() {}
class className {}
const age = 18
export { fn as func, className as cN, age }
javascript 复制代码
// 导入
import { func, cN, age as nl } from './module.js'
console.log(func) // ƒ fn() {}
console.log(cN) // class className {}
console.log(nl) // 18

4、export 与 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import 语句可以与 export 语句写在一起。

javascript 复制代码
export { foo, bar } from 'my_module'

// 可以简单理解为
import { foo, bar } from 'my_module'
export { foo, bar }

上面代码中,export和import语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。

模块的接口改名和整体输出,也可以采用这种写法。

javascript 复制代码
// 接口改名
export { foo as myFoo } from 'my_module'

// 整体输出
export * from 'my_module'

默认接口的写法如下。

javascript 复制代码
export { default } from 'foo'

具名接口改为默认接口的写法如下。

javascript 复制代码
export { es6 as default } from './someModule'

// 等同于
import { es6 } from './someModule'
export default es6

同样地,默认接口也可以改名为具名接口。

javascript 复制代码
export { default as es6 } from './someModule'

ES2020 之前,有一种 import 语句,没有对应的复合写法。

javascript 复制代码
import * as someIdentifier from 'someModule'

ES2020 补上了这个写法。

javascript 复制代码
export * as ns from "mod";

// 等同于
import * as ns from 'mod'
export { ns }

5、export 与 import 的总结

在 ES6 中使用 export、和 export default 向外导出成员; 使用 import 来导入成员。

复制代码
在一个模块中,可以同时使用 export default 和 export 向外导出成员;export default 只允许向外导出一次。

使用 export default 向外暴露的成员,可以用任意变量接收。

使用 export 向外暴露的成员,必须用 {} 接收,变量之间用逗号分隔,且名称必须与导出时的名称一致。

export 可以向外暴露多个成员,如果某些成员,在 import 导入时,不需要,可以不在 {} 中定义

export 导出的变量或 import导入的变量,可以使用 as 关键字重命名。

6、import() 动态导入

import() 是 ES6 中的动态导入语法,它允许在运行时动态地加载模块。与传统的静态导入(使用 import 关键字:import 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。)相比,动态导入可以根据需要按需加载模块,提供了更大的灵活性。

javascript 复制代码
// 静态导入:语法错误(import 和 export 命令只能在模块的顶层,不能在代码块之中)
if (x === 2) {
	import MyModual from './myModual'
}

import 函数的参数 specifier,指定所要加载的模块的位置。import 命令能够接受什么参数,import() 函数就能接受什么参数,两者区别主要是后者为动态加载。

javascript 复制代码
// 动态导入
import(specifier)

import() 返回一个 Promise 对象,因此可以使用 async/await 等方式来处理导入的模块。

适用场合

(1)按需加载:import() 可以在需要的时候,再加载某个模块。

javascript 复制代码
// 只有用户点击了按钮,才会加载 dialogBox 模块
button.addEventListener('click', event => {
	import('./dialogBox.js').then(dialogBox => {
		dialogBox.open()
	}).catch(error => {
		/* Error handling */
	})
})

(2)条件加载:import() 可以放在 if 代码块,根据不同的情况,加载不同的模块。

javascript 复制代码
if (condition) {
	import('moduleA').then(...)
} else {
	import('moduleB').then(...)
}

(3)动态的模块路径:import() 允许模块路径动态生成。

javascript 复制代码
import(f()).then(...)

动态导入的优点

复制代码
可以根据需要按需加载模块,避免一次性加载所有模块,提高性能和加载速度。

可以在条件满足时才加载模块,实现延迟加载和懒加载的效果。

可以动态地根据运行时信息选择不同的模块进行加载。

三、ES6 Module 和 CommonJS 模块的区别

ES6 Module 是 JavaScript 的官方模块系统,而 CommonJS 是 Node.js 的模块系统。ESM 具有更强大的特性和更高的效率,适用于现代浏览器环境和某些前端工具链中。而 CJS 则适用于 Node.js 环境和一些较老的浏览器环境。然而,随着 Node.js 对 ESM 的支持不断改进,ESM 已经成为了通用的模块系统,并逐渐取代了 CJS 的使用。

1、语法差异

CommonJS 使用 module.exports = {} 导出一个模块对象,require('file_path') 引入模块对象。

ES6 Module 使用 import 和 export 关键字来导入和导出模块。

2、加载方式

CommonJS 在运行时同步加载模块。它会先加载整个模块,然后再执行模块代码。

ES6 Module 在解析阶段进行静态分析,即在编译时确定模块依赖关系。它使用异步加载模块的方式,在运行时按需加载模块。

3、输出差异

CommonJS 模块中,module.exports 导出的是一个值的拷贝。当其他模块使用 require 导入该模块时,会得到这个拷贝的值,而不是原始值本身。因此,如果在被导入的模块内部对导出的值进行修改,不会影响到其他模块中已经导入的值。

javascript 复制代码
// module.js
let count = 0

setTimeout(() => {
	count++
}, 1000)

module.exports = count

// main.js
const count = require('./module')

setTimeout(() => {
	console.log(count) // 输出: 0,因为count只是module.js导出的初始值的一个拷贝,不会变化
}, 2000)

ES6 Module 模块中,export 导出的是一个值的引用。当其他模块使用 import 导入该模块时,得到的是原始值的引用。这意味着,如果在被导入的模块内部对导出的值进行修改,会直接影响到其他导入该模块的地方,因为它们引用同一个原始值。

javascript 复制代码
// module.js
export let count = 0

setTimeout(() => {
	count++
}, 1000)

// main.js
import { count } from './module'

setTimeout(() => {
  console.log(count) // 输出: 1,因为count是module.js导出的原始值的引用,会随着变化而变化
}, 2000)

需要注意的是,虽然 ES6 模块的输出是引用,但并不意味着你可以直接修改导入的值。在严格模式下,对于 import 导入的值是只读的,不能直接修改。如果你想要修改导入的值,可以通过修改原始值或者使用 export default 来实现。

4、导入和导出特性

CommonJS 只支持默认导出,通过给 module.exports 赋值来导出模块。

ES6 Module 支持命名导出(Named Exports)和默认导出(Default Exports)两种导出方式。

相关推荐
伍哥的传说1 小时前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi9878381 小时前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
浪裡遊1 小时前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听12 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿2 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉2 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
归于尽2 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课2 小时前
React useEffect 详解与运用
前端·react.js
我想说一句2 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript
funnycoffee1232 小时前
Huawei 6730 Switch software upgrade example版本升级
java·前端·华为