必须用defer:脚本要操作DOM或依赖前一个脚本导出的变量;只能用async:脚本完全自包含、不读写document、不依赖其他JS;defer仅对带src的外部脚本生效;混用async/defer会导致执行顺序不可控。

async/defer 用错就挂 DOM
脚本加载时机错配,document.getElementById 返回 null 是最常见症状,不是网络慢,是执行时 DOM 根本还没建好。
- 必须用
defer:脚本要操作 DOM(比如挂载 Vue/React 应用、绑定按钮事件)、或依赖前一个脚本导出的变量(如先加载lodash.js,再用_.debounce) - 只能用
async:脚本完全自包含,不读写document、不依赖其他 JS、失败也不影响主流程——典型如埋点analytics.js、错误监控error-tracker.js -
defer只对带src的外部脚本生效;<script defer>console.log(1)</script>中的defer被浏览器直接忽略 - 别混用:
<script src="a.js" defer></script>和<script src="b.js" async></script>同时存在,执行顺序彻底不可控,b.js可能在a.js还没开始下载时就执行完
动态 import() 比 script 标签更可控
当组件加载由用户行为触发(比如点击“展开详情”才加载富文本编辑器),硬塞 async 或 defer 不够用,得靠运行时控制。
- 用
import()动态导入 ES 模块,返回 Promise,可配合try/catch处理加载失败 - 避免手动创建
script标签:漏写onload或onerror会导致静默失败,调试困难 - 模块路径必须是字符串字面量或受控变量,不能拼接未验证的用户输入,否则触发 CSP 报错
- Webpack/Vite 等构建工具会自动代码分割,
import('./Editor.vue')生成独立 chunk,按需下载
渲染卡顿?先占位再替换
异步加载数据或组件后直接塞进 innerHTML,用户看到的是白屏→闪动→内容,体验断层。关键不是“快”,而是“有反馈”。
- 初始 HTML 放骨架占位符(灰色块),用
loading类控制显隐,CSS 控制高度/宽度,避免布局抖动 - fetch 成功后移除
loading类,再用document.createElement+textContent渲染纯文本;若需插 HTML 片段(如后台返回的富文本),必须过DOMPurify.sanitize(),别信正则过滤 - 批量插入节点时,用
DocumentFragment收集所有li元素,最后一次性appendChild,避免反复重排重绘 - 加超时控制:
AbortController设 8s 上限,超时显示「加载失败,请重试」按钮,别让用户干等
本地开发 CORS 不是 bug,是协议限制
双击打开 index.html,fetch('/api/data') 直接报 CORS 错误?这不是代码问题,是浏览器对 file:// 协议的强制拦截。
立即学习“前端免费学习笔记(深入)”;
- 唯一合规解法:起本地服务,比如
npx http-server或python3 -m http.server 8000,让地址变成http://localhost:8000 - 开发环境配代理(如 Vite 的
server.proxy),把/api/前缀转发到真实后端,避免跨域又不用改业务代码 - 绝对别用
--disable-web-security启动浏览器——这等于卸掉盔甲上战场,上线前还得全改回来,纯属给自己挖坑 - 注意
fetch()默认不带 credentials,跨域请求要 Cookie 必须显式加{ credentials: 'include' }
useEffect 里发请求,但没清理 AbortController,用户快速切换页面后回调仍执行,试图更新已卸载组件的 state —— 这类问题不会报错,但内存泄漏和 UI 错乱会慢慢堆积。



















