JavaScript 浏览器兼容性问题

常见的 JS 浏览器兼容问题及解决方案

问题

经常遇到的浏览器 JS 兼容性问题有哪些?如何解决?

解答

1. 事件处理兼容

// 添加事件监听
function addEvent(element, type, handler) {
  if (element.addEventListener) {
    // 标准浏览器
    element.addEventListener(type, handler, false);
  } else if (element.attachEvent) {
    // IE8 及以下
    element.attachEvent('on' + type, handler);
  } else {
    // 更老的浏览器
    element['on' + type] = handler;
  }
}

// 移除事件监听
function removeEvent(element, type, handler) {
  if (element.removeEventListener) {
    element.removeEventListener(type, handler, false);
  } else if (element.detachEvent) {
    element.detachEvent('on' + type, handler);
  } else {
    element['on' + type] = null;
  }
}

// 获取事件对象和目标元素
function getEvent(e) {
  return e || window.event;
}

function getTarget(e) {
  return e.target || e.srcElement;
}

// 阻止默认行为
function preventDefault(e) {
  if (e.preventDefault) {
    e.preventDefault();
  } else {
    e.returnValue = false; // IE
  }
}

// 阻止事件冒泡
function stopPropagation(e) {
  if (e.stopPropagation) {
    e.stopPropagation();
  } else {
    e.cancelBubble = true; // IE
  }
}

2. DOM 操作兼容

// 获取元素样式
function getStyle(element, property) {
  if (window.getComputedStyle) {
    // 标准浏览器
    return getComputedStyle(element)[property];
  } else {
    // IE8 及以下
    return element.currentStyle[property];
  }
}

// 获取文本内容
function getText(element) {
  return element.textContent !== undefined 
    ? element.textContent 
    : element.innerText;
}

// classList 兼容(IE9 以下不支持)
function addClass(element, className) {
  if (element.classList) {
    element.classList.add(className);
  } else {
    if (!hasClass(element, className)) {
      element.className += ' ' + className;
    }
  }
}

function hasClass(element, className) {
  if (element.classList) {
    return element.classList.contains(className);
  }
  return new RegExp('\\b' + className + '\\b').test(element.className);
}

3. XMLHttpRequest 兼容

function createXHR() {
  if (window.XMLHttpRequest) {
    // 标准浏览器
    return new XMLHttpRequest();
  } else {
    // IE6/7
    return new ActiveXObject('Microsoft.XMLHTTP');
  }
}

4. 数组方法 Polyfill

// Array.prototype.forEach polyfill
if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(callback, thisArg) {
    for (var i = 0; i < this.length; i++) {
      callback.call(thisArg, this[i], i, this);
    }
  };
}

// Array.prototype.map polyfill
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {
    var result = [];
    for (var i = 0; i < this.length; i++) {
      result.push(callback.call(thisArg, this[i], i, this));
    }
    return result;
  };
}

// Array.prototype.filter polyfill
if (!Array.prototype.filter) {
  Array.prototype.filter = function(callback, thisArg) {
    var result = [];
    for (var i = 0; i < this.length; i++) {
      if (callback.call(thisArg, this[i], i, this)) {
        result.push(this[i]);
      }
    }
    return result;
  };
}

5. 现代解决方案

// 使用 Babel 转译 ES6+ 代码
// .babelrc 配置
{
  "presets": [
    ["@babel/preset-env", {
      "targets": "> 0.25%, not dead",
      "useBuiltIns": "usage",
      "corejs": 3
    }]
  ]
}

// 使用 core-js 提供完整的 polyfill
import 'core-js/stable';
import 'regenerator-runtime/runtime';

6. 特性检测

// 推荐使用特性检测而非浏览器检测
if ('querySelector' in document) {
  // 支持 querySelector
  document.querySelector('.box');
}

if ('localStorage' in window) {
  // 支持 localStorage
  localStorage.setItem('key', 'value');
}

if ('Promise' in window) {
  // 支持 Promise
  new Promise(resolve => resolve());
}

// 使用 Modernizr 库进行特性检测
if (Modernizr.flexbox) {
  // 支持 flexbox
}

关键点

  • 特性检测优于浏览器检测:检测功能是否存在,而非判断浏览器类型
  • Polyfill 补齐缺失 API:为旧浏览器添加新 API 的实现
  • Babel 转译 ES6+ 语法:将新语法转换为 ES5 兼容代码
  • 事件处理差异最常见:addEventListener/attachEvent、event 对象获取方式
  • 使用成熟的工具链:core-js、@babel/preset-env 自动处理兼容性