Skip to content

Module 语法

ES6 模块是 JavaScript 官方的模块化方案,通过 importexport 实现代码的模块化组织。

export 导出#

命名导出#

math.js
export const PI = 3.14159
export function add(a, b) {
return a + b
}
export function multiply(a, b) {
return a * b
}
export class Calculator {
// ...
}

声明后导出#

utils.js
const name = '工具库'
function format(str) {
return str.trim()
}
function parse(str) {
return JSON.parse(str)
}
// 统一导出
export { name, format, parse }

导出时重命名#

helpers.js
function internalHelper() {
// 内部实现
}
function privateUtil() {
// 内部使用
}
export { internalHelper as helper, privateUtil as util }

默认导出#

person.js
export default class Person {
constructor(name) {
this.name = name;
}
}
// 或者
class Person {
constructor(name) {
this.name = name;
}
}
export default Person;

默认导出与命名导出混用#

api.js
export default function request(url) {
return fetch(url)
}
export const GET = 'GET'
export const POST = 'POST'
export function createHeaders(token) {
return { Authorization: `Bearer ${token}` }
}

默认导出的本质#

// 默认导出实际上是导出一个名为 default 的变量
export default function() {}
// 等价于
function _default() {}
export { _default as default };
// 所以可以这样导入
import { default as myFunc } from './module.js';

import 导入#

导入命名导出#

// 导入指定成员
import { add, multiply } from './math.js'
// 导入时重命名
import { add as sum, multiply as mul } from './math.js'
// 导入所有命名导出
import * as math from './math.js'
math.add(1, 2)
math.multiply(3, 4)

导入默认导出#

// 默认导出可以用任意名称导入
import Person from './person.js'
import MyPerson from './person.js' // 名称随意
// 等价于
import { default as Person } from './person.js'

同时导入默认和命名导出#

import request, { GET, POST, createHeaders } from './api.js'
// 或者
import request, * as api from './api.js'
api.GET // 'GET'

仅执行模块#

// 不导入任何内容,只执行模块代码
import './polyfill.js'
import './init.js'

模块路径#

相对路径#

import { helper } from './helpers.js'
import { utils } from '../shared/utils.js'
import config from './config/index.js'

包名导入#

import React from 'react'
import { useState, useEffect } from 'react'
import lodash from 'lodash'

子路径导入#

import { Button } from '@mui/material'
import { format } from 'date-fns/format'
import cloneDeep from 'lodash/cloneDeep'

export from 语法#

转发导出#

// index.js - 聚合模块
export { add, multiply } from './math.js'
export { format, parse } from './utils.js'
export { default as Person } from './person.js'

转发所有导出#

// 转发 math.js 的所有命名导出
export * from './math.js'
// 注意:不包括默认导出
// 默认导出需要单独处理
export { default } from './math.js'
// 或重命名
export { default as math } from './math.js'

重命名转发#

export { add as sum } from './math.js'
export { default as MathUtils } from './math.js'

模块聚合实践#

components/index.js
export { default as Button } from './Button.js'
export { default as Input } from './Input.js'
export { default as Modal } from './Modal.js'
export { default as Table } from './Table.js'
// 使用时
import { Button, Input, Modal } from './components'

动态导入#

import() 函数#

// 动态导入返回 Promise
const module = await import('./module.js')
// 或使用 then
import('./module.js').then((module) => {
module.doSomething()
})

条件导入#

async function loadFeature(featureName) {
if (featureName === 'chart') {
const { Chart } = await import('./features/chart.js')
return new Chart()
}
if (featureName === 'editor') {
const { Editor } = await import('./features/editor.js')
return new Editor()
}
}

懒加载#

// 按钮点击时才加载
button.addEventListener('click', async () => {
const { openDialog } = await import('./dialog.js')
openDialog()
})

动态路径#

async function loadLocale(lang) {
const locale = await import(`./locales/${lang}.js`)
return locale.messages
}
// 使用
const messages = await loadLocale('zh-CN')

