最新版jQuery v3.3.1的BUG以及解决办法(什么问题不重要,怎么解决问题才重要)

发现问题

最新版的 FineUIPro v5.2.0 中,咱们将内置的 jQuery v1.12.4 升级到 jQuery v3.3.1 ,能够看升级记录javascript

+升级到jQuery v3.3.1。
    -jQuery v3.x支持的浏览器:Chrome,Edge,Firefox,Safari,IE9+。
    -增长类型JSLibrary枚举值JQv1,用来引入jQuery v1.x。
    -若是须要支持IE8,请在Web.config中增长配置项JSLibrary=JQv1。
    -IE8有限支持而且复杂页面可能会有性能问题,建议你们积极引导用户使用现代浏览器。css

之因此作这个升级,主要考虑以下因素:html

1. IE8的市场份额逐渐萎缩,能够考虑将IE8的支持放到次要位置java

2. 经过所有配置参数 JSLibrary=JQv1 引入老版本的 jQuery v1.12.4,这样老客户仍然能够支持IE8jquery

3. 默认使用 jQuery v3.3.1,紧跟jQuery版本意味着较好的性能和安全性,以及遇到问题可以及时解决git

4. 考虑到 jQuery 这么多年的发展,稳定性应该不是问题github

 

整个升级过程仍是很平缓的,没有大的改动。api

惟一让我头疼的时,好像表格中节点的位置总计算不对,好比在表格的单元格编辑时,若是表格没有滚动条,显示的编辑框是正确的:浏览器

可是,表格的滚动条一出现,编辑框就错位了:安全

而这个问题,在使用 jQuery v1.12.4 时是不存在的!

分析问题

通过一段时间的调试,最终肯定这是最新版 jQuery v3.3.1 的一个BUG,出现这个问题需知足以下条件:

1. 引用最新版 jQuery v3.3.1

2. 计算表格 tr 或者 td 的相对位置时出错,好比:$('table tr:eq(1) td(0)').position()

为了更直观的演示这个问题,我写了个例子,其中HTML代码块:

<div class="container">
  <table>
    <tr>
      <td>row-1</td>
    </tr>
    <tr>
      <td id="theTD">row-2</td>
    </tr>
  </table>
</div>
<div id="result">
</div>

CSS代码块:

.container {
  background-color: lightgreen;
  padding: 50px;
  position: relative;
}

table {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
  background-color: green;
}

table td {
  padding: 0;
  height: 50px;
  color: #fff;
  vertical-align: top;
}

为了更直观的描述问题,咱们用不一样的背景色标识外层的容器(.container)和内部的表格(table)。

JavaScript代码块:

$(function() {
    var tdPosition = $('#theTD').position();
    $('#result').html('top:' + tdPosition.top + ' left:' + tdPosition.left);
});

按照正常的思惟模式,上面的 theTD 的相对位置应该是相对 .container 的偏移量(由于 .container 是 td 的第一个遇到的相对定位的父元素)。 

由于 .container 设置了 50px 的内边距,表格每行的高度为 50px,因此 theTD 距离 .container 的垂直高度应该是 100px。

因此咱们指望的输出结果应该是:

top:100 left:50

 

可是结果然的如此吗?我建立了两个示例,分别是 引用 jQuery v1.9.1 的示例引用 jQuery v3.3.1 的示例,获得的结果以下所示:

 

可见,在 jQuery v3.3.1 中,获取表格中 td 节点的相对位置(position)获得的结果并非咱们想要的。那这个值究竟是什么?

top: 50 left:0

解决问题

通过认真分析,我认为这个值是 td 相对外部 table 节点的偏移量,而不是相对于第一个浮动父节点的位置(position: relative / absolute)。

曾经一度我对 position() 和 offset() 的确切含义产生了怀疑,难道是我理解错了?后来发现个人理解没有问题:

.offsetParent() is supposed to return the nearest positioned element, where "positioned" means it has a css position attribute of "relative", "absolute", or "fixed".

我在不少地方依赖于 position 的计算解决,难道是 jQuery 计算 offsetParent 节点时出了差错:

打开浏览器的调试窗口,我输入以下代码:

$('#theTD').offsetParent()

可见,经过 jQuery 获取到的依然是 .container, 这个方法返回的没问题。

没办法,既然出了问题,只好先在本身的代码中修正了。

第一次尝试

既然 td 的相对位置是相对于 table,那不如用 table 作个中转,计算出 table 的 position 加上去不就好了,以下所示:

$(function() {
    var tdPosition = $('#theTD').position();
   var tablePosition = $('#theTD').parents('table').position();
  
    $('#result').html('top:' + 
      (tdPosition.top + tablePosition.top) + ' left:' + 
      (tdPosition.left + tablePosition.left));
    
});

 

看着好像是正确的,后来测试发现遇到嵌套表格就不行了,以下示例:

<div class="container">
<table class="table-outer">
  <tr>
    <td>table1-row1</td>
  </tr>
  <tr>
      <td>
        <table>
          <tr>
            <td>row-1</td>
          </tr>
          <tr>
            <td id="theTD">row-2</td>
          </tr>
        </table>
    </td>
   </tr>
 </table>
</div>
<div id="result">
</div>
.container {
  background-color: lightgreen;
  padding: 50px;
  position: relative;
}

