You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

页面加载时如何控制多个脚本的执行?解决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);
  }
}

然后在两个脚本里都用这个函数:

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,比如:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 10:18:11