浏览器兼容性SDK的核心功能:特性检测、Polyfill与条件注释
这三样东西,说白了就是兼容性SDK的三大支柱。我当年刚接手一个跨平台老项目时,被各种浏览器差异折磨得够呛——后来才明白,用好它们能省下90%的重复排查时间。
咱们一个一个拆开看。记住一个原则:先检测,再修补,最后才考虑条件注释。这是我在多个项目中摔打出来的顺序。
1. 特性检测:不猜浏览器,直接问能力
特性检测,就是不依赖浏览器名称或版本号,而是直接运行一段代码,看看这个环境有没有某个API。
举个例子,你想用 fetch,但不确定用户浏览器支不支持。不要写:
// 错误做法:靠UA猜
if (navigator.userAgent.indexOf('Chrome') > -1) {
fetch(url);
}
为什么不行?因为用户可能用的Edge、国产浏览器、或者Chrome的某个旧版本。UA能伪造,能变化,根本不可靠。
正确做法是:
// 特性检测:直接问浏览器会不会
if (window.fetch) {
fetch(url);
} else {
// 降级方案
}
我个人习惯把特性检测封装成小工具函数:
function hasAPI(name) {
return typeof window[name] === 'function';
}
我在项目中遇到过一件事:某个功能在iOS Safari上总是报错,排查半天发现是检测了 navigator.mediaDevices,但忘了检测 getUserMedia 是否存在。特性检测一定要检验具体方法,不能只查根对象。
2. Polyfill:给老浏览器“补课”
Polyfill(补丁)就是用新的代码模拟缺失的API。它不是魔改,而是填补。你想想看,如果浏览器没有 Array.prototype.includes,你手动给原型加上一个实现——这就是polyfill。
常用的polyfill分两类:
- 全局补丁:比如
core-js直接修改内置对象 - 局部加载:按需引入,只补需要的API
babel-polyfill 全量包,结果首包体积大了120KB。后来改成按需 import 'core-js/stable/array/includes',体积降到8KB。写polyfill的时候有个坑:要考虑浏览器本身的实现是否标准。比如 Promise,有些旧版浏览器虽然实现了 Promise,但不符合Promise/A+规范。这时候你不仅要检测 window.Promise,还要跑一个简单的测试:
function needsPromisePolyfill() {
if (typeof Promise === 'undefined') return true;
// 快速测试:microtask 顺序
return !(new Promise(function(r) { r(); })).toString().includes('[object Promise]');
}
Array.prototype.push 会导致其他依赖原生push的库出问题。polyfill应该只添加缺失方法,不修改既有行为。3. 条件注释:回顾历史的“僵尸技术”
条件注释是IE时代的产物。写法像这样:
<!--[if IE 8]>
<link rel="stylesheet" href="#" aria-disabled="true" data-keke-missing-local-link="1" onclick="return false">
<![endif]-->
只有IE 8及以下版本会解析这段注释里的内容。说实话,我在最近3年已经很少用它了——毕竟微软自己都停止了IE 11支持。但如果你还在维护一些内部系统或银行老项目,可能还会碰到。
IE10及以上版本不再支持条件注释。如果你真的需要针对IE做兜底,建议用特性检测+polyfill代替。我曾经在一个政府项目中被迫使用条件注释来加载ES5 shim,代码像是活在十年前——但我没办法,客户强行要求支持IE8,最终我写了个自检脚本,如果检测到是IE且版本号<9,就动态加载polyfill,而不是用注释。这样更干净。
三者之间的关系与选择
| 方法 | 核心理念 | 适用场景 | 缺陷 |
|---|---|---|---|
| 特性检测 | 询问浏览器“你能做吗?” | 所有现代项目,首选方案 | 需要写检测逻辑,可能漏检边缘情况 |
| Polyfill | “你不能,我补全” | 需要支持旧浏览器但不想重写代码 | 增加体积,可能影响性能 |
| 条件注释 | “你的版本是X,我特殊处理” | 仅限IE 5-9,目前几乎淘汰 | 检测版本而非能力,容易误判 |
你看看这个表格就明白了:条件注释是“看户口本”,特性检测是“看本事”。polyfill则是给没有本事的人“加技能”。
一张图串起整个流程
下面的SVG图展示了兼容性SDK处理页面的核心流程:先检测、再判断、要么直接用原生,要么加载polyfill——如果跨越不了,就降级或提示。
实战中的搭配节奏
我建议你这样组织你的兼容性SDK:
- 先做特性检测表:列出项目用到的所有现代API,用
if (window.xxx)判断 - 按需加载polyfill:用动态导入(如
import())加载缺失的补丁 - 保留一个降级开关:如果polyfill也不支持(比如IE8下缺了太多东西),用一个全局标志位关闭高级功能
我曾经帮一个同事排查bug:他的页面在Safari上白屏,因为用了 Array.flatMap,但只打了 Array.prototype.includes 的polyfill,漏了 flatMap。这就是“检测不全”的典型教训。
ensureAPI 函数里。至于条件注释,除非你的项目明确只支持IE 5-9而且无法降级,否则我强烈建议你彻底忘掉它。用特性检测+polyfill的方案,覆盖更广,代码更干净。