验证外部脚本是否已加载

96

我正在创建一个jquery插件,我想要验证外部脚本是否已经加载。这是为了内部web应用程序,并且我可以保持脚本名称/位置一致(mysscript.js)。这也是一个ajax插件,可以在页面上多次调用。

如果我能够验证该脚本尚未加载,则会使用以下方法加载:

jQuery.getScript()
如何验证脚本是否已加载,因为我不想在页面上重复加载相同的脚本?这是由于脚本缓存而无需担心的事情吗? 更新: 我可能无法控制我们组织中使用此插件的人,并且可能无法强制执行脚本是否已经存在于页面上,无论是否具有特定的ID,但脚本名称始终位于相同位置并具有相同名称。我希望我可以使用脚本名称来验证它是否已加载。


该线程位于SO jQuery标签FAQ的顶部,并且有大量检查现有脚本的方法。https://dev59.com/mXNA5IYBdhLWcg3wUL_n 有各种各样的方法,得到了很多反馈。 - charlietfl
14个回答

141

如果脚本在全局空间中创建了任何变量或函数,您可以检查它们是否存在:

外部JS(在全局作用域中)--

var myCustomFlag = true;

而要检查此操作是否已运行:

if (typeof window.myCustomFlag == 'undefined') {
    //the flag was not found, so the code has not run
    $.getScript('<external JS>');
}

更新

你可以通过选择所有的<script>元素并检查它们的src属性来检查所需的<script>标签是否存在:

//get the number of `<script>` elements that have the correct `src` attribute
var len = $('script').filter(function () {
    return ($(this).attr('src') == '<external JS>');
}).length;

//if there are no scripts that match, the load it
if (len === 0) {
    $.getScript('<external JS>');
}

或者你可以直接将这个.filter()功能嵌入到选择器中:

var len = $('script[src="<external JS>"]').length;

$.getScript() 的回调函数中,您可以设置一个全局标志以供检查:$.getScript('<external JS>, function () { window.myCustomFlag = true; }')。然后,您只需检查特定的 <script> 元素或 myCustomFlag 变量是否存在即可。 - Jasper
@jasper,是的,这可能是最有效的方法,这就是为什么我接受你的答案的原因。刚刚完成了我的解决方案(其中应该包括一个全局标志)。 - AGoodDisplayName
40
脚本src的检查没问题,但问题是具有正确src的脚本标签的存在并不意味着该脚本已经加载。插入标签只是加载阶段的开始。 - Karol
3
根据我理解的问题,我认为脚本是通过将标签插入DOM来动态加载的。只有在这种情况下,您才可以多次插入相同的脚本(没有人会将脚本插入HTML中,然后尝试动态加载它)。因此,一旦插入脚本标签,仍需等待其加载。在这种情况下,您的答案将不起作用,因为它只检查脚本标签是否存在,而不关注其是否已加载。 - Karol
正如@Karol所述,这个解决方案并没有告诉你脚本是否已经加载。请参考我的答案获取正确的方法。 - lonix
显示剩余2条评论

64

这个问题有一些答案,但我认为这个解决方案值得添加。它结合了几个不同的回答。

对我来说关键点是:

  • 添加一个 #id 标签,以便易于查找,避免重复
  • 使用 .onload() 等待脚本加载完成后再使用它

mounted() {
  // First check if the script already exists on the dom
  // by searching for an id
  let id = 'googleMaps'
  if(document.getElementById(id) === null) {
    let script = document.createElement('script')
    script.setAttribute('src', 'https://maps.googleapis.com/maps/api/js?key=' + apiKey)
    script.setAttribute('id', id)
    document.body.appendChild(script) 

    // now wait for it to load...
    script.onload = () => {
        // script has loaded, you can now use it safely
        alert('thank me later')
        // ... do something with the newly loaded script
    }      
  }
}

7
这里的正确答案是 script.onload。谢谢分享! - Vigs
3
像这样的回答就是为什么我不再信任被接受的回答了...谢谢!script.onload实际上会等待加载完成后才采取行动,这正是我所需要的。 - jetsetter
1
是的,这很烦人,但经常值得检查所有答案的努力。事情会改变,而不同的答案通常都可以提供一些有用的东西。 - atlas_scoffed
感谢分享。script.onload做得很好。 - undefined