.table-outer {
  background-color: blue;
}
table {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
  background-color: green;
}

table td {
  padding: 0;
  height: 50px;
  color: #fff;
  vertical-align: top;
}

 

第二次尝试

一计不成,再生一计。既然 table 的 position 出问题,那么 div 的 position 应该是正常的吧。此次我就来在 td 里面动态建立一个 div 节点怎么样?

$(function() {
  var tmpDiv = $('<div style="position:relative;"/>').prependTo($('#theTD'));
  var tdPosition = tmpDiv.position();
  tmpDiv.remove();
  
    $('#result').html('top:' + tdPosition.top + ' left:' + tdPosition.left);
});

 

看着好像没有问题,能够问题依旧,当设置 td 的 vertical-align: middle 时,问题暴露出来了,由于此时 td 内的 div 是居中显示的:

 

第三次尝试

就在我束手无策时,我突然想到前面的 offsetParent() 函数,这个函数能获取正确的父节点。既然 position() 函数有问题,我何不尝试 offset() 函数。

1. jQuery.position(): 获取元素相对于父元素的偏移量(position: relative / absolute)

2. jQuery.offset(): 获取元素相对于视口的偏移量

$(function() {
    var tdOffset = $('#theTD').offset();
    var tdParentOffset = $('#theTD').offsetParent().offset();
    $('#result').html('top:' + (tdOffset.top  - tdParentOffset.top) + ' left:' + (tdOffset.left - tdParentOffset.left));
});

此次使用当前元素和相对父元素的 offset 相减,获得的结果是否咱们所须要的呢?

 

不错,正是咱们所须要的。由于这个逻辑判断很简单,和是否表格嵌套不要紧,因此测试下第一次尝试失败的状况:

 Bingo! 一切正常。此问题圆满解决!

真是的jQuery的BUG吗?

想一想就难以想象,一个被全球用户使用的公共JavaScript竟然有这样的BUG,难道就没人发现么?

网上搜索了一圈,看起来我多虑了,早就有用户提出这个问题:

https://github.com/jquery/api.jquery.com/issues/1081

 Per the spec, an offsetParent is defined as an element with a position that is non-static or is a table, th, or td element. Since 3.3.0, position started using the native offsetParent property which started respected table, th, and td as offset parents, but the .offsetParent() method was left unchanged. We'll fix this in an upcoming release, but we'll need to note the special behavior for those 3 elements in the docs.

这个是 jQuery 核心开发团队给出的答案,大意是说 offsetParent 这个节点属性指的是这样一个父节点:

1. 节点是非静态的,也就是拥有 position: relative / absolute / fixed 样式

2. 节点是 table, th 或者 td

哈,这么多年过去了,我竟然第一次据说 offsetParent 还有相对于 td,th,table这个说法,尽管这个td,th,table节点是 position:static,下面使用代码来验证一下:

$(function() {
    var tdPosition = $('#theTD').position();
  var offsetParent = $('#theTD').offsetParent()[0].tagName;
  var nativeOffsetParent = $('#theTD')[0].offsetParent.tagName;
    $('#result').html('top:' + tdPosition.top + ' left:' + tdPosition.left + 
           '<br>offsetParent():' + offsetParent + 
      '<br>Native offsetParent:' + nativeOffsetParent);
});

分别在不一样浏览器中查看结果。

Chrome:

Firefox:

 

Edge:

 

IE11(因为IE11打不开jsfiddle网站,咱们单首创建了一个页面):

甚至IE9也是这样的:

 

好吧,看来我真是孤陋而寡闻了。

 

jQuery 是在 v3.3.0 开始考虑到第二种状况的,可是 jQuery.offsetParent() 的定义没有改变。这种不一致性自己就是一个BUG。

而不和以前的 jQuery v1.x, v2.x 乃至于 v3.3.0 以前的版本兼容,对于一个普遍使用的公共库而言,更应该算是一个BUG,或者至少提供兼容之间的方法。

 

考虑另外一个更现实的问题:在实际应用中,咱们为何要获取 td ,table 的 position() 呢?

不是为了拿获取到的值去更新其余 td 或者 table 的 top/left 属性!

而是为了拿获取的值去更新其余相关 div 节点的 top/left 属性,而 div 节点的 position() 是相对于 position: relative / absolute / fixed 父节点的,而非 td ,table 节点,这就必定会出问题。

 

因此,咱们仍是认定这是一个BUG。

可是 jQuery 官方貌似没有修正这个问题的意思,而是可能在未来版本修改  jQuery.offsetParent() 的定义:

 

可是jQuery你这样作真的好么,都10多年了你都没遵照标准,忽然来个小版本号称支持标准,搞的和以前的代码不兼容,你让以前的代码情何以堪!

无论怎样,咱们有了本身的解决办法。

小结

这篇文章描述了咱们 FineUIPro 产品 更新中遇到的一个问题,最终将问题定位到 jQuery.position() 函数,虽然jQuery的作法是依照HTML规范来的,可是 jQuery.offsetParent() 和 jQuery.position() 两个函数有冲突,而且会致使以前的jQuery插件出错,应该算是一个BUG吧。

好在本文给出了一个补救的方法,若是这里的方法可以对你有所启发或者帮助,就给个推荐呗。

相关文章
相关标签/搜索