js延迟加载的方式有哪些:实操方案对比
js延迟加载的方式有哪些,主流分为HTML原生属性延迟、动态创建script标签、setTimeout延时执行、requestIdleCallback空闲加载、IntersectionObserver视口监听这五类,原生defer和async上手成本最低,适合页面通用脚本,动态script适合按需触发的组件脚本,IntersectionObserver适合滚动到指定区域才加载的埋点、弹窗脚本,setTimeout只适合非关键、可容忍较长加载时差的辅助脚本,requestIdleCallback适合优先级低、不阻塞交互的统计脚本,defer会保持脚本顺序执行、DOM解析完成后运行,async加载完成就立刻执行,不保证顺序,这两类都不能操作还没解析完成的DOM,如果你需要在DOM渲染完毕后按固定顺序执行脚本,优先选defer,动态脚本和视口监听方案可以自主控制加载时机,但需要手写少量JS逻辑,不适合完全不写脚本的静态页面开发者。
js延迟加载原生属性方案
你直接在script标签上添加defer属性,浏览器会并行下载JS文件,不阻塞HTML解析,等到整个文档解析完成、DOMContentLoaded事件触发前,按标签书写顺序执行脚本。这个方式不需要额外写JS代码,兼容性覆盖所有现代浏览器,旧版IE9以下不支持,你可以把依赖DOM结构、且有先后依赖关系的公共脚本加上defer,比如页面基础工具类。
async属性同样让浏览器后台并行下载JS,区别是脚本下载完成后会中断HTML解析,立刻执行,执行顺序无法和标签顺序保持一致。独立、无依赖关系的第三方统计脚本适合async,多个脚本存在调用依赖时不要使用async,会出现变量未定义的报错。
js延迟加载动态创建脚本方案
你通过JS代码新建scriptDOM节点,设置src地址,挂载到文档中,脚本才会发起网络请求。加载时机完全由你代码控制,可以绑定按钮点击、下拉菜单展开等用户交互事件,实现真正按需加载,减少首屏请求数量。创建节点时设置script.async=true,脚本加载后自动执行,如果你需要等待加载完成后执行回调,可以监听onload事件捕获加载成功状态。
| 方案 | 加载时机 | 执行顺序 | 代码成本 |
|---|---|---|---|
| defer | 后台下载,DOM解析完执行 | 保持标签顺序 | 很低 |
| async | 后台下载,下载完成即执行 | 无序 | 很低 |
| 动态script | 代码触发时发起请求 | 可自主控制 | 中等 |
| IntersectionObserver | 元素进入视口时加载 | 可自主控制 | 中等 |
IntersectionObserver用来监听DOM元素是否进入浏览器可视区域,当目标元素出现时,再创建script标签加载JS。长页面底部模块、滚动展示的广告组件脚本适配这个方案,相比监听scroll事件,它不会高频触发回调,对主线程性能消耗更低,你只需要配置阈值参数,控制元素露出多少比例后触发加载。
setTimeout是把脚本加载逻辑放进定时器回调,等待指定毫秒数之后再执行。它的延迟时间不可靠,浏览器主线程繁忙时会延后很久,只适合和页面渲染、用户交互逻辑无关的次要脚本,比如非紧急的日志上报,不要用它承载核心业务脚本。
requestIdleCallback在浏览器主线程空闲时段运行加载逻辑,浏览器有富余时间才会执行回调,不会抢占渲染资源。它自带超时参数,防止脚本一直得不到执行机会,适合优先级最低的后台统计、离线缓存类脚本,对交互响应要求高的功能不适合使用。
DOMContentLoaded事件内加载脚本,会等页面DOM树全部构建完成之后再发起JS请求,此时你可以安全读取页面上所有DOM节点,不会出现获取元素为null的情况。图片、样式等外部资源还可能没有加载完毕,如果你脚本依赖图片尺寸等资源信息,不能只依赖这个事件。
DOM还没构建完成时执行延迟脚本,会获取不到DOM节点。
