Javascript 如何查找链接的异步脚本是否已加载?

Javascript 如何查找链接的异步脚本是否已加载?,javascript,jquery,amd,Javascript,Jquery,Amd,下面是一个场景。我正在执行$.getScript()函数调用,以在javascript文件中获取脚本。我从$.getScript()下载的脚本尝试下载它所依赖的其他脚本。在我的脚本中,我使用done()检查脚本是否已完全加载。如果确实如此,那么我尝试调用的函数不在我刚刚加载的脚本中,而是加载在其中的脚本 它越来越让人困惑,所以让我用一些代码来演示:- //my script.js $.getScript("http://myexternaljs.com/first.js").done(funct

下面是一个场景。我正在执行
$.getScript()
函数调用,以在javascript文件中获取脚本。我从
$.getScript()
下载的脚本尝试下载它所依赖的其他脚本。在我的脚本中,我使用
done()
检查脚本是否已完全加载。如果确实如此,那么我尝试调用的函数不在我刚刚加载的脚本中,而是加载在其中的脚本

它越来越让人困惑,所以让我用一些代码来演示:-

//my script.js
$.getScript("http://myexternaljs.com/first.js").done(function(){
    doSecond(); //<- this resides in second.js which is being called in first.js below
}

//first.js
(function(){
    $.getScript("http://firstexternal.com/second.js");
}());

//second.js
function doSecond(){
    return console.log("hello world");
}
//my script.js
$.getScript(“http://myexternaljs.com/first.js)完成(函数(){

doSecond();//您可以修改
$.getScript
的工作方式

$.fn.getScript = (function() {
  var originalLoad = $.fn.getScript;
  return function() {
    originalLoad.apply($, arguments);
    $(document).trigger('load_script')
  };
})();
这将在每次加载脚本时触发事件

因此,您可以等待触发这些事件并检查您的方法是否存在

$(document).one('second_loaded', function() {
  doSecond();
}).on('load_script', function() {
  doSecond && document.trigger('second_loaded');
});

注意
one
而不是
on
。它会使事件触发一次。

事实:

  • 您有
    first.js
    ,在这个脚本中包含
    second.js
  • 您需要调用
    doSecond()
    ,它在
    second.js
  • 在调用之前,需要确保
    doSecond()
    可用
  • 您不能直接更改
    first.js
    second.js
    ,但您可以让其他人更改它
可能的解决方案,按从最佳到最差排序

1)请求从
first.js
中删除
second.js
。分别调用它们,以便可以嵌套它们:

$.getScript("first.js").done(function(){
  $.getScript("second.js").done(function(){
    doSecond(); 
  });
});
这是最好的解决方案。除此之外,还有其他方法基本上可以做相同的事情(例如,其他人的回答)。如果
first.js
同步包含
second.js
,或者在继续之前强制加载(例如,下面的选项#3),您一开始就不会遇到这个问题。因此,必须对
first.js
进行结构化,以处理
second.js
被*a*sync加载的问题,因此他们将其从文件中删除并由您自己调用应该不会有问题

但是您提到second.js的位置是在first.js中定义的,所以这对您来说是不可行的(为什么不呢?他们能把path/to/script放在一个变量中让您访问吗?)

2)请求将
second.js
包装在一个
.done
或等效加载的回调函数中,该回调函数弹出一个您可以定义的回调函数

// inside first.js
$.getScript("second.js").done(function(){
  if (typeof 'secondLoaded'=='function') 
    secondLoaded();      
});


// on-page or elsewhere, you define the callback
function secondLoaded() {
  doSecond();
}
这只是一个通用且简单的“回调”示例。实现这一原则的方法有一百万种,具体取决于这些脚本中的实际内容以及人们愿意付出多大的努力来重构内容

3)请求将
second.js
script include更改为通过
document.write包含

document.write(unescape("%3Cscript src='second.js' type='text/javascript'%3E%3C/script%3E"));
这将迫使js解析
document.write
,然后js才能继续,因此
second.js
应该在您想要使用
doSecond()时加载
。但这被认为是不好的做法,因为在解决
document.write
之前,不会发生其他任何事情。因此,如果
second.js
永远无法加载或最终超时..这将导致糟糕的用户体验。因此,除非由于“繁文缛节”原因,您别无选择,否则应避免使用此选项

