2. 浏览器兼容测试:从“一个页面,多个问题”说起
说实话,搞前端和测试的朋友,谁没被浏览器兼容坑过呢?
我个人的经验是,别看现在Chrome一家独大,真到企业级项目里,用户手里的浏览器五花八门。你永远不知道哪家企业还在用IE11,或者某个领导还在用旧版Edge。
说白了,浏览器兼容测试,就是确保你的页面在不同浏览器的不同版本里,看起来差不多、用起来也一样。别让用户因为“浏览器不对”就骂产品烂。
2.1 主流浏览器与内核
你想想看,市面上的浏览器虽然多,但内核就那么几个。内核决定了浏览器怎么解析HTML、怎么画CSS、怎么执行JS。我私下总结过一句话:浏览器可以换,内核才是灵魂。
| 浏览器 | 内核 | JS 引擎 | 备注 |
|---|---|---|---|
| Chrome | Blink (WebKit 分支) | V8 | 目前市场份额最大 |
| Firefox | Gecko | SpiderMonkey | 标准支持积极 |
| Safari | WebKit | JavaScriptCore | macOS/iOS 默认浏览器 |
| Edge (新) | Blink | V8 | 基于 Chromium |
| Edge (旧) | EdgeHTML | Chakra | 已基本淘汰 |
| IE 11 | Trident | Chakra | 微软已停止支持,但个别政企仍在使用 |
关键认识: 内核差异是兼容问题的底层原因。同一个CSS属性,在WebKit里用 -webkit- 前缀,在Gecko里用 -moz-。这些差异不是凭空产生的,是各个浏览器厂商在标准定稿前的“抢先实现”。
2.2 浏览器内核之间的差异
内核差异主要体现在三个层面:渲染机制、CSS 支持度、JS 引擎行为。
举几个我遇到过的实际例子:
- 盒模型计算:IE曾经的盒模型(怪异模式)把padding和border算进width里。虽然项目现在很少触发怪异模式了,但旧版文档声明还是可能出事。
- CSS Grid 布局:Chrome 和 Firefox 早早就完整支持了,但Safari直到较晚版本才完全跟上。我在做一个后台系统时,因为用了
repeat(auto-fill, ...),在Safari上直接崩溃。 - 字体渲染:同一款字体,在Windows上的Chrome和macOS上的Safari里,高度和间距都有细微差别。这个真没法统一,我一般会用
line-height配合百分比去微调。
小心这个坑: 我曾经因为忽略了Mac下的Safari对 -webkit-font-smoothing 的渲染差异,导致线上页面在部分Mac用户那里文字糊成一片。后来养成了习惯:字体渲染属性一律按需加,不能偷懒不写。
2.3 CSS 兼容性问题与处理
CSS 兼容性,说白了就是你写的样式能不能在所有浏览器里长得差不多。我个人习惯把CSS兼容问题分成三类:属性支持、前缀问题、渲染差异。
2.3.1 常见CSS属性兼容性速查
| CSS 属性 / 特性 | Chrome | Firefox | Safari | IE 11 |
|---|---|---|---|---|
| Flexbox | ✓ | ✓ | ✓ | ⚠ 部分 |
| CSS Grid | ✓ | ✓ | ✓ (10.3+) | ✗ |
position: sticky |
✓ | ✓ | ✓ | ✗ |
calc() |
✓ | ✓ | ✓ | ⚠ 部分 |
custom properties (var) |
✓ | ✓ | ✓ | ✗ |
2.3.2 厂商前缀处理
处理前缀,我建议用自动化工具,比如 Autoprefixer。手动加太容易漏了。
/* 手动写法(不建议) */
.element {
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
display: -webkit-flex;
display: flex;
}
/* 配合 Autoprefixer 的写法(推荐) */
/* 你只需写标准属性,构建工具会自动补充 */
.element {
border-radius: 8px;
display: flex;
}
我的经验: 使用 PostCSS + Autoprefixer 配置 browserslist 来定义目标环境。比如设为 "last 2 versions, not dead, > 0.2%",工具会帮你处理好一切。我只管写标准CSS就行。
2.3.3 回退策略
有些属性旧浏览器压根不认识。这时候“渐进增强”或者“优雅降级”就很重要了。我常用的办法是提供fallback值。
/* 渐进增强示例 */
.box {
/* 旧浏览器会使用这个值 */
display: block;
/* 新浏览器会覆盖 */
display: flex;
flex-wrap: wrap;
}
/* 针对Grid的降级 */
.layout {
display: block; /* 降级方案 */
display: grid; /* 正常方案 */
gap: 20px;
}
2.4 JavaScript 兼容性问题与处理
JS 的兼容性,痛点在ES6+新语法和DOM API。早期的IE连 Array.forEach 都没有,更别提 Promise 和 fetch 了。
2.4.1 常见的JS引擎差异点
| 特性 | Chrome V8 | Firefox SpiderMonkey | Safari JSC | IE11 Chakra |
|---|---|---|---|---|
let / const |
✓ | ✓ | ✓ | ⚠ 有限支持 |
箭头函数 |
✓ | ✓ | ✓ | ✗ |
Promise |
✓ | ✓ | ✓ | ✗ |
fetch |
✓ | ✓ | ✓ | ✗ |
Object.assign |
✓ | ✓ | ✓ | ✗ |
2.4.2 兼容性处理方案
核心策略就两条:转译 + Polyfill。
- 转译(Transpile):Babel 把 ES6+ 语法转成 ES5。比如箭头函数转成普通函数,
const转成var。 - Polyfill(垫片):对于 Promise、fetch 这些API,用
core-js或者@babel/polyfill注入实现。这样旧浏览器也能跑。
注意性能: 我曾经在一个项目里全量打了 Polyfill,结果包体积直接翻倍。后来改成了根据浏览器特性动态加载,或者只针对 browserslist 里的旧版环境引入。另外,IE 11 不支持 fetch,我一般直接 npm install whatwg-fetch 并引入。
// Babel 转译前
const add = (x, y) => x + y;
new Promise(resolve => resolve(1)).then(console.log);
// Babel 转译后(大致效果)
var add = function(x, y) { return x + y; };
// Promise 需要额外的 Polyfill 才能存在
new Promise(function(resolve) { resolve(1); }).then(console.log);
2.4.3 事件兼容处理
事件方面的差异,主要是 addEventListener 和 attachEvent 的区别。虽然现在IE快绝迹了,但以防万一。
// 事件绑定的兼容写法
function addEvent(element, event, handler) {
if (element.addEventListener) {
element.addEventListener(event, handler, false);
} else if (element.attachEvent) {
// IE 8 及以下
element.attachEvent('on' + event, handler);
} else {
// 降级
element['on' + event] = handler;
}
}
小结一下: 浏览器兼容测试不是玄学,是有规律可循的。抓住内核差异、CSS前缀历史、JS引擎支持度这几条线,再去针对你的用户群体(看统计数据)做有针对性的测试。别想着覆盖所有浏览器,那是不可能的。用工具(Autoprefixer、Babel、Polyfill.io)帮你搞定大部分问题,剩下的靠真机测。