Jquery中文网 www.dm48.cn
Jquery中文网 >  数据库  >  正文 细说浏览器特性检测(1)

细说浏览器特性检测(1)

发布时间:2017-12-13 01:37:42   编辑:www.dm48.cn
浏览器 结果 IE6 on IE7 on IE8 on IE9 Beta on Firefox 3.6 on Chrome 7 [空字符串] Safari 5 on

经测试,除Chrome外,所有浏览器都会给没有valueinput[type="checkbox"]一个默认的valueon

该特性被jQuery用来获取input[type="checkbox"]input[type="radio"]的值,兼容的判断语句如下:

// 不支持checkOn的浏览器都不存在property/attribute混用问题,因此需要明确使用getAttribute return support.checkOn ?       element.value :     (element.getAttribute('value') === null ? 'on' : element.value); 

optSelected

使用以下代码可以检测该特性:

<select id="optSelected">   </select>   <script type="text/javascript">       var select = document.getElementById('optSelected');     var option = document.createElement('option');     select.appendChild(option);     console.log(option.selected); </script>   

以下为各浏览器中运行结果:

浏览器 结果 IE6 false IE7 false IE8 false IE9 Beta false Firefox 3.6 true Chrome 7 true Safari 5 false

经测试,IE系列和Safari使用appendChild对空的<select>元素添加一个<option>后,该<option>selected属性不会被默认设置为true

该问题引起的BUG描述如下:

部分浏览器在获取option的selected属性时,会错误地返回false。

该问题的解决方案是在访问selected属性时,先访问其父级<select>元素的selectedIndex属性,强迫浏览器计算<option>selected属性,以得到正确的值。需要注意的是<option>元素的父元素不一定是<select>,也有可能是<optgroup>。具体代码如下:

if (!support.optSelected) {       var parent = option.parentNode;     parent.selectedIndex;     //处理optgroup时的情况     if (parent.parentNode) {         parent.parentNode.selectedIndex;     } } return option.selected;   

optDisabled

使用以下代码可以检测该特性:

<select id="optDisabled" disabled="disabled">       <option></option> </select>   <script type="text/javascript">       var select = document.getElementById('optDisabled');     var option = select.getElementsByTagName('option')[0];     console.log(option.disabled); </script>   

以下为各浏览器中运行结果:

浏览器 结果 IE6 false IE7 false IE8 false IE9 Beta false Firefox 3.6 false Chrome 7 false Safari 5 true

经测试,Safari会将设置了disabled<select>中的<option>也同样设置上disabled

这个特性用来获取<select>元素的value值,特别是当<select>渲染为多选框时,需要注意从中去除disabled<option>元素,但在Safari中,获取被设置为disabled<select>的值时,由于所有<option>元素都被设置为disabled,会导致无法获取值。

因此有optDisabledtrue表示<option>不会被自动设置disabled)后,可以有这样的代码:

// 如果optDisabled为true,则disabled属性返回的是option的真实状态 // 否则判断disabled属性是否为null var disabled = support.optDisabled ?       option.disabled : option.getAttribute('disabled') !== null; if (!disabled) {       return option.value; } 

checkClone

使用以下代码可以检测该特性:

<div id="checkClone">       <input type="radio" name="checkClone" checked="checked" /> </div>   <script type="text/javascript">       var fragment = document.createDocumentFragment();     var div = document.getElementById('checkClone');     var radio = div.getElementsByTagName('input')[0];     fragment.appendChild( radio );     console.log(fragment.cloneNode(true).cloneNode(true).lastChild.checked); </script>   

需要注意的是,重现这个问题,需要给<input>显式地指定一个name属性,并且在复制fragment对象时连续调用2次cloneNode函数。

以下为各浏览器中运行结果:

浏览器 结果 IE6 true IE7 true IE8 true IE9 Beta true Firefox 3.6 true Chrome 7 true Safari 5 true Safari 4 false

由结果可以看出,该问题出现在Safari 4中,并且已经在Safari 5得到修复,介于Safari在市场中的占有率以及版本较老的原因,这个问题确实不需要太多的重视。