35

@jasper的回答完全正确,但在现代浏览器中,一个标准的JavaScript解决方案可能是:

function isScriptLoaded(src)
{
    return Boolean(document.querySelector('script[src="' + src + '"]'));
}

2021年7月更新:

上述已被接受的解决方案随着时间的推移已经改变和改进了很多。我之前回答的范围仅限于检测 是否在文档中插入脚本来加载(而不是脚本是否已经完成加载)。

要检测脚本是否已经加载,我通常使用以下方法:

  1. 创建一个通用的库函数来动态加载所有脚本。
  2. 在加载之前,它使用上面的 isScriptLoaded(src) 函数来检查脚本是否已经被添加过(比如,由另一个模块)。
  3. 我使用类似以下的 loadScript() 函数来加载脚本,该函数使用回调函数来通知调用模块脚本是否已经成功加载完成。
  4. 我还使用其他逻辑来在脚本加载失败时进行重试(以防网络问题)。
    1. 重试是通过从 body 中删除 <script> 标记并再次添加它来完成的。
    2. 如果经过配置的重试次数后仍无法加载,则将 <script> 标记从 body 中删除。
    3. 出于简单起见,我已经从以下代码中删除了该逻辑。但是增加它应该很容易。

/** 
 * Mark/store the script as fully loaded in a global variable.
 * @param src URL of the script
 */
function markScriptFullyLoaded(src) {
    window.scriptLoadMap[src] = true;
}

/** 
 * Returns true if the script has been added to the page
 * @param src URL of the script
 */
function isScriptAdded(src) {
    return Boolean(document.querySelector('script[src="' + src + '"]'));
}

/** 
 * Returns true if the script has been fully loaded
 * @param src URL of the script
 */
function isScriptFullyLoaded(src) {
    return src in window.scriptLoadMap && window.scriptLoadMap[src];
}

/** 
 * Load a script. 
 * @param src URL of the script
 * @param onLoadCallback Callback function when the script is fully loaded
 * @param onLoadErrorCallback Callback function when the script fails to load
 * @param retryCount How many times retry laoding the script? (Not implimented here. Logic goes into js.onerror function)
 */
function loadScript(src, onLoadCallback, onLoadErrorCallback, retryCount) {
    if (!src) return;
    
    // Check if the script is already loaded
    if ( isScriptAdded(src) )
    {
        // If script already loaded successfully, trigger the callback function
        if (isScriptFullyLoaded(src)) onLoadCallback();
        
        console.warn("Script already loaded. Skipping: ", src);
        return;
    }

    // Loading the script...
    const js = document.createElement('script');
    js.setAttribute("async", "");
    js.src = src;
    
    js.onload = () => {
        markScriptFullyLoaded(src)
        
        // Optional callback on script load
        if (onLoadCallback) onLoadCallback();
    };
    
    js.onerror = () => {
        // Remove the script node (to be able to try again later)
        const js2 = document.querySelector('script[src="' + src +'"]');
        js2.parentNode.removeChild(js2);
        
        // Optional callback on script load failure
        if (onLoadErrorCallback) onLoadErrorCallback();
    };

    document.head.appendChild(js);
}


