第二节 常见浏览器兼容性问题类型
做前端的人,几乎每天都会跟兼容性打交道。我自己刚入行那会儿,总觉得「Chrome 能跑就等于一切」,直到被 IE 和 Safari 轮番教育……嗯,今天我们就来细数一下最常见的四类兼容性坑。
2.1 CSS 兼容性
CSS 的问题往往最直观——样式不对,一眼就能看出来。但背后的原因可就多了。
2.1.1 属性前缀与标准差异
你想想看,CSS3 刚出来那阵子,各厂商都抢着实现自己的前缀版本。`-webkit-`、`-moz-`、`-ms-`、`-o-`,光一个圆角就写了五行。现在虽然好多了,但有些属性依然需要前缀。
/* 典型前缀写法 */
.example {
-webkit-transform: rotate(10deg);
-moz-transform: rotate(10deg);
-ms-transform: rotate(10deg);
-o-transform: rotate(10deg);
transform: rotate(10deg);
}
我个人的做法:用 Autoprefixer 或 PostCSS 自动添加前缀,别手写。但如果你的项目不用构建工具,那就老老实实对照 Can I Use 写全。
2.1.2 Flexbox 与 Grid 兼容性
Flexbox 在大部分现代浏览器上已经稳定了,但老 IE(尤其是 IE10/11)的 Flexbox 实现满是 BUG。我记得有一次排查一个纵向居中问题,最后发现是 IE11 下 min-height 与 Flex 冲突。Grid 更不用说——IE 只支持旧版 -ms- 语法,而且功能少一大截。
| 布局方式 | 现代浏览器 | IE 10/11 | IE 9 及以下 |
|---|---|---|---|
| Flexbox | ✅ 完全支持 | ⚠️ 部分支持(需加 -ms- 前缀及 bug 处理) | ❌ 不支持 |
| Grid | ✅ 完全支持 | ⚠️ 仅支持旧版 -ms- 语法 | ❌ 不支持 |
| 多列布局 | ✅ 支持 | ⚠️ -ms- 前缀 | ❌ 不支持 |
曾经踩过的坑:我在一个后台管理项目中用了 Grid 做主布局,测试时 IE11 下的网格全乱了。后来改用 Flexbox + 降级方案才稳住。如果你的目标用户包含大量老旧浏览器,别盲目上 Grid。
2.1.3 盒子模型差异
老生常谈了——IE 的怪异模式下,width 包含 padding 和 border。虽然现在基本用 box-sizing: border-box 统一了,但有些第三方组件可能没设,导致尺寸对不上。
/* 全局统一盒子模型 */
*,
*::before,
*::after {
box-sizing: border-box;
}
2.2 JavaScript API 兼容性
JS 的兼容性更隐蔽。同一个 API 在 Chrome 上跑得好好的,到 Safari 或 IE 就报错。说白了,就是引擎实现不同步。
2.2.1 ES6+ 新特性
Promise、async/await、箭头函数、模板字符串……这些咱们用着爽,但 IE 全部不认。个人习惯:使用 Babel 编译,外加 polyfill 兜底。
// 箭头函数在 IE 中会报语法错误
// 需要 Babel 转成普通函数
const sum = (a, b) => a + b;
// Promise 在 IE 中不存在
// 需要引入 es6-promise polyfill
fetch('/api').then(res => res.json());
建议:使用 @babel/preset-env 配合 core-js 做按需 polyfill,别一股脑把所有 polyfill 都塞进去,影响包体积。
2.2.2 内置对象方法不一致
比如 Array.prototype.includes 在 IE 中不存在,String.prototype.startsWith 也是。还有 Object.assign 在 IE 下完全不可用。
// 安全写法:先用特性检测
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
// 自己实现一个
};
}
// 更好的方式:直接用 lodash 的 _.includes 或 polyfill
举个例子,我碰到过一个线上 Bug:某个列表筛选功能在 Chrome 正常,但用户用 Safari 11 时就白屏了。最后定位是用了 Array.flat(),而那个版本的 Safari 不支持。
2.2.3 定时器与异步行为差异
setTimeout 的最小延迟在各浏览器中不一样。我记得 Chrome 里嵌套超过 5 层后最小间隔会被强制设为 4ms,但老版 Firefox 会不同。还有 requestAnimationFrame 在隐藏标签页中行为也不一致。
2.3 DOM 差异
DOM 操作可以说是前端兼容性「重灾区」了。为什么?因为各浏览器对 DOM 标准实现的程度和方式不同。
2.3.1 节点操作差异
比如 childNodes 和 children:前者包含文本节点和注释节点,后者只包含元素节点。IE 8 及以下会把注释节点也当作元素节点,导致遍历出错。
// 安全的获取子元素方式
function getChildren(elem) {
// 用 children 属性,并过滤非元素节点
return Array.prototype.filter.call(elem.children, function(node) {
return node.nodeType === 1;
});
}
2.3.2 innerHTML vs innerText vs textContent
innerHTML 在几乎所有浏览器上都支持,但 innerText 和 textContent 可不一样。innerText 不是标准属性,Firefox 早期版本没有,而 textContent 在 IE 9 以下也不存在。
| 属性 | IE 8 | IE 9+ | Firefox | Chrome/Safari |
|---|---|---|---|---|
| innerHTML | ✅ | ✅ | ✅ | ✅ |
| innerText | ✅ | ✅ | ❌ | ✅ |
| textContent | ❌ | ✅ | ✅ | ✅ |
我个人推荐:直接用 textContent 并做好 fallback:elem.textContent || elem.innerText || ''。
2.3.3 样式获取方式
getComputedStyle 在 IE 9+ 才支持,IE 8 只能用 currentStyle。而且 getComputedStyle 返回的是只读的实时样式,而 currentStyle 在某些属性上行为也不同。
// 兼容写法
function getStyle(element, property) {
if (window.getComputedStyle) {
return window.getComputedStyle(element, null).getPropertyValue(property);
} else if (element.currentStyle) {
return element.currentStyle[property];
}
return '';
}
2.4 事件模型差异
事件处理是前端交互的核心,但不同浏览器对事件模型的处理可谓「五花八门」。我之前处理一个跨浏览器拖拽功能,差点被事件对象搞崩溃。
2.4.1 绑定与移除事件
标准方式:addEventListener / removeEventListener。但 IE 8 及以下使用 attachEvent / detachEvent,而且事件名前要加 on。
// 兼容的事件绑定函数
function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
}
一点提醒:attachEvent 里 this 指向 window,而不是触发元素。解决办法是封装一层匿名函数手动改变 this。另外 attachEvent 不支持捕获阶段,所以第三个参数永远没有用。
2.4.2 事件对象处理
标准事件对象通过回调参数传递,IE 则用 window.event。而且常用属性名也不同,比如 target vs srcElement,preventDefault vs returnValue = false。
// 获取事件目标元素
function getTarget(e) {
e = e || window.event;
return e.target || e.srcElement;
}
// 阻止默认行为
function preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
2.4.3 事件冒泡与捕获差异
标准事件流有三个阶段:捕获 → 目标 → 冒泡。但 IE 8 及以下只支持冒泡,不支持捕获。而且 event.stopPropagation() 在 IE 中对应 event.cancelBubble = true。
// 阻止冒泡
function stopPropagation(e) {
e = e || window.event;
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
}
小结一下
以上四类兼容性问题,几乎覆盖了你在日常开发中遇到的大部分跨浏览器 Bug。我个人觉得,最难的不是解决问题本身,而是「提前感知到可能会有问题」。所以养成良好的测试习惯——Chrome 写代码,Firefox 查样式,Safari 看交互,IE 兜底。经历过几次线上事故,你就明白这些经验值多珍贵了。