页面加载时如何控制多个脚本的执行?解决Vanilla JS中DOMContentLoaded多次绑定失效问题
解决Vanilla JS中DOMContentLoaded仅触发一次导致回调未执行的问题
这个问题确实很常见——原生JS的DOMContentLoaded事件只会在DOM首次加载完成时触发一次,如果你的脚本在事件已经触发后才注册回调,那这个回调永远不会执行。而jQuery的$(function(){})之所以能多次调用都生效,是因为它内部做了DOM加载状态的判断,如果DOM已经准备好,会立即执行回调,而不是傻等事件触发。
下面给你几个靠谱的解决方案:
1. 封装一个模拟jQuery的DOMReady工具函数
这是最通用的方案,我们可以自己写一个函数,自动判断DOM是否已经加载完成,决定是立即执行回调还是绑定事件:
function onDOMReady(callback) { // 检查DOM是否已经处于可操作状态 if (document.readyState === 'interactive' || document.readyState === 'complete') { // 立即执行回调 callback(); } else { // 否则监听DOMContentLoaded事件 document.addEventListener('DOMContentLoaded', callback); } }
然后在两个脚本里都用这个函数:
menu.js(通用脚本)
function onDOMReady(callback) { if (document.readyState === 'interactive' || document.readyState === 'complete') { callback(); } else { document.addEventListener('DOMContentLoaded', callback); } } onDOMReady(() => { document.querySelector(".navbar-menu").addEventListener('click', () => { // 你的菜单逻辑 }); });
index.js(页面专属脚本)
// 先处理异步数据请求 const { httpConstants, someMore } = (await axios.get('/some-data')).data; // 复用onDOMReady函数(如果是模块,可导出/导入该函数,或直接写判断逻辑) function onDOMReady(callback) { if (document.readyState === 'interactive' || document.readyState === 'complete') { callback(); } else { document.addEventListener('DOMContentLoaded', callback); } } onDOMReady(() => { // 这里写页面专属的DOM操作逻辑 });
2. 监听readystatechange事件
这个事件会在文档加载状态变化时触发,包括从loading到interactive、complete的过程。我们可以利用它来确保回调执行,同时注意移除监听避免重复触发:
document.addEventListener('readystatechange', function handler() { if (document.readyState === 'interactive' || document.readyState === 'complete') { // 执行完逻辑后移除事件监听,防止重复触发 document.removeEventListener('readystatechange', handler); // 你的DOM操作逻辑 } });
3. 把脚本移到</body>标签前
这是最简单的方案——如果把脚本放在页面内容的最后、</body>之前,此时DOM已经完全构建完成,不需要任何事件监听,直接执行代码即可:
<body> <!-- 所有页面内容 --> <script type="text/javascript" src="/javascripts/menu.js"></script> <script type="module" src="/javascripts/index.js"></script> </body>
这种情况下,menu.js和index.js里的代码可以直接操作DOM,比如:
menu.js
document.querySelector(".navbar-menu").addEventListener('click', () => { // 菜单逻辑 });
index.js
const { httpConstants, someMore } = (await axios.get('/some-data')).data; // 直接执行页面专属的DOM操作 // ...
为什么你之前的方法会触发递归错误?
你尝试在第一个脚本的DOMContentLoaded回调里手动触发DOMContentLoaded事件,这会导致事件被反复触发——第二个脚本监听了这个事件,执行时可能又触发了相关逻辑再次触发事件,最终形成无限递归,所以出现了InternalError: too much recursion,这个思路是不可行的。
内容的提问来源于stack exchange,提问作者Wernfried Domscheit
相关产品推荐
相关产品推荐

