在html页面中包含JavaScript有很多不同的方法。我知道以下几种选择:

内联代码或从外部URI加载 包含在<head>或<body>标签中[1,2] 没有,延迟或异步属性(仅外部脚本) 包含在静态源代码中或由其他脚本动态添加(在不同的解析状态下,使用不同的方法)

不包括来自硬盘的浏览器脚本,javascript: uri和onEvent-attributes[3],已经有16个选项来执行JS,我确定我忘记了一些东西。

我不太关心快速(并行)加载,我更好奇的是执行顺序(这可能取决于加载顺序和文档顺序)。是否有一个好的(跨浏览器的)参考可以涵盖所有的情况?例如,http://www.websiteoptimization.com/speed/tweak/defer/只处理其中的6种浏览器,并且测试的大多是旧浏览器。

因为我担心没有,这是我的具体问题:我有一些初始化和脚本加载的(外部)头部脚本。然后在正文的末尾有两个静态的内联脚本。第一个允许脚本加载器动态地将另一个脚本元素(引用外部js)追加到主体。第二个静态内联脚本希望使用来自添加的外部脚本的js。它可以依赖于另一个已经被执行(为什么:-)?


如果您没有动态加载脚本或将它们标记为延迟或异步,则脚本将按照页面中遇到的顺序加载。不管它是一个外部脚本还是一个内联脚本——它们都是按照在页面中遇到的顺序执行的。外部脚本之后的内联脚本将被保留,直到它们之前的所有外部脚本都加载并运行。

异步脚本(不管它们是如何被指定为异步的)以不可预知的顺序加载和运行。浏览器会并行加载它们,并且可以自由地以任何它想要的顺序运行它们。

多个异步事物之间没有可预测的顺序。如果需要一个可预测的顺序,那么就必须通过注册来自异步脚本的加载通知来编码,并在适当的东西加载时手动排序javascript调用。

动态插入脚本标记时,执行顺序将取决于浏览器。您可以在这篇参考文章中看到Firefox的行为。简而言之,Firefox的新版本默认将动态添加的脚本标记添加到async,除非脚本标记另有设置。

带有async的脚本标签可以在加载时立即运行。事实上,浏览器可能会暂停解析器,不管它正在做什么,然后运行该脚本。所以,它几乎可以在任何时候运行。如果脚本被缓存,它几乎可以立即运行。如果脚本需要一段时间才能加载,那么它可能会在解析器完成后运行。关于async要记住的一件事是,它可以在任何时候运行,而且时间是不可预测的。

A script tag with defer waits until the entire parser is done and then runs all scripts marked with defer in the order they were encountered. This allows you to mark several scripts that depend upon one another as defer. They will all get postponed until after the document parser is done, but they will execute in the order they were encountered preserving their dependencies. I think of defer like the scripts are dropped into a queue that will be processed after the parser is done. Technically, the browser may be downloading the scripts in the background at any time, but they won't execute or block the parser until after the parser is done parsing the page and parsing and running any inline scripts that are not marked defer or async.

下面是那篇文章中的一段话:

脚本插入的脚本在IE和WebKit中异步执行,但是 在Opera和4.0版本之前的Firefox中同步运行。

HTML5规范的相关部分(适用于更新的兼容浏览器)在这里。这里有很多关于异步行为的文章。显然,该规范不适用于您可能需要测试才能确定的旧浏览器(或不符合规范的浏览器)。

引用HTML5规范:

Then, the first of the following options that describes the situation must be followed: If the element has a src attribute, and the element has a defer attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute The element must be added to the end of the list of scripts that will execute when the document has finished parsing associated with the Document of the parser that created the element. The task that the networking task source places on the task queue once the fetching algorithm has completed must set the element's "ready to be parser-executed" flag. The parser will handle executing the script. If the element has a src attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute The element is the pending parsing-blocking script of the Document of the parser that created the element. (There can only be one such script per Document at a time.) The task that the networking task source places on the task queue once the fetching algorithm has completed must set the element's "ready to be parser-executed" flag. The parser will handle executing the script. If the element does not have a src attribute, and the element has been flagged as "parser-inserted", and the Document of the HTML parser or XML parser that created the script element has a style sheet that is blocking scripts The element is the pending parsing-blocking script of the Document of the parser that created the element. (There can only be one such script per Document at a time.) Set the element's "ready to be parser-executed" flag. The parser will handle executing the script. If the element has a src attribute, does not have an async attribute, and does not have the "force-async" flag set The element must be added to the end of the list of scripts that will execute in order as soon as possible associated with the Document of the script element at the time the prepare a script algorithm started. The task that the networking task source places on the task queue once the fetching algorithm has completed must run the following steps: If the element is not now the first element in the list of scripts that will execute in order as soon as possible to which it was added above, then mark the element as ready but abort these steps without executing the script yet. Execution: Execute the script block corresponding to the first script element in this list of scripts that will execute in order as soon as possible. Remove the first element from this list of scripts that will execute in order as soon as possible. If this list of scripts that will execute in order as soon as possible is still not empty and the first entry has already been marked as ready, then jump back to the step labeled execution. If the element has a src attribute The element must be added to the set of scripts that will execute as soon as possible of the Document of the script element at the time the prepare a script algorithm started. The task that the networking task source places on the task queue once the fetching algorithm has completed must execute the script block and then remove the element from the set of scripts that will execute as soon as possible. Otherwise The user agent must immediately execute the script block, even if other scripts are already executing.