这个特性的使用场合极少,在开发中几乎不会有如此严格的环境(对DocumentFragment连续调用2次cloneNode),在jQuery中,该特性用做buildFragment这个内部函数中的缓存功能,jQuery会对比较简单的创建DOM元素的字符串的创建结果缓存到DocumentFragment中,但当遇到创建input[type="radio"]时,如果cloneNodefalse,则强制不进行缓存。

inlineBlockNeedsLayout

这是一个历史久远的问题,IE7以下版本并不支持display: inline-block;样式,而是使用display: inline;并通过其他样式触发其hasLayout形成一种伪inline-block的状态(具体请点击这里)。

inline-blockinline的一个重要区别在于,inline-block的元素可以显式地设置宽和高,因此可以用以下代码检测该特性:

<div id="inlineBlockNeedsLayout"        style="width: 1px; padding-left: 1px; display: inline; zoom: 1;"> </div>   <script type="text/javascript">       var div = document.getElementById('inlineBlockNeedsLayout');     console.log(div.offsetWidth); </script>   

以下为各浏览器中运行结果:

浏览器 结果 IE6 2 IE7 2 IE8 1 IE9 Beta 1 Firefox 3.6 1 Chrome 7 0 Safari 5 0

对于inline元素,width样式是无效的,在该测试中,webkit系浏览器均获取了0,IE8以上版本及Firefox获取了1,只有IE7及以下版本同时计算了widthpadding-left,得到了2px的宽度。

这个功能可以用于设置元素的css样式,当需要设置为inline-block时,针对IE7及以下浏览器可以同时设置display: inline;zoom: 1;来模拟效果,核心代码如下:

if (name == 'display' && value == 'inline-block') {       if (support.inlineBlockNeedsLayout) {         element.style.display = 'inline';         element.style.zoom = 1;     }     else {         element.style.display = value;     } } 

当然这样直接这样使用肯定是有问题的,当需要获取display样式的时候怎么办呢?同时判断zoomdisplay吗?并且hasLayout会引起一些其他的问题。

因此,jQuery只将该特性用于动画效果,当需要对widthheight进行动画,并且元素是inline时,首先设置为(伪)inline-block状态,动画结束后将相关样式恢复。

shrinkWrapBlocks

这个问题的详细解释可以参考此处,使用以下代码可以检测该特性:

<div id="shrinkWrapBlocks" style="width: 1px; zoom: 1;">       <div style="width: 4px;">     </div> </div>   <script type="text/javascript">       var div = document.getElementById('shrinkWrapBlocks');     var inner = div.getElementsByTagName('div')[0];     console.log(div.offsetWidth); </script>   

以下为各浏览器中运行结果:

浏览器 结果 IE6 4 IE7 1 IE8 1 IE9 Beta 1 Firefox 3.6 1 Chrome 7 1 Safari 5 1

测试结果表明,IE6即使显式设定了宽度,在触发了hasLayout的情况下,其大小会受子元素的影响而被撑大。

jQuery将该特性用于动画效果,为了动画过程中改变一个元素的width/height时,其子元素不会溢出,jQuery做了以下几步:

  1. 保存元素当前的overflowoverflow-xoverflow-y三个样式。
  2. 将元素设置为inline-block以便修改width/height值。
  3. 将元素的overflow设为hidden,防止子元素溢出或当前元素被子元素撑开(IE6)。
  4. 在动画结束后,确保元素不会被子元素撑开(shrinkWrapBlockstrue)的情况下,才恢复overflow样式。

reliableHiddenOffsets

这个问题在上两天工作中遇到,刚好jQuery1.4.3升级了这方面的内容,使用以下代码可以检测该特性:

<table id="reliableHiddenOffsets">       <tbody>         <tr>             <td style="display: none;">             </td>             <td>                 abcd             </td>         </tr>     </tbody> </table>   <script type="text/javascript">       var table = document.getElementById('reliableHiddenOffsets');     var td = table.getElementsByTagName('td')[0];     console.log(td.offsetHeight); </script>   

