与脚本相关的兼容性问题

内容如题


一、JavaScript 核心

  • 1、在 ECMAScript 中,变量名、对象的属性或方法名都是标识符,标识符可以包含英文字母、数字、美元符号 ‘$’ 和下划线 ‘_’,但不能以数字开头,不能是保留字。换句话说,’+’,’.’ 等符号是不能出现在标识符中的。关于标识符的更多信息,请参考 ECMAScript 规范 7.6 Identifier Names and Identifiers 的内容。

    解决办法:避免使用 IE 的这些“特性”,以保证兼容所有浏览器。

  • 2、Firefox 的 TraceMonkey 引擎对函数声明的处理与 ECMAScript 规范的要求不符,TraceMonkey 将块中的函数声明作为“函数语句”来处理。而其他浏览器的引擎仍将这类块中的函数声明当作该块之外的函数声明来解析。

    解决办法:将条件语句中的函数声明替换为函数表达式,如:

function foo(){
  if(window===parent){
    var bar=function(){alert(1);}
  }
  else{
    var bar=function(){alert(2);}
  }
  bar();
}
foo();
  • 3、改变 Date、Array 或 String 等对象的原型对象中的属性或方法的引用后,再用 for in 遍历一个 Date、Array 或 String 对象时,在 Safari Chrome 中可以遍历出这个修改过的属性或方法名。

    解决办法:对于数组,避免用 for…in 方式而采用索引即数字下标的形式枚举数组成员。对于 Date String 以及其他对象,没有必要使用 for…in 来枚举它们的成员,因此一般不会有兼容性问题。

  • 4、Date 对象的 toLocaleString 方法在各浏览器下的返回值存在格式上的差异。

    解决办法:要获得相同格式的时间字符串,请不要使用 Date.prototype.toLocaleString() 方法,可以通过分别使用 getFullYear、getMonth、getDate 和 getDay 分别获得各关键字符串并拼装

  • 5、在 IE6 IE7 IE8 中,JavaScript 代码中语句块结束符号“}”之后的第一个分号“;”会被忽略。

    解决办法:按照规范书写正确的代码。

  • 6、JSON 字符串或对象直接量的最后一个键值对后加 ‘,’ 在 IE6 IE7 IE8(Q) 浏览器中会导致脚本中断。

    解决办法:即便规范没有强调最后一个键值对的后边不能出现 ‘,’,也要确保最后一个键值对之后没有多余的 ‘,’,以兼容各浏览器。

  • 7、当数组直接量以 ‘,’ 结尾时,IE 和非 IE 得到的数组对象的长度不一致。IE 会以 ECMAScript 规范第三版中规定的方式解析该数组。即该数组的长度将比其他浏览器多 1。

    解决办法:数组直接量的最后不要出现 ‘,’,以保证兼容各浏览器。

  • 8、IE 中允许变量名中出现 ‘,’ 等中文标点符号,而其他浏览器则不允许。

    解决办法:避免在变量名(即标识符)中出现中文标点,以保证兼容各浏览器。

  • 9、在一个元素的属性中绑定事件,实际上就创建了一个内联事件处理函数(如<h1 onclick=”alert(this);”…>…),内联事件处理函数有其特殊的作用域链,并且各浏览器的实现细节也有差异。

    解决办法:

    • 尽量不要使用内联事件处理函数,使用 DOM 标准的事件注册方式为该元素注册事件处理函数
    • 必须使用内联事件处理函数时,要保证该函数内试图访问的变量是位于全局作用域内的,而不会因该函数独特的作用域链而引用到非预期的对象。最简单的办法是使用前缀,如 ‘my_onsearch’。
  • 10、Date 对象的 getYear 方法在各浏览器下的返回值存在差异。

    解决办法:要获得一个具体时间的年份,请不要使用 Date.prototype.getYear() 方法,使用 Date.prototype.getFullYear() 代替,以在各浏览器下获得相同的表现。另外,要给一个 Date 对象设置年份,也请不要使用 Date.prototype.setYear() 方法,使用 Date.prototype.setFullYear() 代替。

  • 11、Chrome Opera 中使用 for-in 语句遍历对象的属性时,遍历出的属性顺序与对象定义时不同。

    解决办法:for-in 语句无法保证遍历顺序,应尽量避免编写依赖对象属性顺序的代码。如果想顺序遍历一组数据,请使用数组并使用 for 语句遍历。 如果想按照定义的次序遍历对象属性,请参考本文针对各浏览器编写特殊代码。

  • 12、IE6 IE7 IE8(Q) 不支持 JSON 对象。

    解决办法:可以使用 window.eval() 或 new Function(){} 的方式解析 JSON 格式字符串

