一、背景与痛点
在 Web 开发中,IE 浏览器(尤其是 IE9/10/11)的兼容性问题一直是前端工程师的噩梦。现代浏览器支持的新特性(如 CSS3 动画、ES6+ 语法、Flexbox/Grid 布局等)在 IE 中往往无法正常运行,导致页面错乱或功能失效。
传统的兼容方案存在以下问题:
| 方案 | 问题 |
|---|---|
| CSS Hack | 代码可读性差,维护困难 |
| 特性检测 | 检测代码与业务代码耦合 |
| UA 嗅探 | 逻辑分散,难以复用 |
| 条件注释 | 仅 IE10+ 支持,不够灵活 |
本文介绍一种 浏览器检测与资源智能加载 的解决方案,通过一个独立的脚本统一处理 IE 浏览器的检测和资源过滤。
二、核心功能
该脚本实现以下核心功能:
- 精确检测 IE 版本 :识别 IE9/10/11,区分 Edge 浏览器
- 智能过滤样式 :根据标记自动加载/移除 CSS 文件
- 智能过滤脚本 :根据标记自动加载/移除 JS 文件
- 动态标签监听 :使用 MutationObserver 监听动态添加的资源标签
- 全局变量暴露 :将检测结果暴露给其他脚本使用
三、使用方法
3.1 引入脚本
定义该脚本文件为ie.js
在 HTML 的 标签中、样式文件加载之前引入脚本:
<head>
<meta charset="UTF-8">
<title>页面标题</title>
<!-- 必须放在样式加载之前 -->
<script src="./js/ie.js"></script>
<!-- IE 专用样式 -->
<link rel="stylesheet" href="./css/style-ie.css" data-ie-only="true" />
<!-- 非 IE 专用样式 -->
<link rel="stylesheet" href="./css/modern.css" data-non-ie-only="true" />
<!-- 通用样式(所有浏览器都加载) -->
<link rel="stylesheet" href="./css/common.css" />
</head>
3.2 标记规则
通过 data-ie-only 和 data-non-ie-only 属性标记资源的适用范围:
| 标记 | 说明 |
|---|---|
| data-ie-only="true" | 仅 IE 浏览器加载 |
| data-non-ie-only="true" | 仅非 IE 浏览器加载 |
| 无标记 | 所有浏览器都加载 |
同样适用于
<!-- IE 专用脚本 -->
<script src="./js/polyfill-ie.js" data-ie-only="true"></script>
<!-- 非 IE 专用脚本 -->
<script src="./js/modern-js.js" data-non-ie-only="true"></script>
3.3 获取检测结果
脚本将检测结果暴露到全局作用域,其他脚本可以直接使用:
// 是否为 IE9/10/11
if (window.isIE) {
console.log('当前浏览器是 IE ' + window.ieVersion);
// IE 兼容处理逻辑
} else {
console.log('当前浏览器不是 IE');
// 现代浏览器逻辑
}
四、核心实现解析
4.1 IE 版本检测
通过解析 navigator.userAgent 字符串判断浏览器类型和版本
function detectIE() {
var ua = window.navigator.userAgent;
// 排除 Edge 浏览器(Edge 不是 IE)
if (ua.indexOf("Edge/") > 0) {
return false;
}
// IE11 检测:Trident/7.0 + rv:11.0
if (ua.indexOf("Trident/") > 0 && ua.indexOf("rv:11") > 0) {
return 11;
}
// IE10 检测:MSIE 10.0
if (ua.indexOf("MSIE 10.0") > 0) {
return 10;
}
// IE9 检测:MSIE 9.0
var msie = ua.indexOf("MSIE ");
if (msie > 0) {
var version = parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10);
if (version === 9) {
return 9;
}
}
return false;
}
检测逻辑说明 :
- Edge/ :Edge 浏览器虽然包含 IE 内核,但不是 IE,需要排除
- Trident/7.0 + rv:11.0 :IE11 的特征标识
- MSIE 10.0 / MSIE 9.0 :IE10/IE9 的特征标识
4.2 样式过滤
遍历所有 标签,根据标记决定是否移除:
function removeUnusedStyles() {
var links = document.getElementsByTagName("link");
var toRemove = [];
for (var i = 0; i < links.length; i++) {
var link = links[i];
var shouldRemove = false;
// 检查是否标记为 IE 专用
if (hasMark(link, "ie-only")) {
shouldRemove = !isIE; // 非 IE 时移除
}
// 检查是否标记为非 IE 专用
else if (hasMark(link, "non-ie-only")) {
shouldRemove = isIE; // IE 时移除
}
if (shouldRemove && link.parentNode) {
toRemove.push(link);
}
}
// 批量移除
for (var j = 0; j < toRemove.length; j++) {
toRemove[j].parentNode.removeChild(toRemove[j]);
}
}
4.3 动态标签监听
使用 MutationObserver 监听动态添加到 DOM 中的 和
if (window.MutationObserver) {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1) {
if (node.tagName === "LINK") {
removeUnusedStyles();
} else if (node.tagName === "SCRIPT") {
removeUnusedScripts();
}
}
});
});
});
if (document.head) {
observer.observe(document.head, { childList: true, subtree: false });
}
if (document.body) {
observer.observe(document.body, { childList: true, subtree: false });
}
}
4.4 执行时机
脚本在多个时机执行,确保捕获所有资源标签
// 1. 立即执行,尽可能快地移除
removeUnusedStyles();
removeUnusedScripts();
// 2. 异步执行一次,确保捕获到所有已解析的标签
setTimeout(function () {
removeUnusedStyles();
removeUnusedScripts();
}, 0);
// 3. DOMContentLoaded 时再执行一次
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
removeUnusedStyles();
removeUnusedScripts();
// 为 HTML 标签添加 IE 版本标识类
if (isIE) {
var html = document.documentElement;
var prefix = html.className ? html.className + " " : "";
html.className = prefix + "ie" + ieVersion;
}
});
}
五、完整代码
/**
* 浏览器检测脚本
* 功能:检测 IE9/10/11 浏览器,移除不需要的样式和脚本
* 注意:必须在 HTML head 中、样式加载之前执行
*/
(function () {
"use strict";
// ==================== 1. IE 版本检测 ====================
function detectIE() {
var ua = window.navigator.userAgent;
if (ua.indexOf("Edge/") > 0) {
return false;
}
if (ua.indexOf("Trident/") > 0 && ua.indexOf("rv:11") > 0) {
return 11;
}
if (ua.indexOf("MSIE 10.0") > 0) {
return 10;
}
var msie = ua.indexOf("MSIE ");
if (msie > 0) {
var version = parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10);
if (version === 9) {
return 9;
}
}
return false;
}
// ==================== 2. 初始化全局变量 ====================
var ieVersion = detectIE();
var isIE = ieVersion === 9 || ieVersion === 10 || ieVersion === 11;
window.isIE = isIE;
window.ieVersion = ieVersion;
// ==================== 3. 样式/脚本过滤函数 ====================
function hasMark(element, type) {
var dataAttr = element.getAttribute("data-" + type);
if (dataAttr !== null) {
return true;
}
var className = (
element.getAttribute("class") ||
element.className ||
""
).toString();
return className.indexOf(type) > -1;
}
function removeUnusedStyles() {
var links = document.getElementsByTagName("link");
var toRemove = [];
for (var i = 0; i < links.length; i++) {
var link = links[i];
var shouldRemove = false;
if (hasMark(link, "ie-only")) {
shouldRemove = !isIE;
} else if (hasMark(link, "non-ie-only")) {
shouldRemove = isIE;
}
if (shouldRemove && link.parentNode) {
toRemove.push(link);
}
}
for (var j = 0; j < toRemove.length; j++) {
toRemove[j].parentNode.removeChild(toRemove[j]);
}
}
function removeUnusedScripts() {
var scripts = document.getElementsByTagName("script");
var toRemove = [];
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
if (!script.src) continue;
var shouldRemove = false;
if (hasMark(script, "ie-only")) {
shouldRemove = !isIE;
} else if (hasMark(script, "non-ie-only")) {
shouldRemove = isIE;
}
if (shouldRemove && script.parentNode) {
toRemove.push(script);
}
}
for (var j = 0; j < toRemove.length; j++) {
toRemove[j].parentNode.removeChild(toRemove[j]);
}
}
// ==================== 4. 立即执行清理 ====================
removeUnusedStyles();
removeUnusedScripts();
setTimeout(function () {
removeUnusedStyles();
removeUnusedScripts();
}, 0);
// ==================== 5. 监听动态添加的标签 ====================
if (window.MutationObserver) {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1) {
if (node.tagName === "LINK") {
removeUnusedStyles();
} else if (node.tagName === "SCRIPT") {
removeUnusedScripts();
}
}
});
});
});
if (document.head) {
observer.observe(document.head, { childList: true, subtree: false });
}
if (document.body) {
observer.observe(document.body, { childList: true, subtree: false });
}
}
// ==================== 6. DOM 就绪后执行 ====================
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
removeUnusedStyles();
removeUnusedScripts();
if (isIE) {
var html = document.documentElement;
var prefix = html.className ? html.className + " " : "";
html.className = prefix + "ie" + ieVersion;
}
});
} else {
removeUnusedStyles();
removeUnusedScripts();
if (isIE) {
var html = document.documentElement;
var prefix = html.className ? html.className + " " : "";
html.className = prefix + "ie" + ieVersion;
}
}
})();
六、注意事项
- 执行顺序 :ie.js 必须在所有样式文件之前引入,确保在样式加载前完成过滤
- 内联脚本 :脚本不会移除内联
- 性能考虑 :脚本在页面加载过程中多次执行清理操作,但都是 O(n) 复杂度,对性能影响极小
- Edge 兼容 :脚本正确识别 Edge 浏览器并将其视为非 IE
- 类名标识 :脚本会在 标签上添加 ie9 / ie10 / ie11 类名,方便 CSS 控制
七、总结
本文介绍的 IE 浏览器检测与资源智能加载脚本,提供了一种优雅的浏览器兼容性处理方案:
- 独立性强 :与业务代码解耦,可复用
- 标记灵活 :通过 data 属性控制资源加载
- 兼容性 :支持 IE9/10/11 及现代浏览器
- 性能优 :执行次数少,不影响页面加载速度
通过这种方式,前端工程师可以更优雅地处理 IE 兼容问题,将精力集中在业务逻辑的实现上。