4)使用
setTimeout
尝试并等待加载

function secondLoaded() {
  if (!secondLoaded.attempts) secondLoaded.attempts = 0;
  if (secondLoaded.attempts < 5) {
    if (typeof 'doSecond'=='function') {
      doSecond();
    } else {
      secondLoaded.attempts++;
      window.setTimeout('secondLoaded()',100);
    }
  }
}
secondLoaded();
函数secondLoaded(){
如果(!secondLoaded.attempts)secondLoaded.attempts=0;
如果(第二次加载。尝试次数<5){
if(类型为'doSecond'='function'){
doSecond();
}否则{
secondLoaded.truments++;
setTimeout('secondLoaded()',100);
}
}
}
二次加载();
我列出了比#3更糟糕的情况,但实际上这是一种tossup。在这种情况下,基本上你要么决定一个截止时间,不执行
doSecond()
(在本例中,我每隔100ms尝试5次),要么编写代码,永远保持检查(删除
.truments
逻辑,或者将其与设置间隔
删除间隔
逻辑交换)。

您还可以使用:

或者,您可以在ajaxComplete中执行此操作:

$(document).ajaxComplete(function(ev, jqXhr, options) {
  // You could simplify to 
  // doSecond && doSecond()
  // if you can trust that it will always be a function
  if ( doSecond && $.isFunction(doSecond) ) {
    doSecond();
  }
});
您是否考虑过使用:


如果我是你,我会facade$.getScript并执行上述技巧的一些组合。在阅读完评论后,似乎有可能加载同一个脚本两次

如果您使用requirejs,这个问题会为您解决,因为它只加载每个脚本一次

加载器: 用法: 此处使用MomentJS和下划线的真实示例

当然,requirejs会用更好的语法为您处理这个问题

安魂曲
你对myexternaljs.com的内容有控制权吗,还是对你来说是只读的?不幸的是,它是只读的。如果它不是只读的,我会接受答案。好吧,如果你有能力这样做,最好的方法是从
first.js
中删除
second.js
,然后将它加载到
first.js
中然后放入
doSecond()
在嵌套的
中。在
second.js上完成
等等,我以为你说它们是只读的。现在你可以修改它们了吗?我迷路了。这是我的第一个想法,但它可能有不希望的副作用,因为
second.js
将加载两次。而且,
second.js
的实际路径可能提前未知(它在
first.js
中定义)。@JoeFrambach你说得对,
second.js
正在从
first.js
加载,它可以随时更改
second.js
的命名。所以,我不知道

$(document).ajaxComplete(function(ev, jqXhr, options) {
  // You could simplify to 
  // doSecond && doSecond()
  // if you can trust that it will always be a function
  if ( doSecond && $.isFunction(doSecond) ) {
    doSecond();
  }
});
$.when($.getScript("http://myexternaljs.com/first.js"),$.getScript("http://firstexternal.com/second.js"))
.done(function(){
  doSecond();
}
var requiredScripts = {};

function importScript(url) {
   if (!requiredScripts[url]) {
       requiredScripts[url] = $.getScript(url);
   }

   return requiredScripts[url];
}
// should cause 2 requests
$.when(
    importScript('first.js'),
    importScript('second.js')
).done(function() {
    // should cause no requests
    $.when(importScript('first.js')).done(function() {});
    $.when(importScript('second.js')).done(function() {});
});
define(function(require) {
    var first = require('first'),
        second = require('second');
});
$(window).one("second", function(e, t) {
  if ( $(this).get(0).hasOwnProperty(e.type) && (typeof second === "function") ) { 
   second(); console.log(e.type, e.timeStamp - t); 
   $(this).off("second") 
  };
   return !second
});


$.getScript("first.js")
.done(function( data, textStatus, jqxhr ) {
  if ( textStatus === "success" ) {
    first();
    $.getScript("second.js") 
    .done(function( script, textStatus, jqxhr, callbacks ) { 
      var callbacks = $.Callbacks("once");
      callbacks.add($(window).trigger("second", [ $.now() ]));         
      return ( textStatus === "success" && !!second 
             ? callbacks.fire()
             : $(":root").animate({top:"0"}, 1000, function() { callbacks.fire() })
             )
    });
  }; 
})
// `first.js` : `function first() { console.log("first complete") }`
// `second.js` : `function second() { console.log("second complete") }`