2. 浏览器兼容测试:从“一个页面,多个问题”说起

说实话,搞前端和测试的朋友,谁没被浏览器兼容坑过呢?

我个人的经验是,别看现在Chrome一家独大,真到企业级项目里,用户手里的浏览器五花八门。你永远不知道哪家企业还在用IE11,或者某个领导还在用旧版Edge。

说白了,浏览器兼容测试,就是确保你的页面在不同浏览器的不同版本里,看起来差不多、用起来也一样。别让用户因为“浏览器不对”就骂产品烂。

浏览器兼容性测试核心知识框架 兼容性问题的根源 浏览器内核差异 CSS 兼容性问题 JS 兼容性问题 WebKit (Chrome) Gecko (Firefox) Blink (新版Edge) Trident (IE) Flexbox / Grid 差异 厂商前缀 (Webkit / Moz) rem / vw / vh 支持 ES6+ 语法支持 DOM/BOM API 缺失 事件模型差异

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 引擎行为。

举几个我遇到过的实际例子:

  1. 盒模型计算:IE曾经的盒模型(怪异模式)把padding和border算进width里。虽然项目现在很少触发怪异模式了,但旧版文档声明还是可能出事。
  2. CSS Grid 布局:Chrome 和 Firefox 早早就完整支持了,但Safari直到较晚版本才完全跟上。我在做一个后台系统时,因为用了 repeat(auto-fill, ...),在Safari上直接崩溃。
  3. 字体渲染:同一款字体,在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。

  1. 转译(Transpile):Babel 把 ES6+ 语法转成 ES5。比如箭头函数转成普通函数,const 转成 var。
  2. 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)帮你搞定大部分问题,剩下的靠真机测。