一、兼容性的三个层次
1 2 3 4 5 6 7 8 9 10 11 12
| 面试时常说的"兼容经验"涵盖三个层面:
跨浏览器兼容(Cross-browser) Chrome / Firefox / Safari / Edge 对 CSS/JS/API 的支持差异
跨平台兼容(Cross-platform) iOS(Safari WebView)vs Android(Chrome WebView)的行为差异 桌面端 vs 移动端的交互差异
跨端兼容(Cross-end) H5 / 微信小程序 / React Native / Electron 相同业务逻辑在不同端上的代码复用与差异处理
|
二、跨浏览器兼容
2.1 浏览器内核差异
| 浏览器 | 内核 | 占比(中国) | 特征 |
|---|
| Chrome | Blink | ~65% | 标准支持最快,自动更新 |
| Safari | WebKit | ~25% | 封闭生态,iOS 强制使用,落后标准 1-2 个版本 |
| Firefox | Gecko | ~5% | 强隐私,DevTools 功能丰富 |
| Edge | Blink | ~5% | 与 Chrome 一致,但有少量差异 |
| 微信内置浏览器 | X5(Blink 变体) | 大量 | 基于 Chromium 但版本滞后,部分功能缺失 |
面试应答策略:
1 2 3 4 5
| 兼容工作不是"所有浏览器 100% 效果一致"——这不现实。 而是: 1. 目标用户的主流浏览器覆盖(看数据,不看列表) 2. 核心功能可用(渐进增强,不影响使用) 3. 体验降级可接受(样式差异 ≠ 功能不可用)
|
2.2 CSS 兼容
自动补全(Autoprefixer)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .card { display: flex; backdrop-filter: blur(10px); }
.card { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); }
|
Vite 内置 Autoprefixer,Webpack 通过 postcss-loader + autoprefixer 引入。
常见 CSS 兼容场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| ┌─ flexbox 兼容 │ 旧版 Safari(iOS < 9)需 -webkit-box │ 绝大多数项目已不兼容 iOS 9 以下
├─ backdrop-filter │ Chrome 可用,Safari 需 -webkit- 前缀 │ 不支持时降级为半透明背景色
├─ sticky 定位 │ 父容器 overflow: hidden 时会失效 │ iOS 11+ 稳定,可通过 position: -webkit-sticky 兼容
├─ aspect-ratio │ 旧浏览器降级为 padding-bottom hack
├─ gap(flex 中使用) │ Safari 15.4+ 才支持 flex gap │ 降级为 margin + :not(:last-child)
├─ font-size 最小值 │ Chrome 桌面端最小 12px │ Safari 可小至 6px(需注意)
└─ scroll-behavior: smooth Safari 不支持 降级为 JS scrollIntoView
|
标准属性 vs 降级写法
1 2 3 4 5 6
| .card { position: -webkit-sticky; position: sticky; top: 0; }
|
2.3 JavaScript 兼容
语言特性兼容
1 2 3 4 5 6
| ┌─ ES6+ 语法 → Babel / esbuild 编译为 ES5 ├─ Optional Chaining(?.)→ Babel @babel/plugin-optional-chaining ├─ Nullish Coalescing(??)→ 同上 ├─ 箭头函数 → 自动转译为普通函数 ├─ Promise → @babel/polyfill 引入 core-js └─ Async/await → Babel runtime 支持
|
API 兼容
常用 API 的支持情况(2026 年参考):
| API | Chrome | Safari | Firefox | 降级方案 |
|---|
| IntersectionObserver | ✅ 原生 | ✅ | ✅ | 回退为 scroll 事件计算 |
| ResizeObserver | ✅ | ✅ | ✅ | 回退为 window.resize |
| WebSocket | ✅ | ✅ | ✅ | 回退为轮询(极少需要) |
| CSS Typed OM | ✅ | ❌ | ❌ | 使用 element.style |
| WebGPU | ✅ 实验性 | ✅ | ❌ | 回退 WebGL |
| SharedArrayBuffer | ✅(跨域隔离) | ✅ | ✅ | 不可降级,需加响应头 |
| import maps | ✅ | ✅ 16.4+ | ✅ | 用打包工具替代 |
特性检测(Feature Detection)
1 2 3 4 5 6 7 8 9 10
| if ('IntersectionObserver' in window) { new IntersectionObserver(callback).observe(el); } else { window.addEventListener('scroll', fallbackHandler); }
|
2.4 跨浏览器测试策略
1 2 3 4 5 6 7 8 9 10 11 12
| ├── 开发期 │ ├── 用 Chrome 开发,Safari 定期验证 │ ├── 使用 BrowserStack / 真实设备做兼容测试 │ └── CSS:Autoprefixer + 特性查询 @supports │ ├── 自动化 │ ├── Playwright / Cypress 跑多浏览器 E2E │ └── CI 中并行运行 Chrome + Firefox + WebKit │ └── 线上 ├── 错误监控(Sentry)按浏览器聚合 └── RUM 数据(Core Web Vitals)按浏览器对比
|
三、跨平台兼容
3.1 iOS vs Android 的 WebView 差异
| 差异点 | iOS(Safari WebView) | Android(Chrome WebView) |
|---|
| 内核 | WebKit(所有 iOS 浏览器强制使用) | Blink / Chromium |
| 版本更新 | 随 iOS 系统更新,用户升级慢 | Play Store 独立更新,版本较新 |
| 内存限制 | 严格(网页内存超限被杀掉) | 相对宽松 |
| 键盘弹起 | visualViewport 变化,window.innerHeight 缩小 | 部分机型 resize 事件不触发 |
| 底部安全区域 | 有(刘海屏指示条) | 有(各厂商不同) |
| Cookie 持久化 | 严格(ITP 智能防追踪) | 正常 |
| 日期输入 | 无原生 <input type="date">(需自行实现) | 原生支持 |
| 字体渲染 | 较细,-webkit-font-smoothing: antialiased | 默认正常 |
3.2 iOS 常见兼容问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| .container { -webkit-overflow-scrolling: touch; overflow-y: scroll; }
input, button { -webkit-appearance: none; border-radius: 0; }
.full-height { height: -webkit-fill-available; height: 100dvh; }
* { -webkit-tap-highlight-color: transparent; }
.card { border-radius: 12px; overflow: hidden; transform: translateZ(0); }
|
3.3 Android 常见兼容问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| .border-1px::after { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #ddd; transform: scaleY(0.5); }
html { -webkit-text-size-adjust: 100%; }
input { font-size: 16px; }
|
3.4 移动端 vs 桌面端交互差异
| 维度 | 移动端 | 桌面端 |
|---|
| 输入 | 触摸(点击/滑动/长按) | 鼠标 + 键盘 |
| 悬停 | 无 hover | hover 可用 |
| 屏幕尺寸 | 小(320-430px) | 大(1024px+) |
| 网络 | 移动网络(弱网/离线) | Wi-Fi/有线(稳定) |
| 性能 | 低功耗 CPU,内存有限 | 高性能 |
| 存储 | IndexedDB/localStorage | 同上但容量更大 |
1 2 3 4 5 6 7 8 9 10
| @media (hover: hover) and (pointer: fine) { .card:hover { transform: translateY(-4px); } }
@media (hover: none) and (pointer: coarse) { .card:active { transform: scale(0.98); } }
|
四、跨端兼容
4.1 多端架构模式
| 模式 | 代码复用率 | 性能 | 团队成本 | 代表 |
|---|
| Web 优先 + 小程序 | 60-70% | 一般 | 低 | 同一套 Vue/React 代码,小程序部分重写 |
| React Native / Weex | 80-90% | 较好 | 中 | 同一 JS 逻辑,原生渲染 |
| Flutter | 90%+ | 最好 | 高(需学 Dart) | 自渲染引擎,完全不依赖系统 WebView |
| 小程序 + H5 同构 | 70-80% | 一般 | 中 | Taro / uni-app 编译到多端 |
| 响应式 Web | 100%(仅 H5) | 依赖浏览器 | 低 | 一套代码适配不同屏幕 |
4.2 Taro / uni-app 的兼容策略
1 2 3 4 5 6 7 8 9 10 11 12
| function Index() { return ( <View className="index"> <Text>Hello</Text> </View> ); }
|
跨端框架的兼容处理方式:
1 2 3 4 5
| ├── 组件映射:View → div / view / View(取决于目标端) ├── API 适配:Taro.request → wx.request / axios ├── 样式处理:CSS → WXSS / RN StyleSheet / 内联样式 ├── 编译时处理:条件编译、按端裁剪 └── 运行时适配:抹平各端 API 差异
|
4.3 条件编译(uni-app)
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> // #ifdef MP-WEIXIN console.log('仅微信小程序执行'); // #endif
// #ifdef H5 console.log('仅 H5 执行'); // #endif
// #ifndef MP-WEIXIN console.log('非微信小程序执行'); // #endif </script>
|
4.4 同一产品的 Web vs 小程序差异点
| 功能 | Web 实现 | 小程序实现 |
|---|
| 页面路由 | React Router / Vue Router | 文件系统路由 |
| 网络请求 | fetch / axios | wx.request |
| 本地存储 | localStorage | wx.setStorageSync |
| 登录 | 账号密码/OAuth | wx.login + code2session |
| 支付 | 微信 H5 支付/支付宝 | wx.requestPayment |
| 分享 | 外部链接 | wx.shareAppMessage |
| 更新 | 刷新即最新 | wx.getUpdateManager |
五、实践策略
5.1 渐进增强 vs 优雅降级
1 2 3 4 5 6 7 8 9
| 渐进增强(Progressive Enhancement): 从最低功能版本开始,逐步增加增强特性。 所有用户都可使用,高级浏览器获得更好的体验。
优雅降级(Graceful Degradation): 先构建完整功能,再为旧浏览器做降级。
推荐:渐进增强。 原因:移动端优先 + 低端设备优先,符合用户分布。
|
5.2 兼容性决策树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 遇到兼容问题: │ ├── 是 CSS 问题? │ ├── 是否有 Autoprefixer 前缀 → 检查 postcss 配置 │ ├── 是否有标准替代属性 → 用 @supports 做降级 │ └── 样式差异是否影响功能 → 不影响则可接受 │ ├── 是 JS API 问题? │ ├── 是否有 Polyfill → 引用对应 polyfill │ ├── 是否有替代 API → 用特性检测 + 降级 │ └── 是否存在 Edge case → 用 try-catch 兜底 │ ├── 是 UI/交互差异? │ ├── 不同端组件行为不一致 → 条件编译/按端适配 │ ├── 安全区域/刘海屏 → env(safe-area-inset-*) + 适配 │ └── 字体/渲染差异 → reset.css + 系统字体优化 │ └── 是性能差异? ├── 低端设备 → 减少动画/降低帧率 ├── 弱网 → 离线缓存 + 骨架屏 └── 内存限制 → 分页加载 + 及时释放
|
5.3 常用的 Polyfill 与降级库
1 2 3 4 5 6 7 8 9 10
| │ 特性 │ Polyfill / 降级方案 │ │────────────────────────┼────────────────────────────│ │ IntersectionObserver │ 谷歌官方 polyfill │ │ ResizeObserver │ polyfill.io 按需加载 │ │ WebP │ <picture> 标签降级 │ │ CSS Nesting │ PostCSS 预处理 │ │ ES6+ 语法 │ Babel + core-js │ │ CSS Container Queries │ 用媒体查询 + JS 计算替代 │ │ backdrop-filter │ 降级为半透明背景色 │ │ WebSocket │ 降级为 HTTP 轮询 │
|
5.4 面试回答框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 当被问到"说一下你的兼容经验"时,按以下结构组织:
1. 明确范围 "我主要处理过三类兼容: 跨浏览器(Chrome/Safari/Firefox 的 CSS/API 差异)、 跨平台(iOS 与 Android 的 WebView 行为差异)、 跨端(同一产品同时有 H5 和微信小程序)。"
2. 具体案例(挑一个最典型的) "例如在 XX 项目中,我们遇到了 iOS Safari 的 100vh 问题—— 页面底部被地址栏遮挡。解决方案是使用 100dvh 代替 100vh, 不支持的浏览器回退为 -webkit-fill-available。"
3. 方法论 "我们建立了完善的兼容策略: Autoprefixer 处理 CSS 前缀, 特性检测 + 降级处理 JS API, BrowserStack 做多浏览器测试, 线上 Sentry 按浏览器监控错误率。"
4. 原则 "我的原则是:不追求所有浏览器 100% 一致, 而是保证核心功能可用 + 体验可接受 + 对目标用户覆盖达标。"
|
六、面试题
Q1: 如何处理 Safari 的 100vh 问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
.full-height { height: 100dvh; }
.full-height { height: -webkit-fill-available; height: 100dvh; }
function setVh() { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } window.addEventListener('resize', setVh); setVh();
|
Q2: 如何检测浏览器是否支持某个特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const supports = CSS.supports('backdrop-filter', 'blur(10px)'); CSS.supports('display', 'grid');
'IntersectionObserver' in window; 'ResizeObserver' in window; 'navigator.mediaDevices' in navigator;
@supports (backdrop-filter: blur(10px)) { .modal { backdrop-filter: blur(10px); } } @supports not (backdrop-filter: blur(10px)) { .modal { background: rgba(0, 0, 0, 0.5); } }
|
Q3: 移动端 1px 边框怎么处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| .hairline::after { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #ddd; transform: scaleY(0.5); transform-origin: 0 100%; }
@media (-webkit-min-device-pixel-ratio: 3) { .hairline::after { transform: scaleY(0.33); } }
|
Q4: 一套代码如何同时跑在 H5 和小程序上
1 2 3 4 5 6
| 主流方案: ├── Taro / uni-app / Remax ├── 编译时:将 React/Vue 代码编译为目标端代码 ├── 运行时:提供统一的 API 适配层 ├── 条件编译:各端差异化代码通过预编译指令隔离 └── 组件库:NutUI / NutUI 等支持多端的 UI 库
|
Q5: 什么是 Polyfill,什么情况下需要
1 2 3 4 5 6 7 8 9 10 11 12 13
| Polyfill 是用 JS 模拟浏览器未实现的 API,让旧环境也能使用新特性。
需要的情况: ├── 目标用户使用旧浏览器(如国内大量微信 WebView) ├── 使用了较新的 ES API(Promise、Array.from) ├── 使用了新的 CSS 特性需要检测兼容
不需要的情况: ├── 仅在 Chrome 中使用的内部工具 ├── Polyfill 体积过大、影响性能 └── 可以通过构建工具编译替代(如 Babel)
推荐:用 polyfill.io 按需加载,只加载当前浏览器缺少的。
|
Q6: 如何在项目中系统性地做兼容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 系统性兼容方案:
1. 确定目标环境 ├── 从产品/运营获取用户浏览器分布数据 ├── 确定兼容目标(如:Chrome 90+、Safari 15+、微信 8+) └── 明确不支持的浏览器范围
2. 工具链配置 ├── browserslist → 统一声明目标环境 ├── Autoprefixer → 自动添加 CSS 前缀 ├── Babel + core-js → 编译 JS 语法 + polyfill └── Browserslist 与 Webpack/Vite/Babel 打通
3. 测试 ├── 开发期:Chrome 为主,定期切 Safari 看效果 ├── CI:Playwright 跑 Chrome + Firefox + WebKit └── 线上:监控各浏览器错误率 + 性能指标
4. 文档 ├── 记录已知兼容问题和解决方案 ├── 新功能开发时标注兼容性 └── Review 时检查兼容处理
|