目录
- 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. 减少假设
- 编写跨浏览器、可重用的代码是一场关于假设的战役,要通过合理的检测和程序编写,减少代码中的假设