document.getElementById 返回 null 是因脚本执行时机早于 DOM 构建完成,而非网络延迟;国际化脚本若用 async 加载,会抢在 HTML 解析完毕前执行,导致元素尚未入树。

为什么 document.getElementById 返回 null 不是网络慢,而是执行时机错配
国际化脚本(比如 i18n.js 或 locale/en.json)若用 async 加载,极大概率在 DOM 尚未解析完成时就执行 —— 此时 document.getElementById('header-title') 必然返回 null。这不是请求慢,是脚本抢在 HTML 构建完之前就跑起来了。
常见现象包括:Cannot set property 'textContent' of null、翻译文案没生效但控制台无报错、部分元素被翻译而另一些完全没反应。
- 只要脚本里出现
document.querySelector、getElementById、addEventListener,就必须排除async -
defer是安全底线:它保证脚本在 HTML 解析完毕、DOMContentLoaded触发前执行,DOM 树完整可用 -
defer仅对带src的外部脚本生效;内联<script defer>initI18n()</script>中的defer被浏览器直接忽略 - 多个国际化资源(如
locales/en.js、locales/zh.js)必须按顺序加载,defer天然保序,async则完全不可控
如何让 locale 数据异步加载又不破坏 DOM 操作链
纯 defer 只解决执行时机,不解决数据依赖 —— 比如 main.js 依赖 i18n.js 导出的 t() 函数,但两者都设 defer 也不够:若 i18n.js 下载慢,main.js 执行时 window.t 还未定义。
正确做法是把数据加载和 DOM 渲染解耦,用显式 Promise 控制流转:
立即学习“前端免费学习笔记(深入)”;
- 用
fetch('/locales/' + lang + '.json')动态加载语言包,而非一次性预载全部 - 在
DOMContentLoaded后触发加载,避免竞态:document.addEventListener('DOMContentLoaded', () => loadLocale('en')) - 加载成功后再调用
renderI18nElements(),而不是在loadLocale内部直接操作 DOM - 失败时 fallback 到默认语言,并记录错误:
console.warn('Failed to load locale en, using zh-CN')
动态 import() 加载语言模块时要注意路径和错误处理
ES 模块方式(import('./locales/en.js'))比 script 标签更可控,但约束更强:路径必须是字符串字面量或受控变量,且必须带扩展名。
典型错误包括:import('./locales/' + lang) 触发 CSP 报错、import('./locales/en') 因缺少 .js 后缀静默失败、旧版 IE 直接不执行。
- 构建工具(Webpack/Vite)会自动处理
import('./locales/en.js')并生成独立 chunk,但需确保输出目录结构与导入路径一致 - 必须用
try/catch包裹import(),不能只靠.catch()链式调用 ——import()本身抛同步错误 - 模块内导出必须明确:
export const messages = { ... },避免默认导出导致命名冲突 - 服务端渲染(SSR)场景下,
import()无法在 Node 环境执行,需配合typeof window !== 'undefined'守卫
本地开发时 fetch('/locales/en.json') 报 CORS 不是 bug
双击打开 index.html 时,浏览器以 file:// 协议运行,所有 fetch 请求都会被拦截,报 No 'Access-Control-Allow-Origin' header —— 这不是代码问题,是协议限制。
唯一合规解法是起本地服务,而非绕过安全策略:
-
npx http-server -p 8080(轻量,无需安装) -
python3 -m http.server 8080(Python 自带) - Vite / Webpack Dev Server 已内置,直接
npm run dev - 千万别用
--disable-web-security启动 Chrome —— 这会让后续真实环境调试失效,且掩盖真实问题
真正容易被忽略的是:CORS 错误只发生在浏览器端,Node 服务代理或后端直连不受影响;一旦上线,必须确保 API 响应头含 Access-Control-Allow-Origin,否则生产环境照样挂。



