<script type="text/javascript">
  window.onload = function(){
    var info = document.getElementById("info"),
      jsonStr = '{"name": "w3help", "url": "www.w3help.org", "tech": ["js", "HTML", "CSS", 5, 4.01, 2.1],'
          + '"online": true, "category": {"RCA": "Root Cause Artical", "KB":"Knowledge Base"},'
          + '"version": 1}', p, w3help;

    //使用两种简单的方式解析 JSON 格式字符串
    json1 = eval("(" + jsonStr + ")"),
    json2 = (new Function("return " + jsonStr))();

    for(p in json1)
      info.innerHTML += p + " : " + json1[p] + "<br/>";
    info.innerHTML += "-----------------------<br />";
    for(p in json2)
      info.innerHTML += p + " : " + json2[p] + "<br/>";
  }
</script>
<div id="info" style="width:350px;"></div>
  • 13、ECMAScript 中并没有明确说明 comparefn 返回值是布尔型时应该如何处理,它仅仅描述 comparefn 调用的返回值应当是 -1、0、1 这三种情况之一。comparefn 的返回值规范实现约束是 “引擎开发者” 还是 “脚本使用者” 并没有明确表述.这导致不同引擎对于 comparefn 返回值为非 -1、0、1 范围时具体处理不一致,从而使排序结果非预期。

    解决办法:调用 Array.prototype.sort 函数并需要依赖 comparefn 处理排序结果时,应遵循规将 comparefn 函数返回值约束在 -1、0、1 范围内。


二、文本对象模型

  • 1、在 IE6 IE7 IE8(Q) 中,支持以 document.getElementById(elementName) 的方式获取 name 属性值为 elementName 的 A APPLET BUTTON FORM IFRAME IMG INPUT MAP META OBJECT EMBED SELECT TEXTAREA 元素。

    解决办法:在使用 document.getElementById 方法获取页面元素时,应传入元素的 id 属性值,而不能使用元素的 name 属性值。同时需注意页面中的元素的 id 属性值不能与其他元素的 name 属性值有重复。

  • 2、使用 document.getElementById 方法获取页面内的元素时,在 IE6 IE7 IE8(Q) 中的 id 是大小写不敏感的。

    解决办法:在使用 document.getElementById 获取页面元素时,应保证作为参数的 id 与目标元素的实际 id 值完全一致。

  • 3、IE 在创建 DOM 树时,会忽略某些空白字符,因此会比其他浏览器少创建一些文本节点。反过来说,同样的一篇文档,其他浏览器将比 IE 多创建一些文本节点。

    解决办法:

    • 没有必要时尽量去掉各标签之间的空白字符。
    • 在获取节点时做类型判断。
  • 4、各浏览器中的 NodeList 接口存的实现有差异。

    解决办法:要从 NodeList 中获取元素,请使用 NodeList[index]、NodeList[name] 或 NodeList.item(index),以保证兼容各浏览器。

  • 5、在非 IE 浏览器中可能导致脚本异常,停止解析。

    解决办法:使用方式 1 达到简写一些 DOM 和 BOM 对象原生方法的目的。