以下为各浏览器中运行结果:

浏览器 结果 IE6 0 IE7 0 IE8 21 IE9 Beta 0 Firefox 3.6 0 Chrome 7 0 Safari 5 0

只有IE8存在这个问题,那当<td>元素的displaynone时,其高度依旧会受其所在行的高度的影响,而不是0。

这个问题的存在根本上导致了对元素可见性的判定出现差错,原本判断一个元素是否隐藏的代码是这样的:

function isHidden(element) {       return element.offsetWidth == 0 || element.offsetHeight == 0; } 

因为这个BUG的出现,上面的函数对于td元素失去了效果,因此需要改进为:

function isVisible(element) {       return (element.offsetWidth == 0 && element.offsetHeight == 0) ||         (!support.reliableHiddenOffsets && getStyle(element, 'display') == 'none'); } 

阅读jQuery源码的时候,会发现这一段的判断里多了一句element.style.display,这一句是用来判断元素有display值才去取来看看是不是none的,以免获取运行时样式的开销。

结语

  • 特性检测确实很有用,有时比浏览器版本嗅探更佳可靠,但检测某些特性相当麻烦,不是必要的时候不如用浏览器嗅探。
  • jQuery对特性的命名真让人想砍了他们团队。
  • 有些特性可以重现的浏览器版本之低令人惊讶,在多数项目中完全可以不考虑,如checkClone。jQuery本身为了兼容做了太多的假设,个人认为有一些完全可以抛弃,比如以后会说的getBoundingClientRect问题。
  • 另外还有2个关于事件上的特性检测,由于事件的特性检测是一个通用的话题,会有今后专门写文讲述,因此就不在本文中赘述了。
  • jQuery每一个小版本的改进都很大,特别在细节方面,这些都是要通过阅读源码不断发掘的,前端的世界就是这么多变(叹)。

浏览器特性检测即通过探测对象是否拥有某个属性或者函数,或者通过其他的编码探测方式,来决定其是否支持某一功能、特性。其最经典的运用莫过于通用的addEvent函数:

function addEvent(element, type, handler) {       if (element.attachEvent) { //IE8及以下浏览器         element.attachEvent('on' + type, handler);     }     else { //W3C标准浏览器         element.addEventListener(type, handler, false);     } } 

函数可以通过检测attachEvent函数是否存在,以决定使用attachEvent或者addEventListener,这也是最简单的一种特性检测,因而通常在需要时才进行实时的检测。另一种特性检测由于检测的过程较为麻烦,因此会预先完成检测,将检测的结果(通常是boolean类型)保存在某个变量中。

本文的主要目标是分析、说明在jquery1.4中浏览器特性检测新增的内容,同时加深浏览器兼容性方面几个细节的记忆。

jQuery1.4主要增加了以下几个浏览器特性标识,本文针对它们一一进行分析:

  • checkOn:1.4版本引入,决定没有设置value值的input[type="checkbox"]是否有默认的valueon
  • optSelected:1.4.3版本引入,决定<select>元素的第一个<option>元素是否会默认被选中。
  • optDisabled:1.4.3版本引入,决定当<select>元素设置为disabled后,其所有<option>子元素是否也会被设置为disabled
  • checkClone:1.4.1版本引入,决定对DocumentFragment使用cloneNode函数时是否会将input[type="radio"]input[type="checkbox"]checked属性保留。
  • inlineBlockNeedsLayout:1.4.3版本引入,决定在IE下一个block元素拥有hasLayout属性并有display: inline;时,是否会按inline-block显示。
  • shrinkWrapBlocks:1.4.3版本引入,决定在IE下一个元素拥有hasLayout属性和固定的width/height时,是否不会被子元素撑大。
  • reliableHiddenOffsets:1.4.3版本引入,决定一个td或th元素设置为display: none;时,是否还有offsetHeight

checkOn

使用以下代码可以检测该特性:

<input id="checkOn" type="checkbox" />   <script type="text/javascript">       console.log(document.getElementById('checkOn').value); </script>   

以下为各浏览器中运行结果: