前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库script type=module 与普通脚本区别
JavaScript模块与语言机制

<script type="module"> 和普通 script 标签有哪些区别?

模块脚本能用 import 和 export,此外还有几个行为差异:默认像 defer 一样延迟执行,自动严格模式,顶层变量只在模块内可见不会挂到 window,跨域加载必须有 CORS,同一个模块 URL 只执行一次。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#模块与语言机制
先看核心答案读代码示例
理解线索

差异可以归成三类

  1. 加载时机默认延迟执行,async 对内联模块也生效
  2. 作用域和模式独立的模块作用域,强制严格模式
  3. 请求方式按 CORS 模式请求,同一 URL 只加载执行一次

普通脚本之间靠全局变量通信,换成模块后顶层变量彼此不可见。老代码里一个脚本定义 var config、另一个脚本直接用,改成模块后会报 ReferenceError,要么改成导入导出,要么显式挂到 window 上。

核心回答

先记住这个答案

<script type="module"> 除了能用 import 和 export,还和普通脚本有好几处行为差异。执行时机上,它默认像加了 defer,不阻塞 HTML 解析,等文档解析完按顺序执行。代码层面,它自动是严格模式,有独立的模块作用域,顶层声明不会挂到 window 上,顶层 this 是 undefined。加载层面,它按 CORS 模式请求,跨域资源必须有 Access-Control-Allow-Origin 响应头,同一个 URL 在页面里只执行一次。

  • 模块脚本默认延迟执行,不阻塞 HTML 解析
  • 自动严格模式,顶层 this 是 undefined
  • 顶层 var 和函数不会变成 window 的属性
  • 跨域模块请求受 CORS 约束

同样的代码,两种标签跑出不同结果

页面上放一个普通脚本和一个模块脚本,内容几乎一样。

作用域、this 和执行顺序HTML
<script type="module">
  var a = 1
  console.log('模块', this, window.a) // 模块 undefined undefined
</script>

<script>
  var b = 2
  console.log('普通', this === window, window.b) // 普通 true 2
</script>

<!-- 控制台先打印「普通」,再打印「模块」 -->

模块脚本写在前面,却后执行,因为它默认延迟到文档解析完才运行。普通脚本遇到就立即执行。 模块里的 var a 只存在于这个模块自己的作用域,window.a 是 undefined;普通脚本的顶层 var b 会变成 window.b。模块的顶层 this 是 undefined,普通脚本的顶层 this 是 window。

加载方面的差异

这几条平时感觉不到,一旦碰上就是线上问题。

CORS、只执行一次和 async
模块脚本总是按 CORS 模式请求。从另一个域名的 CDN 加载模块,对方必须返回允许跨域的响应头,否则浏览器拒绝执行。普通 <script src> 跨域加载则不需要。本地直接双击打开 HTML 文件时,页面来源是 file://,模块请求也被当成跨域,所以会报错,要起一个本地 HTTP 服务。 同一个模块 URL 在页面上写两次 <script type="module" src>,只会执行一次,因为模块有缓存。普通脚本写两次就执行两次。 async 属性对模块脚本也有效,而且对内联模块脚本同样生效:加了 async 的模块在自身和依赖都加载完后立刻执行,不再等文档解析完,也不保证顺序。普通脚本的 async 只对外部脚本有效。 服务器返回的 MIME 类型也要是 JavaScript 类型,比如 text/javascript,模块脚本对此是严格检查的,返回 text/plain 会被拒绝执行。
回答前,多想一步

容易答错的地方

以为模块脚本里的函数能在 HTML 的 onclick 里调用
<button onclick="handleClick()"> 在全局作用域里找 handleClick,而模块里定义的函数不在全局。要么在模块里用 addEventListener 绑定事件,要么显式写 window.handleClick = handleClick。
以为给模块脚本加 defer 会改变什么
模块脚本本来就是延迟执行的,加 defer 没有任何效果。能改变它时机的只有 async。
以为模块脚本会在 DOMContentLoaded 之后执行
非 async 的模块脚本和 defer 脚本一样,在文档解析完成后、DOMContentLoaded 事件触发前执行。所以在模块脚本里注册 DOMContentLoaded 监听是能收到事件的,但直接操作 DOM 也已经没问题了。
试着用自己的话回答

面试官还会怎么问?

模块脚本加载的依赖会阻塞页面吗?

不会阻塞 HTML 解析,但依赖是在执行前一层层发现的:先下载入口,解析出 import 才知道要下载哪些依赖,依赖又有依赖,层级深了就会形成请求瀑布。可以用 <link rel="modulepreload"> 提前声明关键依赖,让浏览器并行下载。

模块脚本里能用顶层 await 吗?

能,这也是模块独有的能力,普通脚本顶层写 await 是语法错误。但要注意它会让依赖这个模块的其它模块一起等待。

为什么模块脚本要求 CORS?

普通脚本跨域加载不需要授权,是早年 Web 留下的历史包袱,比如跨域脚本出错时页面只能拿到一句 Script error.,就是为了弥补这个口子。模块脚本是新设计的加载方式,没有兼容负担,规范就直接采用了更安全的默认:跨域资源必须由服务端显式允许。

从一道题,走向一组知识

把知识连起来

模块与语言机制

import source 是什么?它和普通 import 有什么不一样?

模块脚本除了能 import 求值后的模块,新提案还让它能导入 WebAssembly 等模块的源码对象。

模块与语言机制

ES 模块为什么默认就是严格模式?对模块里的函数有什么影响?

模块脚本自动开启严格模式是本题的一条差异,那道题展开讲严格模式具体改变了什么。

参考资料

  • JavaScript:模块与语言机制参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 同样的代码,两种标签跑出不同结果
  3. 加载方面的差异
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