IE 浏览器检测与资源智能加载脚本

20 阅读5分钟

一、背景与痛点

在 Web 开发中,IE 浏览器(尤其是 IE9/10/11)的兼容性问题一直是前端工程师的噩梦。现代浏览器支持的新特性(如 CSS3 动画、ES6+ 语法、Flexbox/Grid 布局等)在 IE 中往往无法正常运行,导致页面错乱或功能失效。

传统的兼容方案存在以下问题:

方案问题
CSS Hack代码可读性差,维护困难
特性检测检测代码与业务代码耦合
UA 嗅探逻辑分散,难以复用
条件注释仅 IE10+ 支持,不够灵活

本文介绍一种 浏览器检测与资源智能加载 的解决方案,通过一个独立的脚本统一处理 IE 浏览器的检测和资源过滤。

二、核心功能

该脚本实现以下核心功能:

  1. 精确检测 IE 版本 :识别 IE9/10/11,区分 Edge 浏览器
  2. 智能过滤样式 :根据标记自动加载/移除 CSS 文件
  3. 智能过滤脚本 :根据标记自动加载/移除 JS 文件
  4. 动态标签监听 :使用 MutationObserver 监听动态添加的资源标签
  5. 全局变量暴露 :将检测结果暴露给其他脚本使用

三、使用方法

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;
    }
  }
})();

六、注意事项

  1. 执行顺序 :ie.js 必须在所有样式文件之前引入,确保在样式加载前完成过滤
  2. 内联脚本 :脚本不会移除内联
  3. 性能考虑 :脚本在页面加载过程中多次执行清理操作,但都是 O(n) 复杂度,对性能影响极小
  4. Edge 兼容 :脚本正确识别 Edge 浏览器并将其视为非 IE
  5. 类名标识 :脚本会在 标签上添加 ie9 / ie10 / ie11 类名,方便 CSS 控制

七、总结

本文介绍的 IE 浏览器检测与资源智能加载脚本,提供了一种优雅的浏览器兼容性处理方案:

  • 独立性强 :与业务代码解耦,可复用
  • 标记灵活 :通过 data 属性控制资源加载
  • 兼容性 :支持 IE9/10/11 及现代浏览器
  • 性能优 :执行次数少,不影响页面加载速度

通过这种方式,前端工程师可以更优雅地处理 IE 兼容问题,将精力集中在业务逻辑的实现上。