3
如果你愿意的话,可以使用Boolean代替条件运算符。 - CertainPerformance
1
它只能是return document.querySelector('script[src="' + src + '"]'); - Harshal Parekh
1
这对我没有用。例如:document.querySelector('head').append(script_el); console.log(isScriptLoaded(script_el.src) 返回 true,但脚本尚未运行。 - oriadam

11

现在我意识到该如何做,这其实很简单。感谢所有答案引导我找到解决方案。我不得不放弃$.getScript()来指定脚本来源...有时手动操作是最好的。

解决方案

//great suggestion @Jasper
var len = $('script[src*="Javascript/MyScript.js"]').length; 

if (len === 0) {
        alert('script not loaded');

        loadScript('Javascript/MyScript.js');

        if ($('script[src*="Javascript/MyScript.js"]').length === 0) {
            alert('still not loaded');
        }
        else {
            alert('loaded now');
        }
    }
    else {
        alert('script loaded');
    }


function loadScript(scriptLocationAndName) {
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = scriptLocationAndName;
    head.appendChild(script);
}

这告诉您该标记已插入DOM,而不是实际加载脚本。请参阅我的答案以获取正确的方法。 - lonix

9
创建具有特定ID的脚本标签,然后检查该ID是否存在?
或者,循环遍历脚本标签,检查脚本'src',并确保这些脚本没有已经加载与您要避免的相同值?
编辑:根据反馈,代码示例将非常有用:
(function(){
    var desiredSource = 'https://sitename.com/js/script.js';
    var scripts       = document.getElementsByTagName('script');
    var alreadyLoaded = false;

    if(scripts.length){
        for(var scriptIndex in scripts) {
            if(!alreadyLoaded && desiredSource === scripts[scriptIndex].src) {
                alreadyLoaded = true;
            }
        }
    }
    if(!alreadyLoaded){
        // Run your code in this block?
    }
})();

正如评论中提到的一样(https://stackoverflow.com/users/1358777/alwin-kesler),这可能是一个替代方法(未经过基准测试):

(function(){
    var desiredSource = 'https://sitename.com/js/script.js';
    var scripts = document.getElementsByTagName('script');
    var alreadyLoaded = false;

    for(var scriptIndex in document.scripts) {
        if(!alreadyLoaded && desiredSource === scripts[scriptIndex].src) {
            alreadyLoaded = true;
        }
    }
    if(!alreadyLoaded){
        // Run your code in this block?
    }
})();

这是完美的解决方案。我推荐第二个。 - Ilyas karim
如果能够将这些想法与代码结合起来就太好了。 - Ilyas karim
document.scripts 似乎比 document.getElementsByTagName('script') 更快,但尚未测试。 - Alwin Kesler
1
@AlwinKesler,同意。已经用另一种方式更新了代码。最好能够得到一个基准测试。 - MyStream

5

只需检查全局变量是否可用,如果不可用,则再次检查。为了防止超过最大调用堆栈,请在检查时设置100ms的超时时间:

function check_script_loaded(glob_var) {
    if(typeof(glob_var) !== 'undefined') {
    // do your thing
    } else {
    setTimeout(function() {
    check_script_loaded(glob_var)
    }, 100)
    }
}

4

我认为最好使用window.addEventListener('error')来捕获脚本加载错误,并尝试重新加载它。当我们从CDN服务器载入脚本时,这一方法非常有用。如果无法从CDN加载脚本,则可以从我们的服务器加载。

window.addEventListener('error', function(e) {
  if (e.target.nodeName === 'SCRIPT') {
    var scriptTag = document.createElement('script');
    scriptTag.src = e.target.src.replace('https://static.cdn.com/', '/our-server/static/');
    document.head.appendChild(scriptTag);
  }
}, true);

4

检查外部脚本是否加载完成的另一种方法是使用jquery的data函数并存储验证标记。例如:

if(!$("body").data("google-map"))
    {
        console.log("no js");

        $.getScript("https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=initilize",function(){
            $("body").data("google-map",true);

            },function(){
                alert("error while loading script");
            });
        }
    }
    else
    {
        console.log("js already loaded");
    }

3
将上述多个答案合并成易于使用的函数
function GetScriptIfNotLoaded(scriptLocationAndName)
{
  var len = $('script[src*="' + scriptLocationAndName +'"]').length;

  //script already loaded!
  if (len > 0)
      return;

  var head = document.getElementsByTagName('head')[0];
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = scriptLocationAndName;
  head.appendChild(script);
}

2

这个页面上的一些答案是错误的。它们检查<script>标签的存在 - 但这是不够的。这只告诉你标签被插入到DOM中,而不是脚本已经完成加载。

我假设问题有两个部分:插入脚本的代码和检查脚本是否已加载的代码。

动态插入脚本的代码:

let tag    = document.createElement('script');
tag.type   = 'text/javascript';
tag.id     = 'foo';
tag.src    = 'https://cdn.example.com/foo.min.js';
tag.onload = () => tag.setAttribute('data-loaded', true);   // magic sauce
document.body.appendChild(tag);

检查脚本是否已加载的其他代码:

let script   = document.getElementById('foo');
let isLoaded = script && script.getAttribute('data-loaded') === 'true';
console.log(isLoaded);     // true

如果这两件事情(插入和检查)在同一个代码块中,那么你可以简化上面的代码:
tag.onload = () => console.log('loaded');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接