如何检测JavaScript文件是否已加载?

56

当 JavaScript 文件加载时,是否有事件触发?这个问题是由于 YSlow 建议将 JavaScript 文件移动到页面底部而引起的。这意味着在包含 function1 代码的 js 文件加载之前,$(document).ready(function1) 就已经被触发了。

如何避免这种情况?

7个回答

78

我没有方便的参考资料,但是脚本标签是按顺序处理的,因此,如果您将$(document).ready(function1)代码放在定义function1等函数的脚本标签之后,应该就可以了。

<script type='text/javascript' src='...'></script>
<script type='text/javascript' src='...'></script>
<script type='text/javascript'>
$(document).ready(function1);
</script>
当然,另一种方法是通过将文件合并到构建过程中来确保您仅使用一个脚本标签(除非您从CDN某处加载其他标签)。这也将有助于提高页面的感知速度。

编辑:刚意识到我实际上没有回答你的问题:我不认为有跨浏览器事件可以触发。如果您足够努力,就会有,参见下文。您可以测试符号并使用setTimeout重新安排。
<script type='text/javascript'>
function fireWhenReady() {
    if (typeof function1 != 'undefined') {
        function1();
    }
    else {
        setTimeout(fireWhenReady, 100);
    }
}
$(document).ready(fireWhenReady);
</script>

......但如果你正确安排脚本标签的顺序,就不需要这样做。


更新:如果需要,在将 script 元素动态添加到页面时可以获取加载通知。要获得广泛的浏览器支持,你需要执行两个不同的操作,但是作为一个组合技术,这个方法是可行的:

function loadScript(path, callback) {

    var done = false;
    var scr = document.createElement('script');

    scr.onload = handleLoad;
    scr.onreadystatechange = handleReadyStateChange;
    scr.onerror = handleError;
    scr.src = path;
    document.body.appendChild(scr);

    function handleLoad() {
        if (!done) {
            done = true;
            callback(path, "ok");
        }
    }

    function handleReadyStateChange() {
        var state;

        if (!done) {
            state = scr.readyState;
            if (state === "complete") {
                handleLoad();
            }
        }
    }
    function handleError() {
        if (!done) {
            done = true;
            callback(path, "error");
        }
    }
}

根据我的经验,错误通知(onerror)并不是完全跨浏览器可靠的。同时请注意有些浏览器会同时执行两种机制,因此使用done变量可以避免重复通知。


谢谢你的回答,但对我来说,为什么应该在<script>标签后放置$(document).ready(...)似乎有些奇怪。 - Eugeniu Torica
1
这很好,但如果你正在使用jQuery,也要考虑$.getScript() http://api.jquery.com/jQuery.getScript/ - tybro0103
1
对于动态加载器,你忘记了appendChild。不过代码还是很棒的。 - Eddie
@Eddie:谢谢,已修复。 - T.J. Crowder
1
@Steverino:不,除非您使用asyncdefer属性,这会改变事情的进行方式。在HTML中使用script标签会使页面加载停滞,直到运行该脚本代码(因为它可能使用document.write输出内容),因此您可以确定较早的script已加载(或失败)以便于后面的加载(浏览器可以并行下载脚本,但它们确保按照HTML中的顺序执行)。asyncdefer会改变这一点。请参见:https://html.spec.whatwg.org/multipage/scripting.html#attr-script-async - T.J. Crowder
显示剩余8条评论

6
当他们说“页面底部”时,他们并不是字面意思上的底部:他们指的是在关闭的</body>标签之前。将你的脚本放在那里,它们将在DOMReady事件之前加载;如果放在之后,DOM将在它们加载之前准备好(因为在解析关闭的</html>标签时已经完成),而这将无法正常工作,正如你所发现的那样。
如果你想知道我是怎么知道这就是他们的意思的:我曾在雅虎工作过,我们把我们的脚本放在</body>标签之前 :-)
编辑:还要参考T.J. Crowder的回复,并确保你的顺序正确。

5

有时候很有用的功能 - Eugeniu Torica

3

参照 @T.J. Crowder 的回答,我加了一个递归外层循环,允许我们遍历数组中的所有脚本,然后在所有脚本加载完成后执行一个函数:

loadList([array of scripts], 0, function(){// do your post-scriptload stuff})

function loadList(list, i, callback)
{
    {
        loadScript(list[i], function()
        {
            if(i < list.length-1)
            {
                loadList(list, i+1, callback);  
            }
            else
            {
                callback();
            }
        })
    }
}

当然,如果您愿意,可以编写一个包装器来去除“0”:

当然,如果你想的话,你可以写一个包装器来消除这个“0”:

function prettyLoadList(list, callback)
{
    loadList(list, 0, callback);
}

很好的工作@T.J. Crowder - 我在其他帖子中看到了“只需在运行回调之前添加几秒延迟”的做法,让我感到不安。


2
我通常在JavaScript文件末尾调用一个函数来注册其加载情况,这对于所有浏览器都非常有效。
例如:我有一个index.htm、Js1.js和Js2.js文件。我在index.htm的头部添加了函数IAmReady(Id),并从Js1和Js2文件的末尾分别以参数1和2调用它。IAmReady函数将具有一种逻辑,即在收到两个js文件的调用后(将调用次数存储在静态/全局变量中),运行引导代码。

0

更改脚本的加载顺序,以便在ready回调中使用function1之前定义它。

此外,我发现将ready回调定义为匿名方法比命名方法更好。


这是不可能的事,因为它在JavaScript文件中定义。 - Eugeniu Torica
你可以先包含定义function1的文件,然后再包含使用该函数的另一个文件。关键是你不能随意添加脚本文件。 - RaYell

0

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