Javascript模块脚本,type="module"?

Javascript现在支持用这样的语法加载模块:

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

或者,带src属性:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

所有type="module"的脚本都会自动被赋予defer属性。这将与页面的其他加载一起并行(如果不是内联)下载它们,然后在解析器完成之后按顺序运行它们。

模块脚本也可以被赋予async属性,该属性将尽快运行内联模块脚本,而不是等待解析器完成,也不会等待以相对于其他脚本的任何特定顺序运行异步脚本。

这里有一个非常有用的时间轴图,显示了不同脚本组合的获取和执行,包括本文中的模块脚本:Javascript模块加载。

浏览器将按照找到脚本的顺序执行脚本。如果调用外部脚本,它将阻塞页面,直到脚本被加载和执行。

为了验证这一事实:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

动态添加的脚本在被追加到文档后立即执行。

为了验证这一事实:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

警报的顺序是“追加”->“你好!”- - - - - - >“最终”

如果在一个脚本中,你试图访问一个还没有到达的元素(例如:<script>do something with #blah</script><div id="blah"></div>),那么你会得到一个错误。

总的来说,您可以包含外部脚本,然后访问它们的函数和变量,但前提是退出当前的<script>标记并开始一个新的标记。

@addyosmani的一个很好的总结

无耻地抄袭https://addyosmani.com/blog/script-priorities/

在测试了许多选项之后,我发现以下简单的解决方案是按照在所有现代浏览器中添加脚本的顺序加载动态加载的脚本

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])

我在理解如何在onload事件发生之前执行嵌入式模块脚本时遇到了麻烦。上面的回答很有帮助,但让我补充一个部分的答案,关于什么解决了我误解“脚本的加载和执行顺序”的特定问题。

我第一次使用……这导致了一个奇怪的问题,它在正常加载页面时工作,但在FireFox的调试器中运行时不工作。这使得调试变得非常困难。

注意:类型为“module”的脚本总是有一个隐式的“deferred”属性,这意味着它们不会停止html的解析,这意味着onload-event可以在脚本执行之前发生。我不想那样。但我确实想使用type="module"使我未导出的JavaScript函数和变量对同一页面上的其他脚本不可见。

我尝试了不同的选项,但多亏了上面的答案,我得到了一个见解,如果你添加async -属性到一个模块类型的脚本,这意味着脚本异步加载,但一旦加载它立即执行。

但在我的例子中,这是一个嵌入在HTML页面中的脚本。因此,这意味着不需要“异步”加载。它已经与页面一起加载,因为它是嵌入在页面中的。因此,它与这个更改立即执行—这是我想要的。

因此,我认为有必要指出这个特定的情况,因为它有点违反直觉:要立即执行一个嵌入式脚本,必须将ASYNC属性添加到它的标记中。

通常,人们可能认为“异步”意味着某些事情以异步方式发生,不确定顺序,而不是立即发生。但要意识到的是,“async”导致异步加载,但加载完成后立即执行。当脚本被嵌入时,不需要加载,因此可以立即执行。

摘要:使用

     <script type="module" async> ... </script>

让嵌入到html页面的模块脚本立即执行。

完美匹配您的查询!

如果没有一个解决方案为您工作,那么请参考下面的解决方案,我已经从我的方面。

我也在寻找解决方案,但在搜索了很多之后,我总结了我的代码如下,这对我来说是完美的!

这是有用的,当你想要这样的功能,在前一个脚本完全加载后,然后只加载下一个脚本!

只需创建一个名为jsLoadScripts.js的文件,并将其插入到头部或主体的底部。

//From Shree Aum Software Solutions
//aumsoftwaresolutions@gmail.com

//script incrementor for array
scriptIncrementor = 0;

//define your script urls here
let scripts = [
    "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
    "jsGlobalFunctions.js",
    "jsDateParser.js",
    "jsErrorLogger.js",
    "jsGlobalVariables.js",
    "jsAjaxCalls.js",
    "jsFieldsValidator.js",
    "jsTableClickEvent.js",
    "index.js",
    "jsOnDocumentReady.js",
];

//it starts with the first script and then adds event listener to it. which will load another script on load of it. then this chain goes on and on by adding dynamic event listeners to the next scripts! 
function initializeScripts() {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = scripts[scriptIncrementor];
    document.head.appendChild(script);
    script.addEventListener("load", function () {
        loadNextScript();
        scriptIncrementor++;
    });
}

// this function adds event listener to the scripts passed to it and does not allow next script load until previous one has been loaded!
function loadNextScript() {
    if (scriptIncrementor != scripts.length - 1) {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = scripts[scriptIncrementor + 1];
        document.head.appendChild(script);
        script.addEventListener("load", function () {
            loadNextScript();
            scriptIncrementor++;
        });
    }
}

// start fetching your scripts
window.onload = function () {
    initializeScripts();
};

这可能会导致一些与速度相关的问题,因此,您可以根据自定义需求调用函数initializeScripts() !