Chrome调试外部JS断点随机失效问题:Thymeleaf布局场景
问题场景
用SpringBoot+Thymeleaf+Bootstrap开发时,通过th:replace把带外部JS引用的页面片段加载到布局里,外部JS(/js/test.js)里的window.onload断点触发完全没规律——有时候刷五六次能进,刷二十次都不行;但把JS代码直接内嵌到HTML片段里,每次刷新都能稳定触发断点。
相关代码如下:
外部引用JS的页面片段
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <th:block th:fragment="test"> <script src="/js/test.js"></script> <div class="mx-auto container-lg top-div"> <div>TEST</div> </div> </th:block> </html>
对应的test.js代码
window.onload = function() { var aa = 100; }
内嵌JS的页面片段(断点正常触发)
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <th:block th:fragment="test"> <!--<script src="/js/test.js"></script>--> <script> window.onload = function() { var aa = 100; } </script> <div class="mx-auto container-lg top-div"> <div>TEST</div> </div> </th:block> </html>
布局文件代码
<!DOCTYPE html> <html lang="ko" xmlns:th="http://thymeleaf.org"> <!--Layout Head--> <div th:replace="/fragments/head.html :: head"></div> <!--Layout Nav Header--> <div th:replace="/fragments/topnav.html :: topnav"></div> <!-------------------------------------------------------------> <!--Layout Content(Page)--> <div th:replace="@{'/page/' + ${layoutContent_Page}} :: ${layoutContent_Fragment}"></div> <!-------------------------------------------------------------> <!--Layout Footer--> <div th:replace="/fragments/footer.html :: footer"></div> <!--Sidebar(Collapsed)--> <div th:replace="/fragments/sidebar.html :: sidebar"></div> </html>
另外发现个细节:勾选Chrome调试器的Event Listener Breakpoints->Load->load后,Bootstrap的事件处理器会先执行,这时候test.js的window.onload总能触发断点。
原因拆解
脚本加载时机赶不上window.onload
用th:replace加载片段时,外部JS是在布局渲染完才开始加载的。window.onload要等整个页面所有资源都加载完才触发,如果外部JS加载慢了一步,等它加载完注册回调时,window.onload已经触发过了,断点自然不会命中;要是JS加载够快,赶在事件触发前完成注册,断点就能命中——这就是随机性的来源。而内嵌JS是页面渲染时直接执行,回调能及时注册,所以每次都稳定。Thymeleaf片段的脚本位置坑
th:replace是服务器端把片段替换到布局里,最终输出的HTML里,外部<script>标签是在布局的内容区域(中间位置),不是<head>也不是页面底部。这种位置会让脚本加载比页面其他资源晚,大大增加了错过window.onload的概率。Bootstrap的事件监听帮了忙
Bootstrap内部也会监听load事件,当你勾选调试器的Load->load断点时,调试器会在所有load事件触发时暂停,包括Bootstrap的回调。这相当于强制给window.onload的执行流程踩了刹车,给外部JS的加载和回调注册留出了足够时间,所以断点总能命中。
解决办法
给脚本加defer,移到head里
把外部JS的引用移到布局的<head>中,加上defer属性,这样脚本会异步加载,但会在DOM解析完成后、window.onload之前执行,保证回调能及时注册:<!-- 在head.html的<head>标签内添加 --> <script src="/js/test.js" defer></script>换成DOMContentLoaded事件
比起window.onload要等所有资源加载完,DOMContentLoaded只要DOM树构建好就触发,不用等图片、样式这些资源,能大幅降低错过事件的概率:document.addEventListener('DOMContentLoaded', function() { var aa = 100; });把脚本移到页面最底部
在布局文件的最后(比如sidebar之后)添加外部JS引用,这样脚本会在页面大部分内容加载完后再加载,此时window.onload还没触发,回调注册更稳定:<!-- 布局文件末尾添加 --> <script src="/js/test.js"></script>
内容的提问来源于stack exchange,提问作者CBOrange

