昨天我发了一篇文章【抓个Firefox的小辫子,围观群众有:Chrome、Edge、IE8-11】,提到了一个Firefox很多版本都存在的问题,而相同的测试页面在Chrome、Edge、IE8-11下面一切正常。
在评论里面,网友 @Blackheart 的回复引起了我的注意:
我就按照网友提供的方法重新测试了一下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script src="https://code.jquery.com/jquery-1.11.3.js"></script> <style> *, :after, :before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } </style> </head> <body> <fieldset> <legend>fieldset</legend> </fieldset> <script> $(function () { // $('#fieldset1').height(200); document.getElementById("fieldset1").style.height = "200px"; alert(parseInt($('#fieldset1').height(), 10)); }); </script> </body> </html>
在Firefox下显示效果:
在Chrome下显示效果:
截图中提示 181px,而不是 200px,这是完全正确的:
这两者的差异体现在最终设置的fieldset的高度不同:
$('#fieldset1').height(200);
document.getElementById("fieldset1").style.height = "200px";
当然,这两种设置高度的差异不是我们本篇文章讨论的重点。但却是问题的关键所在,下面分析jQuery源代码时会用到这个知识。
问题好像真的消失了,有那么一刻,我差点就要将Firefox的问题丢给 jQuery 了,但事实果真如此嘛?
深入 jQuery 源代码
俗话说:不入虎穴,焉得虎子,我们就来调试一把 jQuery.height 函数。
虽然用了十来年的jQuery,但真正去调试 jQuery 代码却没有几次,毕竟 jQuery 的代码可谓是千锤百炼,吹毛求疵想找个BUG都难。
这次就没办法了,既然怀疑到这里只好入手了:
1. 入口函数
2. 首先进入 style 函数:
3. 进入 set 函数
此时如果我们来执行一下 augmentWidthOrHeight:
得到的是一个负值,通过前面对 jQuery.height 的介绍我们知道,jQuery.height(200)是不包含padding和border的,最终还是要通过 $('#fieldset1')[0].style.height 来设置,所以我们需要先计算 fieldset 上下的padding和border。
4. 进入 augmentWidthOrHeight 函数:
这个截图可以印证我们之前的设想,通过 jQuery.css 函数来获取 paddingTop,borderTopWidth,paddingBottom,borderBottomWidth 四个值。
5. 最后回到 style 函数:
此时 value 值已经是计算后的值了,其中包含用户要设置的 200px,以及上下padding和border 17.6px,总计 217.6px
下面通过 style[ name ] = value,将 217.6px 设置到 fieldset 节点:
此时,我们来仔细观察下三个变量,说白了,这句话的意思下面的代码是一模一样的:
document.getElementById("fieldset1").style.height = "217.6px";
那就奇怪了,既然 jQuery.height 最终也是调用的 style.height,为啥直接用 style.height 设置就没问题呢?
全面复盘
最初,我怀疑是 try-catch 搞的鬼,后来发现不是。按照 jQuery.height 的调用流程,我们自己动手来重现一下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script src="https://code.jquery.com/jquery-1.11.3.js"></script> <style> *, :after, :before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } </style> </head> <body> <fieldset> <legend>fieldset</legend> </fieldset> <script> $(function () { var elem = $('#fieldset1')[0]; var view = elem.ownerDocument.defaultView; if (!view || !view.opener) { view = window; } var styles = view.getComputedStyle(elem); var val = 0; val -= jQuery.css(elem, "paddingTop", true, styles); val -= jQuery.css(elem, "borderTopWidth", true, styles); val -= jQuery.css(elem, "paddingBottom", true, styles); val -= jQuery.css(elem, "borderBottomWidth", true, styles); elem.style.height = (200 - val) + "px"; alert(parseInt($('#fieldset1').height(), 10)); }); </script> </body> </html>
在Firefox下可以重现问题:
简化下代码: