我想在整个页面加载之前运行一些 javascript。这可能吗?还是代码开始在 </html>上执行?
</html>
不仅仅是 可以你,如果你不想的话,你还得特别努力 没有。 : -)
当浏览器在解析 HTML 时遇到一个经典的 script标记时,它停止解析并移交给运行脚本的 JavaScript 解释器。在脚本执行完成之前,解析器不会继续(因为脚本可能对解析器应该处理的输出标记执行 document.write调用)。
script
document.write
这是默认的行为,但是您有一些延迟脚本执行的选项:
使用 JavaScript 模块。在完全解析 HTML 并创建初始 DOM 之前,type="module"脚本是 延期。这不是使用模块的主要原因,但它是原因之一:
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 这样的捆绑包。
在一个经典的 script 标记上使用 defer属性:
defer
<script defer src="./my-code.js"></script>
与模块一样,my-code.js中的代码将与 HTML 解析并行获取和解析,但在 HTML 解析完成之前不会是 快跑。但是,defer不处理内联脚本内容,只处理通过 src引用的外部文件。
my-code.js
src
我不认为这是 你想要的,但是你可以使用 async属性来告诉浏览器与 HTML 解析并行获取 JavaScript 代码,然后尽快运行它,即使 HTML 解析还没有完成。您可以将其放在 type="module"标记上,或者在经典的 script标记上使用它来代替 defer。
async
将 script标记放在文档的末尾,就在结束的 </body>标记之前:
</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标记、 defer、 async、 type="module"和 type="module" async,以及获取和运行 JavaScript 代码的时间:
type="module" async
下面是一个默认行为的例子,一个原始的 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代码的详细信息,请参阅 我的答案就在这里。)
NodeList
当您运行它时,您会看到绿色的“段落1”,但是“段落2”是黑色的,因为脚本与 HTML 解析同步运行,所以它只找到了第一个段落,而不是第二个。
相比之下,这里有一个 type="module"脚本:
<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。)
forEach
在这个现代世界中,PrototypeJS、 jQuery、 ExtJS、 Dojo 和其他大多数当时提供(现在仍然提供)的“ ready”特性对于 DOMContentLoaded事件没有真正的价值; 只需使用模块或 defer。即使在过去,使用它们也没有太多的理由(而且它们经常被错误地使用,当整个 jQuery 库被加载时,因为 script在 head中,而不是在文档之后) ,这是 谷歌的一些开发人员早期就标记出来的。这也是 YUI 推荐将脚本放在 body末尾的部分原因,再次回到过去。
DOMContentLoaded
head
body
您可以在任何时候运行 javascript 代码。AFAIK 在浏览器到达它所在的 < script > 标记时执行。但是您不能访问尚未加载的元素。
因此,如果需要访问元素,应该等到加载 DOM (这并不意味着加载了整个页面,包括图像和内容)。它只是文档的结构,加载时间要早得多,所以通常不会注意到延迟) ,使用 jQuery 中的 DOMContentLoaded事件或 $.ready函数。
$.ready