浏览器兼容性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

💡 我的经验:千万别无脑全量加载polyfill。我在一个移动端项目里看到有人直接引了 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支持。但如果你还在维护一些内部系统或银行老项目,可能还会碰到。

🌟 关键认知:条件注释不是特性检测,它是版本检测。它依赖IE专属的注释解析引擎。其他浏览器会把整段代码视为普通注释。

IE10及以上版本不再支持条件注释。如果你真的需要针对IE做兜底,建议用特性检测+polyfill代替。我曾经在一个政府项目中被迫使用条件注释来加载ES5 shim,代码像是活在十年前——但我没办法,客户强行要求支持IE8,最终我写了个自检脚本,如果检测到是IE且版本号<9,就动态加载polyfill,而不是用注释。这样更干净。

三者之间的关系与选择

方法 核心理念 适用场景 缺陷
特性检测 询问浏览器“你能做吗?” 所有现代项目,首选方案 需要写检测逻辑,可能漏检边缘情况
Polyfill “你不能,我补全” 需要支持旧浏览器但不想重写代码 增加体积,可能影响性能
条件注释 “你的版本是X,我特殊处理” 仅限IE 5-9,目前几乎淘汰 检测版本而非能力,容易误判

你看看这个表格就明白了:条件注释是“看户口本”,特性检测是“看本事”。polyfill则是给没有本事的人“加技能”。

一张图串起整个流程

下面的SVG图展示了兼容性SDK处理页面的核心流程:先检测、再判断、要么直接用原生,要么加载polyfill——如果跨越不了,就降级或提示。

兼容性SDK核心功能流程 兼容性SDK处理流程 页面加载 特性检测(Feature Detection) ✅ 使用原生API ⚠️ 加载Polyfill 执行页面代码 条件注释
(仅IE) 核心原则:能检测就不猜版本;能补丁就不降级;能用polyfill就不写两套代码。条件注释仅作为历史遗留手段。

实战中的搭配节奏

我建议你这样组织你的兼容性SDK:

  1. 先做特性检测表:列出项目用到的所有现代API,用 if (window.xxx) 判断
  2. 按需加载polyfill:用动态导入(如 import())加载缺失的补丁
  3. 保留一个降级开关:如果polyfill也不支持(比如IE8下缺了太多东西),用一个全局标志位关闭高级功能

我曾经帮一个同事排查bug:他的页面在Safari上白屏,因为用了 Array.flatMap,但只打了 Array.prototype.includes 的polyfill,漏了 flatMap。这就是“检测不全”的典型教训。

📌 要记住:特性检测和polyfill是搭档,不是二选一。检测到缺失→加载polyfill→再次确认→使用。这套逻辑可以写进一个通用的 ensureAPI 函数里。

至于条件注释,除非你的项目明确只支持IE 5-9而且无法降级,否则我强烈建议你彻底忘掉它。用特性检测+polyfill的方案,覆盖更广,代码更干净。