//方式1
var println1 = function(str){document.writeln(str);};
  • 6、IE6 IE7 IE8(Q) 混淆了 DOM 对象的属性(property)及 HTML 标签属性(attribute)这两个概念。其对于 getAttribute 及 setAttribute 方法的实现与 HTML DOM 对象的属性的 getter 与 setter 操作等价,这个错误的实现方式导致了一系列的兼容性问题。而在 IE8(S) 中,导致的大多数兼容性问题已不存在,但是仍然可以通过 “Element.PropertyName” 访问到这个 HTML 元素的自定义属性。

    解决办法:

    • 避免使用 “Element.setAttribute(“style”, “XXX”)” 在所有浏览器中设置元素的 style 属性,可以改用符合规范的 “Element.style.cssText = “XXX””;
    • 避免使用 “Element.setAttribute(“class”, “XXX”)” 在所有浏览器中设置元素的 class 属性,可以改用符合规范的 “Element.className = “XXX””;
    • 避免使用诸如 “Element.setAttribute(“onclick”, “alert(‘ok’)”)” 为元素绑定事件,可以使用符合规范的 Element.onclick = function () { [code] } 或 IE 的 attachEvent 方法等;
      避免使用诸如 “Element.getAttibute(“innerHTML”)” 的方式获取 innerHTML 属性值;
    • 针对表单元素,使用 Element.value 获取控件的 “当前值”,而不要用 getAttribute(“value”) 获取;
    • 针对自定义的 HTML 属性,一律使用 getAttribute 方法获取属性值。
  • 7、各浏览器对 DOMImplementation 接口的支持程度不同。

    解决办法:避免使用各浏览器支持程度不同的 DOMImplementation 的接口

  • 8、DocumnetLS 是 DOM3 中一个过时的接口。

    解决办法:由于 W3C 推荐的标准中,已经放弃了 DocumentLS 接口,为更好的兼容各浏览器,建议采用 XMLHttpRequest 方式载入xml文件,示例代码如下:

