如何从 CDN 异步加载多个文件(但同步执行)?

时间:2020-12-28 12:24:28

标签: javascript html dom loading

在下载多个常用的 javascript/css 文件(例如 boostrap 和 jquery)时,许多主题(例如 this one)推荐使用 CDN,其中一个主要参数可以用于异步加载它们.

这是如何工作的?据我所知,标头中的 <script> 标记是同步读取的,因此在第一个 CDN 文件完成之前,它实际上不会查看第二个 CDN 文件。

  <script src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

如何使页面下载脚本异步,但执行同步?还是默认情况下确实发生了某种情况?那么 CSS 文件呢,我的

  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">

在这个意义上有什么不同吗?我想在将我自己的故障转移添加到本地代码之前正确理解加载过程(如果 CDN 关闭),以防止卡住同步下载。

(请注意,尽管标题几乎相同,但这并不是 this question 的副本,后者是关于动态加载脚本的。)

另请注意,我不能使用 defer(至少以我所知道的普通方式),因为这会阻止我在 CDN 关闭时添加所述故障转移,例如

<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script>
<script>    $.fn.modal || document.write('<script src="Script/bootstrap.min.js">\x3C/script>')</script>

只需添加 defer 就会被破坏。

2 个答案:

答案 0 :(得分:1)

我认为您仍然可以使用 defer,只需将回退代码放入事件处理程序中...

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

<块引用>

defer

这个布尔属性被设置为向浏览器表明 脚本应该在文档被解析后执行,但是 在触发 DOMContentLoaded 之前。

具有 defer 属性的脚本将阻止 DOMContentLoaded 事件从触发直到脚本加载并完成评估。

[...]

具有 defer 属性的脚本将按照以下顺序执行 它们出现在文档中。

...所以 DOMContentLoaded 可能是一个不错的选择。

或者,你也可以把回退代码放到一个单独的.js文件中,然后它也可以用defer加载,依赖于引用的底部,所以有序执行。

答案 1 :(得分:1)

更多的是关于并行而不是异步。 (它们当然是相关的,但 CDN 与限制来自同一来源的多次下载有关的论点是关于并行性的。)

<块引用>

如何让页面异步下载脚本,同步执行?

任何不错的浏览器,当给出您显示的三个脚本标签时,都会并行下载它们(达到其从同一站点并行的限制),然后按顺序执行它们。您无需执行任何操作即可实现这一目标。浏览器会提前读取 HTML 以查找要获取的资源。

使用 document.write 添加回退脚本可能会使浏览器执行此操作的能力复杂化,甚至阻止它,但您可以使用 <link rel="preload" as="script" href="..."> (more on MDN) 以声明方式确保它。结合失败的 CDN 资源的回退脚本,它可能看起来像这样:

<head>
<!-- ... -->
<link rel="preload" as="script" href="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
<link rel="preload" as="script" href="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js">
<link rel="preload" as="script" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js">
</head>
<body>
<!-- ... -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
</body>
</html>

请注意,这不会预加载回退。您可以,但是即使 CDN 正在工作,您也会加载它们,这会浪费最终用户的带宽。回退将用于 CDN 不可用的假设临时降级情况,在这种情况下,降级的用户体验可能没问题。 (您甚至可以在安排回退时向用户显示问题指示符,例如 Gmail 的“某事比平时花费的时间更长”指示符。)


如果您因重复网址而感到困扰,并且您可以少量使用 document.write(就像您看起来那样),您可以通过执行以下操作来避免重复网址:

<head>
<!-- ... -->
<script>
var scripts = [
    {
        url: "//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
        okay: function() { return /*check it loaded*/; }
    },
    {
        url: "//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js",
        okay: function() { return /*check it loaded*/; }
    },
    {
        url: "//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js",
        okay: function() { return /*check it loaded*/; }
    },
];
scripts.forEach(function(script) {
    document.write('<link rel="preload" as="script" href="' + script.url + '">');
});
</script>
</head>
<body>
<!-- ... -->
<script>
scripts.forEach(function(script, index) {
    var fallback = script.url.substring(script.url.lastIndexOf('/') + 1);
    document.write('<script src="' + script.url + '"><\/script>');
    document.write('<script>if (!scripts[' + index + '].okay()) document.write(\'<script src="' + fallback + '"><\\/script>\');<\/script>');
});
</script>
</body>
</html>

(由于这些都是内联脚本,您不太可能转译,我将语法保留为 ES5 级别,以防您必须支持过时的环境。)

相关问题