目录

  • 1. 跨浏览器注意事项
  • 2. 五大开发问题
  • 2.1 浏览器的bug和差异
  • 2.2 浏览器的bug修复
  • 2.3 外部代码和标记
  • 实现代码健壮性的策略
  • 2.4 浏览器的回归
  • 2.4 浏览器缺失的功能
  • 3. 实现策略
  • 3.1 安全的跨浏览器修复方法
  • 3.2 特性检测和垫片(polyfill)
  • 3.3 不可测试的浏览器问题
  • 4. 减少假设

1. 跨浏览器注意事项

  • 在选择兼容哪些浏览器时要考虑:
  • 目标受众的期望和需要(由项目确定)
  • 浏览器的市场份额
  • 浏览器支持所需的工作量

不应以牺牲质量赢取覆盖率

2. 五大开发问题

2.1 浏览器的bug和差异

  • 代码必须完全符合浏览器提供的特性。
  • 这需要完整的测试工具,足以覆盖代码和常用的和不常用的用例

2.2 浏览器的bug修复

  • 为解决浏览器当前bug使用特殊技巧,将来浏览器发布新版本修复了bug,就可能会出现问题
  • 面对浏览器的不一致性,通常需要(通过用户代理字符串)检测当前浏览器的名字
  • 在确定某一功能是否时潜在的错误时,使用规范进行验证

2.3 外部代码和标记

  • 任何可重用的代码必须与围绕它的代码共存
  • 我们的代码不仅必须能够经受得住可能写的很糟糕的外部代码,还必须得克服环境对代码的不利影响

实现代码健壮性的策略

  • 代码封装
  • 封装是指代码(如同)放在容器里,从广义上讲,是一种限制访问其他对象组件的语言机制
  • 尽可能少地使用全局变量,最好仅限一个
  • 为了保证代码的封装,需要避免其他操作,如修改已经存在的变量、函数原型甚至DOM元素
  • 模范代码
  • 当我们的代码与不可控的代码同时运行是,为了安全,需要假设最糟糕的情况
  • 应对ID滥用(贪婪ID问题)
  • 大部分浏览器具有一些反特性,这些特性使得原始元素与添加在元素上的id或name属性产生关联,当id或name属性与已有的属性产生冲突时,就会出现意外
  • 这是为了兼容过去的浏览器的处理方法,老式浏览器不具备获取DOM元素的方法
  • 要避免编写有可能与标准属性发生冲突的过于简单的id和name属性
  • 开发过程中尤其要避免submit值,以免出现bug
<form id="form" action="/conceal">
    <input type="text" id="action">
    <input type="submit" id="submit">
</form>
<script>
    // 预期返回form标签的action属性,返回的却是input标签对象
    console.log(document.getElementById("form").action);
    // <input type="text" id="action">
    document.getElementById("form").submit();
    // Uncaught TypeError: document.getElementById(...).submit is not a function

    // 原因:浏览器将<form>元素内所有input标签都作为表单form的属性
    // 添加到form属性的名称是input元素的id或name属性
    // 当form本身的属性与input元素的id或name属性冲突时
    // form本身的属性就被input元素的id或name属性替换了
    // from.submit属性也发生了同样的情况
</script>
  • 样式和脚本的加载顺序
  • 将外部样式表单防止在外部脚本文件之前,确保CSS规则在代码执行时已经可用

2.4 浏览器的回归

  • 因浏览器的bug或不向后兼容的API发生变化导致代码不可预期地中断了,即过去使用的特性不再运行了
  • 预期的变化
  • 一些API发生的可预见性的变化,可以提前检测并处理(使用if判断标准API和专有API是否有效)
  • 面对不可预期的变化,最佳实践时在浏览器发行的版本中模拟测试,以快速发现问题

2.4 浏览器缺失的功能

3. 实现策略

3.1 安全的跨浏览器修复方法

  • 安全的跨浏览器修复方法有两个重要的特点:
  • 在其他浏览器上没有副作用
  • 不使用浏览器或特性检查
// ignore negative width and height values(来之jQuery)
if ((key == "width" || key == "height") && parseFloat(value) < 0) value = undefined;
// 将width或height值设置为负数时,在IE某些版本会报错,在其他版本或其他浏览器会被忽略
// 这行代码会被大多数浏览器无任何影响,只对抛出错误的IE浏览器产生作用

// 代码来自jQuery
// input元素的type属性本身作为DOM的一部分,在IE浏览器内部是不允许修改的,修改会抛出异常
// jQuery给出中间方案,所有浏览器上都不允许修改input的type属性,并抛出统一的异常信息
if (name == "type" && elem.nodeName.toLowCase() == "input" && elem.parentNode) throw "type attribute can't be changed"; 
// jQuery团队认为:一致的API、一致的行为,比开发跨浏览器代码更重要

3.2 特性检测和垫片(polyfill)

  • 检测某一对象或对象属性是否存在,如果存在,则假设提供了内置方法
  • 通常,特性检测用于在多种API中做出选择,这些API提供相同或相识的功能
  • 对于浏览器缺失的特性,通常使用垫片(polyfill)
  • 垫片是浏览器备用模式,即如果浏览器不支持某个特定的功能,就提供自己的实现
// 如果浏览器不支持find方法(ES6新增)
// 以下是MDN提供的垫片
if (!Array.prototype.find) {    // 特征检测
    // 定义find的实现
    Array.prototype.find = function(predicate) {
        if (this == null) {
            throw new TypeError("find called on null or undefined");
        }
        if (typeof predicate !== "function") {
            throw new TypeError("predicate must be a function");
        }
        var list = Object(this);
        // 确保length是非负整数
        // >>> 是补零右移运算符,将第一个操作数向右移动指定的位数,丢弃多余的部分
        var length = list.length >>> 0;
        var thisArg = arguments[1];
        var value;

        // 查找数组中满足指定条件的第一个元素
        for (var i = 0; i < length; i++) {
            value = list[i];
            if (predicate.call(thisArg, value, i, list)) {
                return value;
            }
        }
        return undefined;
    }
}
  • 如果特征检测不通过可以:
  • 进一步检测,找出可以使用的JS提供一个简版体验
  • 完全不使用JS,回到不用脚本的HTML页面
  • 将用户重定向到一个普通版网站

3.3 不可测试的浏览器问题

  • 绑定事件处理器
  • 浏览器中,无法以编程的方式确定一个事件处理器是否被绑定,我们无法删除一个元素上绑定的所有事件处理器,除非我们维护所有创建的事件处理器的引用
  • 事件触发
  • 浏览器中,无法检测事件是否触发,可以确定浏览器是否支持事件绑定,但不可能知道浏览器是否会触发一个事件,在几个地方会引起问题:
  • 如果在页面加载完成后,会动态加载脚本,脚本试图将一个监听器绑定到等待窗口加载,而事实上,这个事件已经发生了,要执行的代码可能永远处于等待状态
  • 脚本希望使用自定义事件代替浏览器提供的事件,只有在实践中才能知道事件是否会被触发,事先无法确认
  • 浏览器崩溃
  • 在老版本的safari中使用Unicode字符创建正则表达式会造成浏览器崩溃
  • 不一致的API
  • API性能
  • 某些API在有的浏览器运行快,在有的浏览器上运行慢

4. 减少假设

  • 编写跨浏览器、可重用的代码是一场关于假设的战役,要通过合理的检测和程序编写,减少代码中的假设