JavaScript 原生模块怎么用?从文件拆分到动态导入排错

编程狮(w3cschool.cn) 2026-09-15 16:46:14 浏览数 (36)
反馈

JavaScript 原生模块通过 type="module" 启用,使用 import 和 export 连接文件;动态 import 返回 Promise,适合按需加载。你把原来一段脚本拆成两个文件,页面却突然出现语法错误或加载失败,往往并不是函数本身写错,而是模块入口、文件路径或服务器响应不符合要求。

原生模块怎么拆从入口到按需加载

本文把商品小计与说明文字拆成两个模块,再由主入口加载,给出完整目录和每个文件的内容。示例使用现代浏览器,不依赖打包工具。你需要一个本地 HTTP 服务;直接双击 HTML 得到的 file 地址,不适合验证浏览器模块加载流程。

一、模块拆分先明确谁依赖谁

模块像一组有明确出口的零件:price.js 只负责金额计算,help.js 只提供说明,main.js 负责页面交互。主入口可以依赖计算模块,但计算模块不需要知道页面上有哪个按钮。

这种边界可以减少互相牵连。更改按钮文案不应迫使你修改计算函数;更改金额规则也不应要求整个页面都认识内部变量。export 指定外部能够导入的名字,不导出的变量留在模块内部。

模块与函数基础可对照 JavaScript 教程。不要把“分成几个文件”直接等同于模块化:如果每个文件仍然依赖同一堆全局变量,依赖关系依旧难以维护。

二、建立一个能够运行的四文件例子

在同一目录创建 index.html、price.js、help.js 和 main.js。下面先给页面入口。

<!doctype html>
<meta charset="utf-8">
<title>编程狮原生模块练习</title>
<p id="total"></p>
<button type="button" id="help">加载说明</button>
<p id="message"></p>
<script type="module" src="./main.js"></script>

price.js 导出一个具名函数。它按整数分计算,输入必须是非负安全整数,结果也不能超出安全整数范围。

export function subtotal(price, quantity) {
  if (!Number.isSafeInteger(price) || !Number.isSafeInteger(quantity)
      || price < 0 || quantity < 0) {
    throw new TypeError('单价和数量必须是非负安全整数');
  }
  const result = price * quantity;
  if (!Number.isSafeInteger(result)) throw new RangeError('总额超出范围');
  return result;
}

help.js 使用默认导出。一个模块可以有多个具名导出,但最多只有一个默认导出。两种形式对应不同的导入语法。

export default '金额以分计算,说明模块仅在点击后导入。';

main.js 在启动时导入小计函数,在用户点击按钮时导入说明模块。动态加载失败会显示错误,不把未加载成功的状态当作成功。

import { subtotal } from './price.js';

const total = document.querySelector('#total');
const button = document.querySelector('#help');
const message = document.querySelector('#message');
total.textContent = `小计:${subtotal(1250, 2)} 分`;
button.addEventListener('click', async () => {
  button.disabled = true;
  try {
    const module = await import('./help.js');
    message.textContent = module.default;
  } catch (error) {
    message.textContent = '说明加载失败,请检查请求和控制台。';
    console.error(error);
  } finally {
    button.disabled = false;
  }
});

如果本机已有 Python,可以在这个目录运行 python -m http.server 8000 --bind 127.0.0.1,然后打开本机八千端口。这里的 HTTP 服务只是开发用文件服务,不是上线部署方案。

初次打开时应显示 2500 分,点击按钮才显示说明。浏览器开发者工具的 Network 面板可以观察文件请求,Console 面板查看语法和运行异常。

三、分清具名导入、默认导入和动态导入

具名导入中的 subtotal 必须与导出名匹配,若想换本地名称,可以用 as。默认导入可以选择本地变量名,但对应的模块必须确实提供默认导出。不要仅因为名字相同就随意增删花括号。

动态 import 得到的是模块命名空间对象,默认导出放在 default 属性,具名导出则使用对应属性名。示例中的 await 先等待加载和求值完成,再读取 module.default;如果直接把 Promise 当作文字显示,就无法得到预期内容。

场景 使用方式 需要记住的区别
页面启动就要计算 静态具名导入 导出名称需要匹配
模块只有主要说明对象 默认导出 导入时不加花括号
点击后才使用功能 动态导入 结果是 Promise

语法细节可结合 JavaScript 速查手册 核对。浏览器中的相对导入从当前模块地址解析,不是从你想象的项目根目录解析;main.js 移到子目录后,原来的相对路径也可能需要调整。

同一模块标识通常不会在每次 import 时重新执行一遍顶层代码。模块加载缓存也意味着修改文件后测试应注意刷新与缓存条件,不能只靠重复点击按钮判断是否读到了新版本。

四、从错误信息定位具体层次

看到“不能在模块外使用 import”一类错误,先看 script 是否标注 type="module"。如果是“找不到导出”,检查导出名称和默认/具名方式。遇到四零四或 MIME 类型错误,就去 Network 看文件实际返回内容。

请求地址看起来像 JavaScript 文件,服务器却返回了 HTML 错误页,是很常见的情况。浏览器不会因为文件名以 .js 结尾就把任意响应当成正确模块;应检查状态、响应头和响应体,而不是反复更换导入语法。

浏览器原生模块也不会自动理解所有 npm 包名。直接写一个裸包名,需要 import map 或构建工具提供解析规则。本文全用相对路径,故不需要包管理器。跨域加载时还受跨域规则影响,应由提供模块的服务器正确配置。

测试失败分支时,可以把 help.js 临时改名,再用新的页面加载尝试按钮。页面应显示失败提示,按钮最终恢复可点。因为成功加载过的模块可能已缓存,测试要在新页面上下文中进行,避免误以为失败处理没有效果。

文件路径、导出名称、响应内容逐层核对:开发验证使用 HTTP;模块缓存影响失败分支复测

总结

JavaScript 原生模块的关键是依赖方向、导出形式和加载环境。四个文件的小例子已经覆盖启动导入、按需导入和错误反馈,不需要先引入复杂工程配置。

下一步可以增加一个 discounts.js,只导出折扣计算,再让 price.js 组合它。新增依赖时画清“谁引用谁”,保持计算模块不依赖 DOM。遇到问题按入口类型、路径、网络响应、导出名称逐层检查,比无目的地改扩展名或加花括号更有效。

延伸学习

  1. JavaScript Guide 可补函数、异步和对象相关知识。
  2. JavaScript 原生模块课程 可继续练习模块组织方式。
  3. JavaScript 作用域说明 可辅助理解模块内部变量与全局变量的区别。

常见问题

Q:为什么双击 HTML 运行和本地服务结果不同?

浏览器对本地文件与 HTTP 资源的安全处理不同。验证模块加载应使用本地 HTTP 服务,避免 file 地址带来的限制干扰判断。

Q:动态导入一定能让页面更快吗?

不一定。它推迟下载与求值,但用户点击时可能等待加载。适合不常用或体积较大的功能,具体收益仍需要测量。

Q:模块里声明的变量会自动出现在 window 上吗?

不会。模块有自己的作用域,需要通过导出和导入共享。把变量主动挂到 window 是另一种操作,不是模块默认行为。

技术依据:MDN JavaScript 模块指南

0 人点赞