在线编辑器脚本误用async会导致document.getElementById('editor')为null而白屏;必须用defer保证DOM就绪和执行顺序,且仅对外部脚本生效,内联初始化代码需在DOMContentLoaded后调用。

async 和 defer 在在线编辑器脚本中的误用现象
在线编辑器(如 Monaco、CodeMirror、ACE)的初始化脚本常被错误地加 async,结果页面刚渲染出编辑器容器,脚本就执行了——但 document.getElementById('editor') 还是 null,控制台报错,编辑器白屏。这不是脚本没加载完,而是它执行得太早。
更隐蔽的问题是:编辑器依赖多个 JS 文件(核心库 + 语言服务 + 主题配置),若混用 async 和普通 script,极易出现 MonacoEnvironment is not defined 或 require is not defined。这些错误在本地双击 HTML 打开时尤其高频,因为 file:// 协议下跨域策略更严,crossorigin 缺失直接导致堆栈丢失。
-
async脚本适合独立埋点或性能监控 SDK,比如analytics.js;但它绝对不适合编辑器主逻辑 -
defer是编辑器外链脚本的默认选择:它保证 DOM 就绪、所有defer脚本按序执行,且不阻塞解析 - 内联初始化代码(如
monaco.editor.create(...))不能加defer—— 它只对外部src脚本生效
defer 加载编辑器主库时必须配齐的三个条件
光写 defer 不够。现代编辑器(尤其是 Monaco)依赖模块化加载和环境变量注入,漏掉任一条件都会卡在「加载完成但不启动」状态。
- 必须将编辑器初始化逻辑封装进函数,并在
DOMContentLoaded后调用,或直接放在最后一个defer脚本末尾(确保前面的monaco.js和vs/loader.js已载入) - 所有跨域资源(CDN 上的
monaco-editor、vs/language)必须带crossorigin="anonymous",否则 Chrome DevTools 里看不到错误详情,调试靠猜 - 如果用了
monaco-editor的 ESM 版本(type="module"),则无需手动defer—— 浏览器自动按defer行为处理,但要注意:ESM 不支持document.write,且import()动态导入需显式处理加载失败
动态加载语言服务与主题的时机控制
编辑器主体加载完后,语言服务(如 TypeScript Worker)、语法高亮主题、快捷键映射等属于「非首屏必需」资源。它们不该随主库一起加载,否则拖慢 TTI(Time to Interactive)。
立即学习“前端免费学习笔记(深入)”;
正确做法是监听编辑器实例创建成功后,再按需加载:
- 用
monaco.editor.create()返回的实例对象判断是否 ready,再调用monaco.languages.typescript.javascriptDefaults.setCompilerOptions(...) - 语言 Worker 必须通过
monaco.editor.worker.register(...)注册,不能直接import;否则会触发 CORS 错误或加载失败静默 - 主题 CSS 应通过
monaco.editor.defineTheme注入,而非<link rel="stylesheet">—— 后者会阻塞渲染,且无法被编辑器 runtime 动态切换
验证是否真不阻塞:三个必查信号
写了 defer 不代表优化生效。浏览器可能因路径错误、404 或 MIME 类型不对,悄悄回退成同步加载。
- 打开 Chrome DevTools → Network 面板,筛选 JS 请求,看其
Initiator列:若显示parser,说明仍被同步解析阻塞;应为other或空白 - 在编辑器初始化脚本开头加
console.timeStamp('monaco-init-start'),对比performance.getEntriesByName('domContentLoadedEventEnd')[0].startTime,确认执行时间落在 DOM 解析之后 - 用 Lighthouse 跑 Performance 报告,重点看
Render Blocking Resources是否还列出你的编辑器 JS —— 如果还在,说明defer没起作用,大概率是脚本 URL 404 或服务器返回了错误的Content-Type
真正难的是边界情况:比如用户快速刷新页面时,defer 脚本还没下载完,但编辑器容器 DOM 已存在,此时初始化逻辑必须能重试或降级。这类容错不在 HTML 属性层面,得靠 JS 层的加载状态管理来兜底。



















