前端最让人头疼的莫过于兼容性问题,由于操作系统的不同以及操作系统版本,和浏览器内核的不同以及浏览器版本不同,造成很多 CSS 样式、JS API 的兼容性问题。
主流移动设备
Android 设备情况,根据OpenSignal 在 2015 年 8 月发布的基础统计数据可以知道Andoroid的设备复杂度:ANDROID FRAGMENTATION VISUALIZED
iOS 设备情况,苹果公司已推出42款iPhone型号。
主流浏览器
参考文章 OSCHINA - 2022 年 7 月浏览器市场份额提供的数据:
中国 PC 端浏览器市场份额如下:
-
Chrome:35.47%
-
360 Safe:18.76%
-
Edge:12.94%
-
Firefox:11.12%
-
QQ Browser:9.95%
-
Safari:3.92%
中国移动端浏览器市场份额如下:
-
Chrome:48.57%
-
Safari:21.9%
-
UC Browser:19.35%
-
QQ Browser:7.88%
-
Samsung Internet:0.73%
-
Android:0.53%
浏览器内核
浏览器内核包括浏览器的渲染引擎、JS引擎,不同的渲染引擎对网页编写语法的解释不同,所以同一网页在不同内核的浏览器的渲染效果可能不同,而 JS 引擎决定所有的 JS API 能不能使用,这就是浏览器内核造成兼容性问题的根本原因。
国内主流浏览器的内核分别如下:
-
Chrome:Chrome 浏览器使用 Blink 内核。Blink 是一个基于 WebKit 的开源渲染引擎,由 Google 主导开发。从 Chrome 28 版本开始,Chrome 放弃了 WebKit,改用 Blink 引擎
-
360 浏览器:使用的是基于 WebKit 内核开发的 X5 内核
-
Edge:微软的 Edge 浏览器经历了两个版本的内核。从 2020 年开始,微软发布了基于 Chromium 项目的新版 Edge 浏览器,它使用了与 Chrome 相同的 Blink 内核
-
Firefox:Firefox 浏览器使用名为 Gecko 的内核。Gecko 是 Mozilla 基金会开发的开源渲染引擎
-
QQ 浏览器:使用的是基于 WebKit 内核开发的 X5 内核(现已升级至Blink)
-
UC 浏览器:基于 WebKit 内核开发的 U3 内核【增加云端架构(实现压缩流量、加速加载功能)】
-
Safari:使用的是 WebKit 的内核
-
百度浏览器:使用的是基于 WebKit 内核开发的 T5 内核
-
Android 微信内置浏览器:微信5.4之前没有内置浏览器;微信5.4-6.1 (安装了QQ浏览器使用X5,未安装浏览器使用系统内核);腾讯TBS X5、微信自研XWeb(自2020年,现在大部分是这个)
-
iOS 微信内置浏览器:UIWebView、WKWebView(2017年3月1日前逐步升级)
总结:
-
PC 端浏览器兼容问题重点关注 WebKit 内核和 Gecko 内核的兼容性问题
-
移动端浏览器兼容问题重点关注 WebKit 内核
浏览器版本
随着 HTML5、CSS3、ES6 带来的新特性,新的浏览器版本会跟着支持这些新特性,但是老的浏览器版本以及迭代缓慢的浏览器(例如 IE)因为不支持这些新特性而引起兼容性问题。
思考
我们要从这些角度去解决兼容性问题:
-
开发过程中先判断操作系统、浏览器内核及版本,写条件逻辑判断,以及利用一些业内广泛使用的能解决兼容性问题的工具去预防兼容性问题
-
测试过程中尽可能全面地进行真机调试或模拟器调试
-
在测试过程中发现并积累常见的兼容性问题,方便后面开发过程中提前避免问题
判断
如何判断操作系统
根据 navigator.userAgent 字段的值,对子字符串做匹配查找
/** 判断客户端操作系统 */
export function judgeClient() {
const userAgent = navigator.userAgent;
let os = 'Unknown';
if (userAgent.indexOf('Win') !== -1) {
os = 'Windows';
} else if (userAgent.indexOf('Mac') !== -1) {
os = 'macOS';
} else if (userAgent.indexOf('Linux') !== -1 || userAgent.indexOf('X11') !== -1) {
os = 'Linux';
} else if (/Android/.test(userAgent)) {
os = 'Android';
} else if (/iPhone|iPad|iPod/.test(userAgent)) {
os = 'iOS';
}
return os;
}
如何判断浏览器类型、内核、版本根
据 navigator.userAgent 字段的值,对子字符串做匹配查找
function judgeBrowser() {
const userAgent = navigator.userAgent;
let browser = 'Unknown';
let engine = 'Unknown';
let version = 'Unknown';
if (userAgent.search(/MSIE/) != -1 || userAgent.search(/Trident/) != -1) {
browser = 'Internet Explorer';
engine = 'Trident';
version = userAgent.match(/((MS)?IE\s|(\s?rv:))[0-9\.]+/)[0].replace(/[^\d.]/g, '');
} else if (userAgent.search(/Firefox/) != -1) {
browser = 'Firefox';
engine = 'Gecko';
version = userAgent.match(/Firefox\/([0-9\.]+)/)[1];
} else if (userAgent.search(/Chrome/) != -1) {
browser = 'Chrome';
engine = 'WebKit';
version = userAgent.match(/Chrome\/([0-9\.]+)/)[1];
} else if (userAgent.search(/Safari/) != -1 && userAgent.search(/Chrome/) == -1) {
browser = 'Safari';
engine = 'WebKit';
version = userAgent.match(/Version\/([0-9\.]+)/)[1];
} else if (userAgent.search(/Opera/) != -1 || userAgent.search(/OPR/) != -1) {
browser = 'Opera';
engine = 'WebKit'; // 对于 Opera 15 及以上版本
version = userAgent.match(/(Opera\/|Version\/|OPR\/)([0-9\.]+)/)[2];
}
return {
browser: browser,
engine: engine,
version: version,
};
}
虽然该函数识别了常见的浏览器类型、内核和版本,但为了支持更多浏览器或准确的版本信息,可以考虑使用更强大的库,例如 UAParser.js
开发中预防兼容性问题
Can I Use 网站
CSS、JS API 使用前先在 caniuse 网站查询是否支持
Autoprefixer 方案
业内广泛采用 autoprefixer 方案来自动根据浏览器内核添加前缀
在 webpack 中,可以通过安装 npm 包 postcss-loader 来启用 Autoprefixer
步骤如下:
-
安装 postcss-loader autoprefixer 依赖 运行 npm i postcss-loader autoprefixer -D 安装相关依
npm i postcss-loader autoprefixer -D
-
在项目根目录中创建postcss 的配置文件 postcss.config.js,并初始化如下配置
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [autoprefixer] // 挂载插件
}
-
在 webpack.config.js 的 module -> rules数组中,修改 css 的 loader 规则:
rules: [
{
test: /\.css/,
use: ['style-loader','css-loader','postcss-loader']
}
]
重新运行项目即可
Babel 方案
业内广泛采用 Babel 方案 来将新版本 JavaScript(如 ES6、ES7、ES8 等)转换为浏览器和其他环境中可执行的旧版本 JavaScript(如 ES5),以便能够运行在当前和旧版本的浏览器或其他环境中。
Babel 的一些主要功能包括:
-
语法转换:将新的、未广泛支持的 JavaScript 语法转换为旧的、更具兼容性的语法(如将箭头函数、类、async/await等转换为 ES5 等效版本)。
-
Polyfills:通过填充旧浏览器中缺少的 JavaScript API,使其支持一些新特性。例如,添加 Promise、Array.from 等新功能。这使新特性可在更旧的浏览器环境中使用。
-
插件:Babel 可以通过插件系统扩展,为原始 JavaScript 代码添加额外功能,如代码去除不使用的引入( tree shaking )、移除 console.log 语句 和 压缩代码 等。
-
预设:Babel 预设是一组经过预先筛选的插件集合。将预设应用于 Babel 配置,方便快速配置 Babel 进行特定的转换任务。例如,使用 @babel/preset-env 将代码指定为匹配目标浏览器环境的版本。
以下是一个 Babel 配置示例(.babelrc 或 babel.config.json 文件),使用 @babel/preset-env 预设将代码转换为当前浏览器环境支持的版本:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead"
}
]
]
}
在这个配置示例中,通过指定:要兼容 0.25% 及以上市场占有率的浏览器,排除已停止支持的浏览器。
这是语法转换的例子
// Babel 接收到的输入是: ES2015 箭头函数
[1, 2, 3].map(n => n + 1);
// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
return n + 1;
});
前言说到移动端设备型号复杂,尽管在开发过程中尽可能避免兼容性问题,但是还是很难预知所有的 CSS、JS API 在具体哪些设备不兼容,所以我们还需要把重心放在测试过程中。
如何测试兼容性问题
PC 端 Web 兼容性测试
在真实设备上进行测试
只有在真实的设备和操作系统上进行测试,才能提供最准确的结果。如果可能,获取并在所有目标操作系统和浏览器(包括不同版本)上进行测试。
使用模拟器和虚拟机
如果无法获得所有真实设备,可以通过模拟器和虚拟机进行测试。例如,可以使用 VirtualBox 或 VMware 设置虚拟机来模拟不同的操作系统,或使用如 Blisk 这样的开发者浏览器进行模拟。
使用跨浏览器测试平台
这类服务提供了在多种真实设备和环境中进行在线测试的能力。例如,BrowserStack 和 Sauce Labs 提供了在真实设备和虚拟机上进行测试的云服务。
使用自动化测试工具
通过使用自动化测试工具和框架(如 Selenium,Cypress,Puppeteer),可以自动地在各种环境下运行测试用例,从而提高测试效率。
可访问性测试
使用如 Lighthouse 或 WebPageTest 进行可访问性测试,还可以进行性能测试。
移动端 H5 兼容性测试
用户移动端设备复杂、手机版本众多,所以移动端 H5 兼容性测试更加具有挑战性。
真机或模拟器测试
这类测试是 CSS、JS API 兼容性测试的重点:
-
使用真实设备:将网页加载到不同类型的设备上进行测试,例如桌面电脑、笔记本电脑、平板电脑和智能手机等。
-
使用模拟器和仿真器:利用模拟器或仿真器来模拟不同设备的环境,并进行测试。常用的模拟器包括 Android Studio 自带的模拟器和 Xcode 中的 iOS 模拟器,以及微信开发者工具里的多设备模拟器。
自动化测试工具
可以通过编写测试用例的方式,然后在跨平台、跨浏览器在各个真机上进行模拟测试,例如这些:
-
Selenium:Selenium 是一个流行的自动化测试框架,用于模拟用户在不同浏览器上的交互。它支持多种编程语言,并提供了丰富的API和工具,使开发者可以编写功能测试、回归测试和跨浏览器兼容性测试。
-
TestCafe:TestCafe 是一款基于JavaScript的自动化测试工具,用于跨浏览器测试。它不需要额外的插件或驱动程序,能够在真实的浏览器中运行测试,并支持多个浏览器和平台。
-
Cypress:Cypress 是另一个流行的自动化测试工具,专注于现代Web应用的端到端测试。它提供了简单易用的API,允许开发者在多个浏览器中运行测试,并具有强大的调试和交互功能。
-
BrowserStack:BrowserStack 是一个云端跨浏览器测试平台,提供了大量真实浏览器和移动设备进行测试。它允许开发者在不同浏览器上同时运行测试,以检测网页在不同环境中的兼容性问题。
常见兼容性问题
CSS 兼容性问题
样式前缀
CSS 属性由于浏览器内核不同需要加不同的前缀进行兼容:
-
Chrome(谷歌浏览器) 与 Safari(苹果浏览器) 内核:Webkit (中译无) 前缀:-webkit-IE (IE浏览器) 内核:Trident (中译三叉戟) 前缀:-ms-
-
Firefox (火狐浏览器) 内核:Gecko(中译壁虎) 前缀:-moz-
-
Opera (欧朋浏览器) 内核:Presto(中译迅速) 前缀:-o-
例如:
-webkit-border-radius: 10px; /*谷歌浏览器*/
-ms-border-radius: 10px; /*IE浏览器*/
-moz-border-radius: 10px; /*火狐浏览器*/
-o-border-radius: 10px; /*欧朋浏览器*/
border-radius: 10px;
但为了解决兼容性问题 CSS 属性都这么写很费劲,所以我们采用上面说到的 Autoprefixer 自动添加浏览器前缀。
flex 布局
覆盖率 98.14%
-
6-9 版本的 IE
-
10-11.5 版本的 Opera
-
12 版本的 Opera Mobile
不被支持
解决:使用 Autoprefixer 自动添加浏览器前缀,确保 Flexbox 的兼容性。针对 IE10 和 IE11 特殊情况,还可考虑使用其他布局代替 Flexbox(如浮动、行内块等)。
grid 布局
覆盖率:97.56%,在众多浏览器的较旧版本不被支持
解决:使用 Autoprefixer 添加前缀。必要时使用 flex 布局或其他布局(如浮动、行内块等)代替。
CSS 变量
在较旧的浏览器(如 IE11 及以下版本)中,CSS 变量(自定义属性)不被支持。
解决方法:在不支持 CSS 变量的旧浏览器中,可以使用预处理器(如 Sass、Less 等)实现类似的功能。另一个选择是使用 PostCSS 插件 postcss-custom-properties 将 CSS 变量编译为静态值。
动画和过渡
CSS 动画和过渡在旧浏览器(如 IE9 及更早版本)中可能不受支持。此外,某些浏览器可能需要特定的前缀(如 -webkit-)才能识别动画和过渡属性。
解决方法:使用 Autoprefixer 自动处理浏览器前缀。针对不支持 CSS 动画和过渡的浏览器,可以实现回退方法(当动画和过渡不是关键内容时,可以仅使用静态表现形式),或使用 JavaScript 动画库(如 Animate.css 或 GSAP)实现兼容动画
JS API 兼容性问题
iOS 端获取时间异常
前端通过 new Date() 这个 JS API 来获取时间,但在部分 iOS 设备里如果传入格式为 "YYYY-MM-DD HH:mm:ss" 的日期字符串将会返回 NaN,如:
const time = new Date("2023-12-06 12:00:00")
原因是:部分 iOS 设备对于非标准日期字符串的解析相对较严格,认为 "YYYY-MM-DD HH:mm:ss" 这种格式不是日期时间,所以返回 NaN。
解决方案:采用业界内广泛使用的 dayjs 来解决这个问题
步骤如下:
一、安装
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script> // CDN 资源
npm install dayjs // NPM 安装
二、引入
const dayjs = require('dayjs') // ES 2015 之前
import dayjs from 'dayjs' // ES 2015
三、使用
获取时间字符串,格式('YYYY-MM-DD HH:mm:ss')
dayjs().format('YYYY-MM-DD HH:mm:ss') // 2023-12-04 21:03:24
dayjs(1701695087283).format('YYYY-MM-DD HH:mm:ss') // 2023-12-04 21:05:24
iOS 端音频无法自动播放问题
iOS 的 Safari 浏览器和 iOS 微信内置浏览器 都不支持 audio 的自动播放,解决方法是:
-
微信内置浏览器调用微信的 WeixinJSBridgeReady 方法;
-
iOS的 safari 浏览器下,只能通过 ontouchstart 来触发自动播放。
function audioAutoPlay(id) {
const audio = document.getElementById(id),
const play = function() {
audio.play();
document.removeEventListener("touchstart",play,false);
};
audio.play();
document.addEventListener("WeixinJSBridgeReady", function() {
play();
}, false);
document.addEventListener('YixinJSBridgeReady', function() {
play();
}, false);
document.addEventListener("touchstart", play, false);
}
audioAutoPlay('myAudio');
其他
-
Promise
ES6 提供的 Promise 是异步编程的新特性。Promises 在一些较旧的浏览器上(如 IE11 及更早版本)不支持。解决方法:使用 polyfills,如 es6-promise。
-
箭头函数
箭头函数(()=>{})在 IE11 及更早版本的浏览器中不受支持。解决方法:使用 Babel 将箭头函数转译成 ES5 函数,或使用传统函数语法。
-
class(类)
ES6 引入了基于 class 关键字的面向对象编程。这个特性在 IE11 及更早版本的浏览器中不受支持。解决方法:使用 Babel 将 class 转换为 ES5 函数形式,或使用原型链继承的方式。
-
Fetch API
作为 XMLHttpRequest 的现代替代方案,Fetch API 在较旧的浏览器(尤其是 Internet Explorer)中并不受支持。解决方法:考虑使用 whatwg-fetch 等 Polyfill 或使用 XMLHttpRequest。
-
Object.assign
Object.assign() 用于将一个或多个对象的属性合并到目标对象。这个方法在 IE11 及更早版本不受支持。解决方法:使用 Polyfill,比如 object.assign。
-
requestAnimationFrame
requestAnimationFrame 是一个进行高性能动画和游戏循环的函数。在一些旧版本浏览器中不支持。解决方案:实现 Polyfill,回退到 setTimeout 或 setInterval。
-
WebSocket
WebSockets 用于实现实时通信功能,在较旧的浏览器(如 IE9 及以下版本)中不被支持。解决方法:考虑保留策略,如轮询、长轮询。
总结
-
Autoprefixer 方案解决 CSS 样式前缀问题postcss-custom-properties 方案解决 CSS 变量不被支持的问题
-
Animate.css 方案解决 CSS 动画和过渡不被支持的问题
-
Babel 方案解决 ES6 及以上新特性不被支持的问题
-
VMware 方案创建虚拟机
-
Android Studio 自带的模拟器和 Xcode 中的 iOS 模拟器,以及微信开发者工具里的多设备模拟器
-
开发前:在 caniuse 网站先查一下要使用的 API 兼容性,如果兼容性很好可以直接使用,如果兼容性很差则寻找中转方案
-
开发过程中:用业界广泛使用的方案代替代码里的条件逻辑判断
-
开发后:模拟器或真机实测
-
测试过程中:发现问题并积累,回到第 1 步
行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 759968159,里面有各种测试开发资料和技术可以一起交流哦。
最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。