You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript报ReferenceError:初始化前无法访问hasReleased变量

错误原因

这个报错和你有没有写内联初始化没有关系,本质是ES模块循环依赖触发了let/const的暂时性死区(TDZ)规则:

  • ES模块加载时会先扫描所有导入导出绑定,遇到import会优先加载被依赖的模块。如果两个模块存在直接/间接的循环引用(比如A导入B,B又反过来导入A),就可能出现模块代码还没执行完,导出的方法就被外部调用的情况。
  • let/const声明的变量不存在变量提升,在代码执行到声明赋值行之前,变量会一直处于TDZ中,只要在这之前访问就会抛出你看到的ReferenceError——哪怕你已经给变量写了初始值,只要执行流没走到那一行,访问就是非法的。
  • 你当前的执行顺序大概率是:
    1. 调用方模块开始加载,遇到导入onScreenLockReleasedFirstTime的语句,转去加载ScreenLock.tsx
    2. ScreenLock.tsx加载过程中,顶部存在对调用方模块(或其他依赖调用方的模块)的导入,又转回去加载调用方模块
    3. 调用方模块此时已经拿到了onScreenLockReleasedFirstTime的函数引用(ES模块导出是实时绑定),直接执行调用
    4. 此时ScreenLock.tsx的执行流还停留在处理导入的阶段,根本没走到let hasReleased = false这一行,访问hasReleased直接触发TDZ报错。
修复方案

方案1:打破循环依赖(根治,最推荐)

梳理两个模块的依赖关系,把互相耦合的公共逻辑抽离到独立的第三个公共模块中,消除循环引用链,保证ScreenLock.tsx的顶层代码全部执行完成后,外部才会调用它导出的方法,从根源上避免TDZ问题。

方案2:调整状态存储方式,规避顶层变量TDZ

如果暂时没法梳理清楚依赖关系,可以把状态从顶层let变量迁移到函数静态属性上,加一层兜底初始化,改完的ScreenLock.tsx代码如下:

const firstTimeCbs: (() => void)[] = [];
export function onScreenLockReleasedFirstTime(cb: () => void) {
  // 首次调用兜底初始化,避免外层赋值没执行的TDZ问题
  if (typeof onScreenLockReleasedFirstTime.hasReleased === 'undefined') {
    onScreenLockReleasedFirstTime.hasReleased = false;
  }
  if (!onScreenLockReleasedFirstTime.hasReleased) firstTimeCbs.push(cb);
  else cb();
}

// 你原有触发锁屏释放的逻辑里,把标记位改为true即可
// 例如释放逻辑中加:
// onScreenLockReleasedFirstTime.hasReleased = true;
// firstTimeCbs.forEach(cb => cb());

函数声明会在模块加载阶段优先完成初始化,调用时函数本身一定是可访问的,状态挂载到函数上再加兜底逻辑,就不会触发TDZ问题。

方案3:临时用var声明(不推荐,仅应急)

把let hasReleased = false改为var hasReleased = false。var声明的变量会提升到作用域顶部,初始值为undefined,调用函数时!undefined结果为true,和你预期的初始状态逻辑一致,等后续执行流走到赋值行时会自动赋值为false,不会影响业务逻辑。但var的变量提升特性容易埋下其他隐患,只适合临时救急。


内容的提问来源于stack exchange,提问作者Alexis Leite Paracampo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 03:06:37