jQuery?v3.3.1的BUG以及解決辦法(附解決方案)
發(fā)現(xiàn)問題
最新版的 FineUIPro v5.2.0 中,我們將內(nèi)置的 jQuery v1.12.4 升級到 jQuery v3.3.1 ,可以看升級記錄:
+升級到j(luò)Query v3.3.1。
-jQuery v3.x支持的瀏覽器:Chrome,Edge,F(xiàn)irefox,Safari,IE9+。
-增加類型JSLibrary枚舉值JQv1,用來引入jQuery v1.x。
-如果需要支持IE8,請在Web.config中增加配置項JSLibrary=JQv1。
-IE8有限支持并且復(fù)雜頁面可能會有性能問題,建議大家積極引導(dǎo)用戶使用現(xiàn)代瀏覽器。
之所以做這個升級,主要考慮如下因素:
1. IE8的市場份額逐漸萎縮,可以考慮將IE8的支持放到次要位置
2. 通過全部配置參數(shù) JSLibrary=JQv1 引入老版本的 jQuery v1.12.4,這樣老客戶仍然可以支持IE8
3. 默認使用 jQuery v3.3.1,緊跟jQuery版本意味著較好的性能和安全性,以及遇到問題能夠及時解決
4. 考慮到 jQuery 這么多年的發(fā)展,穩(wěn)定性應(yīng)該不是問題
整個升級過程還是很平緩的,沒有大的改動。
唯一讓我頭疼的時,好像表格中節(jié)點的位置總計算不對,比如在表格的單元格編輯時,如果表格沒有滾動條,顯示的編輯框是正確的:
但是,表格的滾動條一出現(xiàn),編輯框就錯位了:
而這個問題,在使用 jQuery v1.12.4 時是不存在的!
分析問題
經(jīng)過一段時間的調(diào)試,最終確定這是最新版 jQuery v3.3.1 的一個BUG,出現(xiàn)這個問題需滿足如下條件:
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)和內(nèi)部的表格(table)。
JavaScript代碼塊:
$(function() { var tdPosition = $('#theTD').position(); $('#result').html('top:' + tdPosition.top + ' left:' + tdPosition.left); });
按照正常的思維模式,上面的 theTD 的相對位置應(yīng)該是相對 .container 的偏移量(因為 .container 是 td 的第一個遇到的相對定位的父元素)。
因為 .container 設(shè)置了 50px 的內(nèi)邊距,表格每行的高度為 50px,所以 theTD 距離 .container 的垂直高度應(yīng)該是 100px。
所以我們期望的輸出結(jié)果應(yīng)該是:
top:100 left:50
但是結(jié)果真的如此嗎?我創(chuàng)建了兩個示例,分別是 引用 jQuery v1.9.1 的示例 和 引用 jQuery v3.3.1 的示例,得到的結(jié)果如下所示:
可見,在 jQuery v3.3.1 中,獲取表格中 td 節(jié)點的相對位置(position)得到的結(jié)果并不是我們想要的。那這個值到底是什么?
top: 50 left:0
解決問題
經(jīng)過認真分析,我認為這個值是 td 相對外部 table 節(jié)點的偏移量,而不是相對于第一個浮動父節(jié)點的位置(position: relative / absolute)。
曾經(jīng)一度我對 position() 和 offset() 的確切含義產(chǎn)生了懷疑,難道是我理解錯了?后來發(fā)現(xiàn)我的理解沒有問題:
.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 節(jié)點時出了差錯:
打開瀏覽器的調(diào)試窗口,我輸入如下代碼:
$('#theTD').offsetParent()
可見,通過 jQuery 獲取到的依然是 .container, 這個方法返回的沒問題。
沒辦法,既然出了問題,只好先在自己的代碼中修正了。
第一次嘗試
既然 td 的相對位置是相對于 table,那不如用 table 做個中轉(zhuǎn),計算出 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)); });
看著好像是正確的,后來測試發(fā)現(xiàn)遇到嵌套表格就不行了,如下示例:
<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 應(yīng)該是正常的吧。這次我就來在 td 里面動態(tài)創(chuàng)建一個 div 節(jié)點怎么樣?
$(function() { var tmpDiv = $('<div style="position:relative;"/>').prependTo($('#theTD')); var tdPosition = tmpDiv.position(); tmpDiv.remove(); $('#result').html('top:' + tdPosition.top + ' left:' + tdPosition.left); });
看著好像沒有問題,可以問題依舊,當設(shè)置 td 的 vertical-align: middle 時,問題暴露出來了,因為此時 td 內(nèi)的 div 是居中顯示的:
第三次嘗試
就在我一籌莫展時,我忽然想到前面的 offsetParent() 函數(shù),這個函數(shù)能獲取正確的父節(jié)點。既然 position() 函數(shù)有問題,我何不嘗試 offset() 函數(shù)。
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 相減,得到的結(jié)果是否我們所需要的呢?
不錯,正是我們所需要的。因為這個邏輯判斷很簡單,和是否表格嵌套沒關(guān)系,所以測試下第一次嘗試失敗的情況:
Bingo! 一切正常。此問題圓滿解決!
真是的jQuery的BUG嗎?
想想就不可思議,一個被全球用戶使用的公共JavaScript居然有這樣的BUG,難道就沒人發(fā)現(xiàn)么?
網(wǎng)上搜索了一圈,看起來我多慮了,早就有用戶提出這個問題:
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 nativeoffsetParent
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 核心開發(fā)團隊給出的答案,大意是說 offsetParent 這個節(jié)點屬性指的是這樣一個父節(jié)點:
1. 節(jié)點是非靜態(tài)的,也就是擁有 position: relative / absolute / fixed 樣式
2. 節(jié)點是 table, th 或者 td
哈,這么多年過去了,我居然第一次聽說 offsetParent 還有相對于 td,th,table這個說法,盡管這個td,th,table節(jié)點是 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);});
分別在不同瀏覽器中查看結(jié)果。
Chrome:
Firefox:
Edge:
IE11(由于IE11打不開jsfiddle網(wǎng)站,我們單獨創(chuàng)建了一個頁面):
甚至IE9也是這樣的:
好吧,看來我真是孤陋而寡聞了。
jQuery 是在 v3.3.0 開始考慮到第二種情況的,但是 jQuery.offsetParent() 的定義沒有改變。這種不一致性本身就是一個BUG。
而不和之前的 jQuery v1.x, v2.x 乃至于 v3.3.0 之前的版本兼容,對于一個廣泛使用的公共庫而言,更應(yīng)該算是一個BUG,或者至少提供兼容之間的方法。
考慮另一個更現(xiàn)實的問題:在實際應(yīng)用中,我們?yōu)槭裁匆@取 td ,table 的 position() 呢?
不是為了拿獲取到的值去更新其他 td 或者 table 的 top/left 屬性!
而是為了拿獲取的值去更新其他相關(guān) div 節(jié)點的 top/left 屬性,而 div 節(jié)點的 position() 是相對于 position: relative / absolute / fixed 父節(jié)點的,而非 td ,table 節(jié)點,這就一定會出問題。
所以,我們還是認定這是一個BUG。
但是 jQuery 官方貌似沒有修正這個問題的意思,而是可能在將來版本修改 jQuery.offsetParent() 的定義:
但是jQuery你這樣做真的好么,都10多年了你都沒遵守標準,突然來個小版本號稱支持標準,搞的和之前的代碼不兼容,你讓之前的代碼情何以堪!
不管怎樣,我們有了自己的解決辦法。
小結(jié)
這篇文章描述了我們 FineUIPro 產(chǎn)品 更新中遇到的一個問題,最終將問題定位到 jQuery.position() 函數(shù),雖然jQuery的做法是依照HTML規(guī)范來的,但是 jQuery.offsetParent() 和 jQuery.position() 兩個函數(shù)有沖突,并且會導(dǎo)致之前的jQuery插件出錯,應(yīng)該算是一個BUG吧。
好在本文給出了一個補救的方法,如果這里的方法能夠?qū)δ阌兴鶈l(fā)或者幫助,就給個推薦唄。
相關(guān)文章
jQuery插件EasyUI設(shè)置datagrid的checkbox為禁用狀態(tài)的方法
這篇文章主要介紹了jQuery插件EasyUI設(shè)置datagrid的checkbox為禁用狀態(tài)的方法,涉及jQuery插件EasyUI相關(guān)屬性設(shè)置技巧,需要的朋友可以參考下2016-08-08jQuery 判斷元素是否存在然后按需加載內(nèi)容的實現(xiàn)代碼
這篇文章主要介紹了jQuery 判斷元素是否存在然后按需加載內(nèi)容的實現(xiàn)代碼,需要的朋友可以參考下2020-01-01jQuery獲取父元素節(jié)點、子元素節(jié)點及兄弟元素節(jié)點的方法
這篇文章主要介紹了jQuery獲取父元素節(jié)點、子元素節(jié)點及兄弟元素節(jié)點的方法,結(jié)合實例形式總結(jié)分析了jQuery節(jié)點操作的相關(guān)技巧,需要的朋友可以參考下2016-04-04分享十五款 jQuery 社交網(wǎng)絡(luò)分享插件
這篇文章主要介紹了分享十五款 jQuery 社交網(wǎng)絡(luò)分享插件,需要的朋友可以參考下2015-05-05jQuery Validate表單驗證深入學(xué)習(xí)
這篇文章主要介紹了jQuery Validate表單驗證入門知識,該插件捆綁了一套有用的驗證方法,包括 URL 和電子郵件驗證,同時提供了一個用來編寫用戶自定義方法的 API,感興趣的小伙伴們可以參考一下2015-12-12自定義jquery模態(tài)窗口插件無法在頂層窗口顯示問題
自定義一個jquery模態(tài)窗口插件只能在mainFrame窗口中顯示,無法在頂層窗口顯示2014-05-05