模块的静态结构#

编译时确定#

// ES6 模块是静态的,以下写法不允许:
// ❌ 不能条件导入
if (condition) {
import { foo } from './foo.js'; // SyntaxError
}
// ❌ 不能动态路径(静态 import)
import { foo } from './' + name + '.js'; // SyntaxError
// ❌ 不能在块作用域中
{
import { foo } from './foo.js'; // SyntaxError
}

静态分析的好处#

// 1. Tree Shaking - 未使用的导出可以被移除
import { used } from './utils.js'
// unused 不会被打包
// 2. 提前发现错误
import { typo } from './module.js'
// 如果 typo 不存在,编译时就能发现
// 3. 更好的 IDE 支持
import { func } from './module.js'
// IDE 可以提供自动补全和跳转

模块的特性#

严格模式#

// ES6 模块自动采用严格模式
// 不需要 'use strict'
export function example() {
// this 是 undefined,不是 window
console.log(this)
}

顶层 this#

// 模块顶层的 this 是 undefined
console.log(this) // undefined
// 不是 window 或 global

导出的是引用#

counter.js
export let count = 0
export function increment() {
count++
}
// main.js
import { count, increment } from './counter.js'
console.log(count) // 0
increment()
console.log(count) // 1(是引用,不是值的复制)

模块只执行一次#

init.js
console.log('模块初始化')
export const value = Math.random()
// a.js
import { value } from './init.js'
// 打印 '模块初始化'
console.log(value) // 0.123...
// b.js
import { value } from './init.js'
// 不会再打印,模块已执行过
console.log(value) // 0.123...(同一个值)

import.meta#

获取模块元信息#

// 获取当前模块的 URL
console.log(import.meta.url)
// file:///path/to/module.js
// 或 https://example.com/module.js
// 获取当前目录
const dirname = new URL('.', import.meta.url).pathname

实际应用#

// 加载相对于当前模块的资源
const imageUrl = new URL('./image.png', import.meta.url)
// 读取相邻的 JSON 文件(Node.js)
import { readFile } from 'fs/promises'
const configPath = new URL('./config.json', import.meta.url)
const config = JSON.parse(await readFile(configPath, 'utf8'))

循环依赖#

循环依赖的处理#

a.js
import { b } from './b.js'
export const a = 'a'
console.log('a.js:', b)
// b.js
import { a } from './a.js'
export const b = 'b'
console.log('b.js:', a)
// main.js
import './a.js'
// 输出:
// b.js: undefined(a.js 还没执行完)
// a.js: b

解决循环依赖#

a.js
// 方案1:重构代码,消除循环依赖
// 方案2:使用函数延迟访问
import { getB } from './b.js'
export const a = 'a'
export function getA() {
return a
}
console.log('a.js:', getB())
// b.js
import { getA } from './a.js'
export const b = 'b'
export function getB() {
return b
}
console.log('b.js:', getA())

最佳实践#

导出风格一致#

Button.js
// ✅ 推荐:一个文件一个主要导出
export default function Button() {}
// ✅ 推荐:工具文件使用命名导出
// utils.js
export function formatDate() {}
export function formatNumber() {}
// ❌ 避免:既有默认导出又有大量命名导出
export default class API {}
export const GET = 'GET';
export const POST = 'POST';
// ... 更多导出

使用 index.js 聚合#

components/index.js
export { default as Button } from './Button.js'
export { default as Input } from './Input.js'
// 使用
import { Button, Input } from './components'

避免命名空间导入滥用#

// ❌ 避免:导入所有可能影响 Tree Shaking
import * as utils from './utils.js'
utils.format()
// ✅ 推荐:只导入需要的
import { format } from './utils.js'
format()

小结#

语法说明
export导出模块成员
export default默认导出
import 导入命名导出
import x导入默认导出
import * as命名空间导入
import()动态导入
export from转发导出

ES6 模块是 JavaScript 模块化的标准方案,掌握它对于现代前端开发至关重要。