var声明有时为undefined因变量提升仅发生在创建阶段而赋值在执行阶段,脚本按html顺序逐块解析,块间不共享执行时机。

多个<script>中var声明为什么有时是undefined</script>
因为变量提升只发生在“创建阶段”,而赋值发生在“执行阶段”——两个阶段被脚本加载顺序严格切割。浏览器按HTML顺序逐个解析每个<script>块,在每个块内完成自己的变量提升和执行,但块与块之间不共享执行时机。</script>
常见错误现象:ReferenceError: a is not defined 或更隐蔽的 a 为 undefined,尤其当第二个脚本试图读取第一个脚本中 var a = 1 的值却失败时。
-
<script>var a = 1;</script>在前,<script>console.log(a);</script>在后 → 输出1 - 顺序颠倒 → 第二个脚本执行时,第一个脚本还没加载,
a确实未声明,报ReferenceError - 如果第一个脚本用
let a = 1,哪怕位置在前,第二个脚本仍会报错——let不提升,且存在暂时性死区(TDZ)
function声明能跨<script>调用,但执行时机仍可能出问题</script>
function 声明会被完整提升到全局上下文的创建阶段,挂载到 window 上,所以只要它所在的 <script></script> 已加载完毕(哪怕还没执行到函数体),后续脚本就能调用它。但这不等于“安全”——执行时机仍可能出问题。
使用场景:工具函数提前声明、初始化逻辑分层(如先定义 init(),再在 DOM 就绪后调用)。
- 前一个
<script></script>中写function init() { console.log('ready'); }→ 后一个可直接init() - 但如果前一个脚本里是
const init = () => {}或var init = function() {},则只提升变量名(init为undefined),调用会报TypeError - 即便函数已提升,若其内部依赖 DOM 元素,而调用发生在
解析前,仍会操作不到节点
动态插入的<script>不会延续原有全局上下文的执行流</script>
用 document.createElement('script') 插入的脚本,会触发一次新的、独立的全局执行上下文创建流程——它有自己的变量提升阶段,不继承之前脚本中 let/const 的值,也不感知前序脚本中尚未执行的赋值语句。
容易踩的坑:以为“插进去就等于接在上一个 <script></script> 后面”,结果发现变量突然变 undefined,或函数调用报错。
- 默认行为是
async = true,下载完立刻执行,不保证顺序;需显式设script.async = false才能同步阻塞(慎用) -
script.defer = true仅对同域有效,且只延迟到 DOM 解析完成,不解决跨脚本变量链断裂问题 - 更可靠的做法是封装成 Promise:
loadScript('a.js')
defer脚本能保障变量就绪,但只对带src的外链脚本生效
defer 脚本的全局执行上下文总是在 document.readyState === 'interactive' 或 'complete' 阶段启动,此时 HTML 解析已完成,DOM 树已构建完毕,所有元素都可被访问。
多个 defer 脚本按 HTML 中出现顺序依次执行,前一个的全局变量可被后一个直接引用。
-
<script src="lib.js" defer></script>定义window.MyLib -
<script src="app.js" defer></script>使用MyLib.init()—— 可靠 -
<script defer>init();</script>会被浏览器忽略,defer对内联脚本无效 - Webpack/Vite 构建产物若已用
type="module",再手动加defer实际无效——模块脚本默认具defer语义
真正容易被忽略的是:defer 只管外链脚本的执行时机,不管它内部的动态行为。即使加了 defer,如果脚本里用了 import(),后续模块的加载和执行仍不受约束,得靠代码自己兜底。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











