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

动态加载JS文件:如何确认代码已执行并可用?

动态加载JS的执行确认方案及完整流程解析

咱先拆解你遇到的问题:你用Promise做了依赖链加载,脚本也按顺序插了HEAD,但onload和MutationObserver都没法精准判断第三方JS是否真的执行完毕——这俩工具的触发时机本来就和代码执行不是一回事,下面给你讲清楚原因、靠谱的解决方案,以及浏览器处理动态脚本的完整流程。

一、为啥onload和MutationObserver不好使?

  • onload事件:它只负责告诉你“脚本文件下载完并插入DOM了”,但浏览器执行JS的时机是要看主线程档期的。如果当时主线程在忙别的(比如渲染页面、跑其他JS),下载好的脚本会进执行队列排队,这时候onload触发了,但代码还没开始执行,自然找不到全局变量。
  • MutationObserver:它只能监听DOM节点的增删改,也就是知道“脚本标签被插进HEAD了”,但完全管不着脚本里的代码什么时候执行——标签插入和代码执行是浏览器处理流程里两个完全独立的步骤。

二、针对第三方JS的可靠检测方案

既然不能改第三方代码,最稳妥的办法就是轮询检测它暴露的全局特征(比如jQuery的$、Bootstrap的$.fn.modal),结合Promise封装成可复用的工具,确保依赖真正可用后再加载下一个脚本。

1. 通用的条件等待函数

先写一个能等任意条件满足的Promise工具,比单纯等变量更灵活:

function waitForCondition(condition, timeout = 10000) {
  return new Promise((resolve, reject) => {
    // 每隔50ms检查一次条件
    const checkInterval = setInterval(() => {
      if (condition()) {
        clearInterval(checkInterval);
        resolve();
      }
    }, 50);

    // 超时兜底,避免无限等待
    setTimeout(() => {
      clearInterval(checkInterval);
      reject(new Error('等待条件满足超时,请检查脚本是否加载成功'));
    }, timeout);
  });
}

2. 结合你的依赖加载流程使用

把这个工具和你的loadScript结合,就能保证依赖真正可用后再走下一步:

// 基础的脚本加载Promise函数
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = src;
    script.onload = () => resolve(script);
    script.onerror = (err) => reject(new Error(`加载脚本 ${src} 失败: ${err.message}`));
    document.head.appendChild(script);
  });
}

// 按依赖链加载:jQuery → Bootstrap → 你的代码
async function loadDependencyChain() {
  try {
    // 先加载jQuery,然后等它的全局变量出现
    await loadScript('jquery.min.js');
    await waitForCondition(() => typeof window.jQuery !== 'undefined');

    // 再加载Bootstrap,等它的特征(比如modal方法)出现
    await loadScript('bootstrap.min.js');
    await waitForCondition(() => {
      return typeof window.jQuery !== 'undefined' && typeof jQuery.fn.modal !== 'undefined';
    });

    // 最后加载你的代码,这时候依赖都稳了
    await loadScript('your-script.js');
    console.log('所有脚本加载并执行完成');
  } catch (err) {
    console.error('加载流程出错:', err);
  }
}

// 启动加载
loadDependencyChain();

如果只是等单个全局变量,也可以封装个简化版的waitForGlobalVar,本质和上面的函数逻辑一致。

三、浏览器处理动态脚本的完整流程

搞懂这个流程,你就明白为啥之前的方法会踩坑了:

  1. 发起下载:当你把<script>标签插进HEAD,浏览器会异步发起HTTP请求下载脚本(除非你加了async=false,那会变成同步加载阻塞主线程)。
  2. 进入执行队列:脚本下载完成后,不会立刻执行——它会被放到主线程的任务队列里排队。如果此时主线程正在执行其他任务(比如渲染页面、处理用户交互、跑其他JS),它就得等前面的任务做完。
  3. 执行脚本:轮到这个脚本时,浏览器会同步执行里面的所有代码:变量声明、函数定义、立即执行的逻辑都会在这一步完成,执行完后才会把全局变量挂载到window上。
  4. 触发onload:按照规范,onload应该是在脚本执行完成后才触发,但实际中可能因为浏览器的任务调度优先级(比如高优先级的渲染任务插队),导致onload和代码执行之间有极短的间隙——这就是你调试时看到onload触发了但变量还没出现的原因。

补充:像jQuery、Bootstrap这类第三方库都是同步执行的,没有异步逻辑,所以只要它们的全局变量出现了,就代表整个脚本已经执行完毕,所有功能都可用。

四、额外优化小技巧

  • 缓存检测结果:如果同一个脚本可能被多次加载,可以把检测通过的全局变量缓存起来,避免重复轮询。
  • 调整轮询间隔:50ms的间隔是平衡性能和响应速度的折中值,如果对响应速度要求高,可以改成20ms,不过一般50ms足够了。
  • 错误处理要到位:一定要加try/catch,不然某个脚本加载失败会导致整个流程挂死。

内容的提问来源于stack exchange,提问作者SBG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:13:37