<script type=“module“>用法的基本介绍
更好的代码组织- 模块化架构明确的依赖关系- 静态分析支持作用域隔离- 避免全局污染现代特性支持- 顶层await、动态导入等开发体验提升- 结合Vite等工具实现快速HMR对于新项目,强烈推荐使用ES模块作为标准开发方式。
·
文章目录
一、文章参考
二、基本语法
2.1 快速入门
<!-- 方式1: 内联模块 -->
<script type="module">
import { greet } from './utils.js';
greet('World');
</script>
<!-- 方式2: 外部模块文件 -->
<script type="module" src="main.js"></script>
2.2 引用要有扩展名和完整的URL路径
// ❌ 错误的导入方式
import utils from './utils' // 缺少文件扩展名
import '../module' // 缺少文件扩展名
import { foo } from 'library' // 需要完整的URL或配置
// ✅ 正确的导入方式
import utils from './utils.js' // 必须有扩展名
import config from '/src/config.js' // 绝对路径
import lodash from 'https://cdn.skypack.dev/lodash' // 完整URL
// 相对路径基准是当前HTML文件位置
import './lib/module.js' // 相对于HTML文件
三、核心特性与例子
1. 模块导入/导出
// math.js - 导出模块
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
export default function multiply(a, b) {
return a * b;
}
// main.js - 导入模块
import multiply, { add, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(PI); // 3.14159
2. 严格模式自动启用
// type="module" 中自动启用严格模式
// 以下代码会报错(非模块脚本不会报错)
x = 10; // ReferenceError: x is not defined
delete Object.prototype; // TypeError
3. 作用域隔离\模块作用域,不会污染全局
<script type="module">
const message = "模块内部";
console.log(message); // "模块内部"
</script>
<script type="module">
console.log(message); // ReferenceError: message is not defined
</script>
<script>
console.log(message); // ReferenceError: message is not defined
</script>
<script type="module">
// 模块作用域,不会污染全局
const privateVar = 'secret'
// 需要显式导出到全局
window.myModule = {
publicMethod: () => console.log(privateVar)
}
</script>
<script>
// 传统脚本不能访问模块变量
console.log(privateVar) // undefined
window.myModule.publicMethod() // 可以访问
</script>
4. 延迟执行(Defer)
<!-- 模块脚本自动延迟执行 -->
<script type="module">
console.log('模块执行'); // 会在HTML解析后执行
</script>
<script>
console.log('普通脚本'); // 可能先执行
</script>
5. 跨域限制
<!-- 需要正确的CORS头部 -->
<script type="module" src="https://other-site.com/module.js"></script>
<!-- 需要服务器设置: Access-Control-Allow-Origin: * -->
6. 动态导入
// 按需加载模块
button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.doSomething();
});
四、实际项目结构示例
project/
├── index.html
├── main.js # 入口文件
└── modules/
├── api.js # API模块
├── utils.js # 工具函数
├── ui.js # UI组件
└── config.js # 配置
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>模块化应用</title>
</head>
<body>
<script type="module" src="main.js"></script>
</body>
</html>
// main.js - 入口文件
import { initApp } from './modules/app.js';
import { loadConfig } from './modules/config.js';
(async () => {
const config = await loadConfig();
await initApp(config);
})();
五、type="module " 的优势
1. 代码组织
// 传统的非模块方式 - 全局污染
var utils = {}; // 全局变量
utils.formatDate = function() { /* ... */ };
// 模块方式 - 清晰的依赖关系
import { formatDate } from './dateUtils.js';
import { validateEmail } from './validation.js';
2. 依赖管理清晰
// package.json + 模块导入
import lodash from 'lodash-es'; // ES模块版本
import axios from 'axios';
3. 浏览器缓存模块
// 模块只会执行一次
// module.js
console.log('模块执行')
// main1.js
import './module.js' // 输出: "模块执行"
// main2.js
import './module.js' // 无输出,已缓存
// 强制重新加载(不推荐)
const freshModule = await import('./module.js?v=' + Date.now())
4. 顶层await的限制
// ✅ 允许顶层await
const response = await fetch('/api/data')
const data = await response.json()
// ❌ 在非模块脚本中不允许
// <script> // 传统脚本中会报错
// await fetch() // SyntaxError
// </script>
5. 多个模块合并为一个模块
// ❌ 避免过多的小模块请求
import tinyUtil1 from './util1.js' // 1KB
import tinyUtil2 from './util2.js' // 1KB
import tinyUtil3 from './util3.js' // 1KB
// 导致3个HTTP请求
// ✅ 合并相关模块
// utils/index.js
export { default as util1 } from './util1.js'
export { default as util2 } from './util2.js'
export { default as util3 } from './util3.js'
// main.js
import { util1, util2, util3 } from './utils/index.js'
5. 查看模块依赖
// 在浏览器控制台
import.meta.url // 当前模块URL
import.meta.resolve('./module.js') // 解析模块路径
使用场景
1. 现代Web应用
// 单页面应用(SPA)
import { createApp } from 'vue';
import router from './router';
import store from './store';
createApp(App).use(router).use(store).mount('#app');
2. 组件库开发
// 组件库入口
export { default as Button } from './components/Button.svelte';
export { default as Modal } from './components/Modal.svelte';
export { default as Input } from './components/Input.svelte';
3. 工具函数库
// utils/index.js
export * from './string-utils.js';
export * from './array-utils.js';
export * from './date-utils.js';
export * from './validation.js';
4. 第三方库的ES模块版本
<!-- 直接使用ES模块CDN -->
<script type="module">
import { html, render } from 'https://unpkg.com/lit-html?module';
render(html`<h1>Hello Module!</h1>`, document.body);
</script>
注意事项
1. 浏览器支持
- 现代浏览器都支持
- 旧浏览器需要构建步骤(Webpack、Vite等)
2. 文件路径
// 需要明确的文件扩展名
import './module.js'; // ✅ 正确
import './module'; // ❌ 错误(在原生模块中)
// 需要正确的路径
import './modules/utils.js'; // ✅ 相对路径
import '/src/utils.js'; // ✅ 绝对路径
import 'https://site.com/module.js'; // ✅ 完整URL
3. 与普通脚本的区别
<!-- 模块脚本 -->
<script type="module">
// 自动defer,作用域隔离,支持import/export
</script>
<!-- 普通脚本 -->
<script>
// 可能阻塞渲染,全局作用域
</script>
4. 开发环境配置
// package.json
{
"type": "module", // Node.js中也使用ES模块
"scripts": {
"dev": "vite", // 使用支持原生ES模块的构建工具
"build": "vite build"
}
}
实时示例
<!DOCTYPE html>
<html>
<head>
<title>模块示例</title>
</head>
<body>
<button id="load">加载模块</button>
<div id="output"></div>
<script type="module">
// 主模块
import { showMessage } from './messageModule.js';
document.getElementById('load').addEventListener('click', async () => {
// 动态导入大型模块
const chartModule = await import('./chartModule.js');
chartModule.renderChart();
showMessage('模块加载成功!');
});
</script>
</body>
</html>
// messageModule.js
export function showMessage(text) {
document.getElementById('output').textContent = text;
}
// chartModule.js
export function renderChart() {
console.log('渲染图表...');
}
总结
type="module" 是现代JavaScript开发的基础,它提供了:
- 更好的代码组织 - 模块化架构
- 明确的依赖关系 - 静态分析支持
- 作用域隔离 - 避免全局污染
- 现代特性支持 - 顶层await、动态导入等
- 开发体验提升 - 结合Vite等工具实现快速HMR
对于新项目,强烈推荐使用ES模块作为标准开发方式。
二、script 标签的行为
2.1 默认行为(无任何修饰)
- 浏览器会立即加载并执行指定的脚本
- 会阻塞 HTML 文档解析,并按照它们出现的顺序执行
2.2 <script async >
- 特性
- async 仅适用于外链,规定脚本异步执行
- 下载不会阻塞页面解析
脚本的加载完成后就马上执行,脚本执行时会阻塞 HTML 解析- 不会按照出现的顺序执行,
先下载完成哪个就先执行哪个 - 执行的时候,有可能页面还没解析完成
- DOMContentLoaded事件的触发并不受async脚本
- 加载的影响。
- 脚本下载与 HTML 解析并行,但
一旦脚本加载完成,就会中断 HTML 解析,同时执行脚本。
2.3 <script defer >
- 特性
- defer 仅适用于外链,规定脚本延迟执行
- 不会阻塞页面解析
- 会按照出现的
顺序执行 在 HTML 解析完成后, DOMContentLoaded 之前执行
- 脚本下载与 HTML 解析并行,
等 HTML 解析完成后每个脚本都会有序执行
2.4 <script type=“module” >
在script标签中写js代码,或者使用src引入js文件时,
默认不能使用module形式,即不能使用import导入文件,但是我们可以再script标签上加上type=module属性来改变方式。
type=“module” 与 async、defer修饰符同时使用
-
<script type="module"> 为类似于 <script defer> ,但是模块依赖关系也会被下载 -
<script type=“module” async> 行为类似于 <script async> ,额外的模块依赖关系也会被下载
案例 —— 在HTML中像ES6编码方式编程
- demo.html
<html>
<head>
<script src='main.js' async type="module"></script>
</head>
<body>
<a class="btn">Search</a>
</body>
</html>
- main.js
import Movie from "./movie.js";
const btnSearch = document.querySelector(".btn");
btnSearch.addEventListener("click", function() {
let filmeTeste = new Movie(
"The Lego Movie",
2014,
"tt1490017",
"Movie",
"https://m.media-amazon.com/images/M/MV5BMTg4MDk1ODExN15BMl5BanBnXkFtZTgwNzIyNjg3MDE@._V1_SX300.jpg"
);
console.log(`object test ${filmeTeste}`);
});
- movie.js
class Movie {
constructor(title, year, imdbID, type, poster) {
this.title = title;
this.year = year;
this.imdbID = imdbID;
this.type = type;
this.poster = poster;
}
}
export default Movie;
更多推荐


所有评论(0)