var xhr = window.XMLHttpRequest ?
                  new window.XMLHttpRequest():
                  new window.ActiveXObject("Microsoft.XMLHTTP")
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        var xmlDoc = xhr.responseXML
        // do something what you want ...
    }
};
xhr.open('GET', 'test.xml', true);
xhr.send();
  • 9、Firefox Opera 不支持 “document.styleSheets” 通过 STYLE 元素 id 获取 CSSStyleSheet 对象,仅支持整数作为下标获取。

    解决办法:避免使用 “document.styleSheets” 通过 STYLE 元素 id 获取 CSSStyleSheet 对象,使用 W3C 规范中的整数下标方式获取。

  • 10、在 IE6 IE7 IE8 中,createElement 方法不仅可以通过合法的标签名创建节点对象,还可以通过传入一段合法的 HTML 代码字符串作为参数创建节点对象。

    解决办法:对于一般的非替换元素,在各浏览器中均使用 W3C 规范中的标准的为 createElement 方法传入标签名的做法。
    对于一些 IE 处理有问题的替换元素,则注意判断浏览器,针对 IE 使用其特有的通过为 createElement 传入一段合法的 HTML 代码字符串作为参数的方法,非 IE 浏览器仍然使用 W3C 规范的标准方法。

  • 11、各浏览器对元素绑定、解绑事件监听器的方法,事件对象的获取,以及 Event 对象的实现上存在差异。

    解决办法:

    • 使用特性判断创建无兼容性问题的事件监听器绑定和解绑函数
    • 使用特性判断获得有效的事件对象
    • 使用特性判断使用与标准对应的非标准方法及属性
  • 12、使用 document.getElementsByName 方法获取页面内的元素时,在 IE6 IE7 IE8 中的 name 是大小写不敏感的。

    解决办法:在使用 document.getElementsByName 方法获取页面元素时,应保证作为参数的 name 与目标元素的实际 name 值完全一致。

  • 13、各浏览器 document、document.body、document.documentElement 对象的 onscroll 事件的支持存在差异。

    解决办法:在给整个浏览器窗口绑定滚动事件 (scroll) 的时候,绑定到 window 对象上。

  • 14、IE 标准模式下 BODY 元素的高度发生变化时就会触发 window.onresize 事件。

    解决办法:

    • 不期望触发 window.onresize 事件时:
    • 期望触发 window.onresize 事件时:
  • 16、使用这两个方法将在 Firefox 浏览器内报错。

    解决办法:在 Firefox 中,可通过扩展 HTMLElement 的原型 (prototype) 来实现这两个方法

  • 17、Firefox 不支持 DOM 对象的 outerHTML innerText outerText 属性。

    解决办法:在 Firefox 中,可通过扩展 HTMLElement 的原型 (prototype) 来实现相关属性。

  • 18、在 IE6 IE7 IE8(Q) 中,一个 ‘position’ 特性值为 ‘static’ 的元素的 offsetParent 可能会是其最近的、触发了 hasLayout 的父元素。
    同时其 offsetTop、offsetLeft 的返回值参照元素也是距离其最近的触发了 hasLayout 的祖先级元素。

    解决办法:可考虑对于为在 IE 中触发了 hasLayout 特性的元素设置 ‘position’ 特性值为非 ‘static’ 值,如 ‘relative’,或避免使用元素的 offsetTop、offsetLeft、offsetParent 属性。

  • 19、插入空白页面 IFRAME 元素时 Chrome Safari Opera 浏览器中会触发 load 事件。

    解决办法:为 IFRAME 标签的 src 属性指定具体 URL 后再将节点插入 DOM 树中 。

  • 20、IE 和 Firefox 提供了对 window.onerror 事件的支持,当页面内的 JavaScript 脚本出现错误时,window.onerror 被触发。

    解决办法:放弃使用 window.onerror,通过合理使用 try-catch 来达到近似的效果。

  • 21、各浏览器中,原生可以触发 onfocus 事件以及通过其 focus() 方法获得焦点的元素不相同,而在元素设置了 tabindex 属性后焦点获取情况也不相同。
    在 IE 中,DIV 和 SPAN 元素若在当前的 IE 中触发了 hasLayout 则也可以触发 onfocus 事件以及通过其 focus() 方法获得焦点。

    解决办法:对于一般常见的可视元素,若需要元素可触发 onfocus 事件以及通过其 focus() 方法获得焦点,则应为其设置 tabindex 属性。

  • 22、页面加载完成后会触发 onload 事件,通常下会使用 window.onload 、 document.body.onload、 HTMLIFrame.onload 方法来处理他;但是各浏览器对页面 onload 事件处理方式并不一致,这些方法可能会导致页面加载完成后无法触事件处理函数。

    解决办法:

    • 统一为 window 对象的 onload 事件绑定函数,避免在 Firefox 中产生 document.body.onload 事件理解歧义。
    • 统一使用 DOM 规范的事件监听方法(或 IE 专有事件绑定方法)为 IFRAME 标记绑定 onload 事件处理函数。
  • 23、各浏览器计算不包含在普通流中的内容元素的 scrollHeight 值时结果有差异。

    解决办法:确保读取 scrollHeight 属性的元素均创建了新的 block formatting context,或者此容器与内部子容器处于同一文档流中,以此避免各浏览器中读数不同。

  • 24、在 IE6 IE7 IE8(Q) 中,无法通过脚本检测到 DOM 接口原型,故无法获得其接口继承关系。
    在 IE8(S) 中,只能检测到部分 DOM 接口原型,但这些原型不可枚举,故无法获得其接口继承关系。
    在 Firefox 中,部分 DOM 接口原型不可枚举,接口继承关系与 DOM 规范中的描述不符。
    在 Chrome Safari Opera 中,DOM 接口继承关系遵照了 DOM 规范中的描述。

    解决办法:由于各浏览器中 DOM 接口实现的不一致性,这里建议应尽量避免修改浏览器脚本引擎内 DOM 接口的原生方法。

  • 25、IE6 IE7 IE8 Opera 支持除 INPUT 和 BUTTON 元素以外的其他元素的 “click” 方法,这使得各浏览器对除 INPUT 和 BUTTON 元素以外的其他元素的 “click” 的支持情况存在差异。

    解决办法:建议尽量避免对除 INPUT 和 BUTTON 元素以外的其他元素通过 “click” 方法模拟鼠标点击事件。

  • 26、当 document 从 window 中移除,将触发 onunload 事件,各浏览器对 onunload 事件的支持与触发条件实现有差异。

    解决办法:各浏览器的支持以及事件触发条件差异较多,需谨慎使用。

  • 27、在用户通过鼠标操作触发 click 事件时,基本的事件触发流程为:MouseDown 事件 –> MouseUp 事件 –> Click 事件。
    如果用户点击的元素可以获得焦点,并且当前还没有获得焦点时,会在 MouseUp 事件前先触发 Focus 事件,再依次触发其后事件。
    此时就会出现问题,如果被点击的元素无法通过点击操作获得焦点,则 Focus 事件不会被触发,他的事件函数也不会因点击操作而运行,这就有可能导致功作者设计的功能无法按预期效果执行。

    解决办法:

    • 只在 INPUT[type=text] INPUT[type=password] SELECT TEXTAREA 元素中,使用 Focus 事件替代 click 事件触发相关业务逻辑处理程序。
    • 其他标记中的 Focus 事件无法使用鼠标指针的 click 操作触发,仅能通过 TAB 键切到可触发 Focus 事件的元素中才会生效。
  • 28、typeof 运算符存在兼容性问题

    解决办法:由于以上几点的各浏览器实现差异,我们建议用户在充分了解 typeof 运算符含义时再使用。
    下面的代码封装了名为 realtypeof 的方法,用来消除已知的各浏览器之间原生 typeof 运算符差异,仅作参考:

