我可以在整个页面加载之前运行 javascript 吗?

我想在整个页面加载之前运行一些 javascript。这可能吗?还是代码开始在 </html>上执行?

263482 次浏览

不仅仅是 可以你,如果你不想的话,你还得特别努力 没有。 : -)

当浏览器在解析 HTML 时遇到一个经典的 script标记时,它停止解析并移交给运行脚本的 JavaScript 解释器。在脚本执行完成之前,解析器不会继续(因为脚本可能对解析器应该处理的输出标记执行 document.write调用)。

这是默认的行为,但是您有一些延迟脚本执行的选项:

  1. 使用 JavaScript 模块。在完全解析 HTML 并创建初始 DOM 之前,type="module"脚本是 延期。这不是使用模块的主要原因,但它是原因之一:

    <script type="module" src="./my-code.js"></script>
    <!-- Or -->
    <script type="module">
    // Your code here
    </script>
    

    代码将被提取(如果它是独立的)并与 HTML 解析并行解析,但是在 HTML 解析完成之前不会是 快跑。(如果您的模块代码是内联的,而不是在它自己的文件中,那么它也会被推迟,直到完成 HTML 解析。)

    当我在2010年第一次写这个问题的答案时,这是不可用的,但是在2020年,所有主要的现代浏览器都支持模块,如果你需要支持旧的浏览器,你可以使用像 Webpack 和 Rollup.js 这样的捆绑包。

  2. 在一个经典的 script 标记上使用 defer属性:

    <script defer src="./my-code.js"></script>
    

    与模块一样,my-code.js中的代码将与 HTML 解析并行获取和解析,但在 HTML 解析完成之前不会是 快跑但是defer不处理内联脚本内容,只处理通过 src引用的外部文件。

  3. 我不认为这是 想要的,但是你可以使用 async属性来告诉浏览器与 HTML 解析并行获取 JavaScript 代码,然后尽快运行它,即使 HTML 解析还没有完成。您可以将其放在 type="module"标记上,或者在经典的 script标记上使用它来代替 defer

  4. script标记放在文档的末尾,就在结束的 </body>标记之前:

    <!doctype html>
    <html>
    <!-- ... -->
    <body>
    <!-- The document's HTML goes here -->
    <script type="module" src="./my-code.js"></script><!-- Or inline script -->
    </body>
    </html>
    

    这样,即使代码一遇到就运行,上面的 HTML 定义的所有元素都存在并可以使用。

    过去,这会在一些浏览器上造成额外的延迟,因为在遇到 script标记之前,浏览器不会开始获取代码,但现在的浏览器会提前扫描并开始预取。尽管如此,这仍然是目前的第三种选择,模块和 defer都是更好的选择。

规范 有一个有用的图表显示原始的 script标记、 deferasynctype="module"type="module" async,以及获取和运行 JavaScript 代码的时间:

enter image description here

下面是一个默认行为的例子,一个原始的 script标记:

.found {
color: green;
}
<p>Paragraph 1</p>
<script>
if (typeof NodeList !== "undefined" && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
document.querySelectorAll("p").forEach(p => {
p.classList.add("found");
});
</script>
<p>Paragraph 2</p>

(有关 NodeList代码的详细信息,请参阅 我的答案就在这里。)

当您运行它时,您会看到绿色的“段落1”,但是“段落2”是黑色的,因为脚本与 HTML 解析同步运行,所以它只找到了第一个段落,而不是第二个。

相比之下,这里有一个 type="module"脚本:

.found {
color: green;
}
<p>Paragraph 1</p>
<script type="module">
document.querySelectorAll("p").forEach(p => {
p.classList.add("found");
});
</script>
<p>Paragraph 2</p>

注意它们现在都是绿色的; 代码直到完成 HTML 解析才运行。对于包含外部内容(但不包含内联内容)的 defer script也是如此。

(这里不需要进行 NodeList检查,因为任何现代浏览器支持模块都已经在 NodeList上安装了 forEach。)

在这个现代世界中,PrototypeJS、 jQuery、 ExtJS、 Dojo 和其他大多数当时提供(现在仍然提供)的“ ready”特性对于 DOMContentLoaded事件没有真正的价值; 只需使用模块或 defer。即使在过去,使用它们也没有太多的理由(而且它们经常被错误地使用,当整个 jQuery 库被加载时,因为 scripthead中,而不是在文档之后) ,这是 谷歌的一些开发人员早期就标记出来的。这也是 YUI 推荐将脚本放在 body末尾的部分原因,再次回到过去。

您可以在任何时候运行 javascript 代码。AFAIK 在浏览器到达它所在的 < script > 标记时执行。但是您不能访问尚未加载的元素。

因此,如果需要访问元素,应该等到加载 DOM (这并不意味着加载了整个页面,包括图像和内容)。它只是文档的结构,加载时间要早得多,所以通常不会注意到延迟) ,使用 jQuery 中的 DOMContentLoaded事件或 $.ready函数。