逻辑降级是主动放弃不支持功能而用更简单可靠方式达成目标,在esm不支持环境(如ie11)中,通过动态script加载+显式依赖管理替代原生import/export,结合ua与特性检测分流,提供轻量loader实现模块化语义。

逻辑降级不是强行兼容,而是主动放弃不支持的功能,用更简单、更可靠的方式达成相同目标。在 ESM(type="module")不可用的老旧浏览器(如 IE11、Android 4.4 WebView、早期 Safari)中,不能靠转译或 polyfill 完全复现模块语义,但可以通过“逻辑降级”策略实现按需加载、依赖隔离和执行可控——本质是用异步脚本加载 + 显式依赖管理,替代原生 import/export。
识别并分流不支持 ESM 的环境
ESM 不被支持时,<script type="module"></script> 标签会被浏览器直接忽略,且不会触发任何错误。因此不能只靠标签存在与否判断,而应主动检测:
- 检查
HTMLScriptElement.prototype.type是否存在且可赋值为"module" - 更稳妥方式:尝试创建一个带
type="module"的 script 并检查其supports属性(现代写法),或直接用 UA + 特性检测组合判断(如!('noModule' in HTMLScriptElement.prototype)) - 一旦确认不支持,就跳过所有
type="module"加载,转向降级路径(如 AMD 风格动态加载或自定义 loader)
用动态 script + 手动依赖链模拟模块行为
核心思路:把每个“模块”视为一个独立 JS 文件,由主逻辑按需创建 script 标签加载,并在 onload 中显式调用回调,形成人工依赖链。
- 不依赖全局变量污染,每个脚本加载后通过闭包或命名空间暴露接口(如
window.myLib = { init(), utils }) - 加载顺序靠嵌套回调或 Promise 链控制:先加载基础工具库,再加载业务模块
- 示例逻辑:
loadScript('utils.js').then(() => loadScript('chart.js')).then(() => initChart()) - 必须监听
onerror并 reject Promise,否则失败静默,调试困难
提供轻量 fallback loader 替代 require.js
不需要引入完整 AMD 加载器。一个 20 行左右的简易 loader 就能支撑常见场景:
- 维护一个
loadedModules缓存对象,避免重复加载同一 URL - 支持传入依赖数组,自动串行加载(类似
require(['a','b'], cb)) - 每个模块脚本末尾调用
define('moduleName', factory)注册自身,loader 在 onload 后执行该函数并缓存返回值 - 业务代码统一用
loader.use(['utils', 'api'], (u, a) => {...})获取实例,与 ESM 的import { x } from './x'在使用层保持语义接近
资源与执行时机双重降级
逻辑降级不仅体现在语法上,也体现在加载策略上:
- ESM 天然 defer + 顺序执行 + CORS 安全,降级方案需手动补足:对关键脚本设
script.async = false模拟 defer 行为(插入到 head 后、DOM 解析完成前执行) - 避免在
DOMContentLoaded前执行 DOM 操作,所有模块初始化推迟到事件后,或封装成ready()工具函数 - 静态资源(如配置、翻译包)改用 JSONP 或 XHR +
eval(仅限可信源)加载,绕过 script 标签的同源限制 - 开发时用构建工具将 ESM 源码一键生成两套产物:一套保留
type="module",一套转为 IIFE + 全局命名空间 + loader 调用,部署时根据 UA 自动选包