function realtypeof(source){
  return (source === undefined)
    ? "undefined"
    : ("object" === typeof source)
      ? (/function/i.test( source + "" ))
        ? "function"
        : "object"
      : (source.constructor == RegExp || !(source.constructor instanceof Function))
        ? "object"
        : typeof source;
}
  • 29、各浏览器下在向文档树中插入通过 cloneNode(true) 创建的节点时,其内的 SCRIPT 元素中的脚本在 Chrome 和 Safari 中会执行,在 IE Firefox Opera 中没有执行。

    解决办法:避免深度复制 “cloneNode(true)” 包含 SCRIPT 元素的节点。

  • 30、各浏览器对标准的支持有差异,并且他们实现了非标准的添加和删除 OPTION 元素的方法,在使用这些方法时,可能造成兼容性问题。

    解决办法:

    1. 在添加 OPTION 元素时
    • 如果需要向指定索引前插入 OPTION,可以使用 options.add(option, index);
    • 如果需要向 SELECT 尾部添加 OPTION,可以使用 options.add(option);
    • 如果需要向指定索引处添加(或更改) OPTION,可以使用 options[index] = option。
    1. 在删除 OPTION 元素时
    • 如果想删除指定索引处的 OPTION 元素,可以使用 select.remove(index) 或 options[index] = null;
    • 如果想删除某个指定的 OPTION 元素,可以使用 select.remove(option);
    • 如果想删除 SELECT 中所有 OPTION,可以使用 select.length = 0 或 options.length = 0。
  • 31、各浏览器对 Range 接口的实现存在差异。IE6 IE7 IE8 实现了独有的类似 Range 的 TextRange 对象,该对象拥有一些与标准 Range 接口中类似的属性及方法, 并且在创建 TextRange 时也与标准存在差异;而 Firefox Chrome Safari Opera 除了实现标准的 Range 接口外,还在此基础上扩展了一些属性及方法。 在创建 Range 时,也可以使用各自实现的 Selection 对象的 getRangeAt() 方法。

    解决办法:通过浏览器特性检测针对 IE6 IE7 IE8 正确创建 TextRange,对其它浏览器创建 Range。虽然 IE6 IE7 IE8 没有实现 Range 接口,但 TextRange 对象中也提供了许多能够实现类似功能的属性和方法。

  • 32、IE6 IE7 IE8 IE9(Q) 中 change、select、submit、reset 事件均不产生事件冒泡。

    解决办法:为了兼容低版本的 IE 浏览器,建议 change、select、submit、reset 事件均不要依赖事件冒泡机制委托给其祖先元素处理。