如何延迟内联Javascript?

How to defer inline Javascript?

我有以下 html 代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/blazy/1.8.2/blazy.min.js" defer></script>
    <script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw=" crossorigin="anonymous" defer></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js" defer></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous" defer></script>
    <!-- 26 dec flexslider js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer></script>
    <script defer>
    (function($) {
        $(document).ready(function() {
            //do something with b-lazy plugin, lightbox plugin and then with flexslider
        });
    })(jQuery);
    </script>
</head>
<body>
</body>
</html>

我得到一个错误,说 jQuery 没有定义。现在,即使我从我的内联 JS 代码中删除 defer,它也会说 jQuery 未定义。出于某种原因,我必须将 jQuery 插件保留在头部并保持我的 JS 代码内联。我的问题是:

  1. defer 属性存在时,为什么内联 Javascript 代码不会被延迟?

  2. 有没有办法模仿我的内联 Javascript 代码的延迟行为?如果需要,我可以把它放在 body 标签的末尾。

来自 MDN 文档:

defer
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. The defer attribute should only be used on external scripts.

这称为 IIFE (立即调用的函数表达式),它在 DOM 可用之前执行。所以,在那种情况下 jQuery 是未定义的,因为它不在 DOM 中。

具有 defer 属性的脚本按指定顺序加载,但不在 文档本身加载之前。由于 deferscript 标签没有影响,除非它们也具有 src 属性,因此第一个执行的脚本是您的内联脚本。所以那个时候jQuery还没有加载

您至少可以通过两种方式解决此问题:

  • 将您的内联脚本放入 .js 文件并使用 src 属性引用它(除了您已经拥有的 defer 属性) , 或

  • 让您的内联脚本等待文档和延迟脚本的加载。 DOMContentLoaded 事件将在发生时触发:

    <script>
        window.addEventListener('DOMContentLoaded', function() {
            (function($) {
                //do something with b-lazy plugin, lightbox plugin and then with flexslider
            })(jQuery);
        });
    </script>
    

注意:请注意,在后一种情况下,不再包含 $(document).ready(function(),因为它会等待相同的事件 (DOMContentLoaded)。您 可以 仍然像在原始代码中那样包含它,但是 jQuery 只会立即 执行回调,这不会实际差异。

您可以从脚本中创建一个 Base64 URL 并将其放入 src!

<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
        defer>
</script>

我构建了一个快速测试以查看它的运行情况。 如果 defer 正在工作,您应该最后看到带有 Hello world! 的警报:

<script defer>
  alert('Why no defer?!?');
</script>

<!-- alert('Hello world!'); -->
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
        defer></script>

<script>
  alert('Buh-bye world!');
</script>

手动操作有点费力,所以如果您有幸以某种方式(Handlebars、Angular 等)编译您的 HTML,那将大有帮助。

我目前正在使用:

<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
        defer>
</script>

Defer/async 脚本标签不够好

有一个常识,你应该使用 <script src=".." async defer> (或者在分配 src 之前设置 script.async = true,当你从 JS 执行它时) and/or 把你的脚本在页面的最底部,以便尽可能快地加载页面并呈现给用户。

defer.js(注意:我是这个脚本的作者)是用普通的JavaScript编写的,使得延迟加载其他内容更加快速和性能。您可以有效地延迟任何 javascript 文件以及内联脚本块。

如果您的页面只是一个 HTML 页面,用一些 JavaScript 进行了增强,那么只需要 <script async> 就可以了。浏览器解析和执行这些脚本需要时间,并且每次 UI 更改都可能重新布局您的布局,使您的加载速度更慢,没有人喜欢盯着空白的白页;用户不耐烦,会很快离开。

在各种情况下,使用 asyncdefer 提供的页面速度并不比 defer.js 快。

延迟加载纯文本数据 URI - Chrome 和 FF

#noLib #vanillaJS

建议不要在 Cross Browser PRODuction 上使用

直到 MS IE 死掉并且 MS Edge 将采用 Chromium 开源 ;)

延迟脚本的唯一方法是外部文件Data_URI(不使用事件 DOMContentLoaded)

推迟

规格 script#attr-defer (MDN web docs): "This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.)"

Data_URI

规格Data_URI

正确 type "text/javascript" 根本不需要 base64... ;)

使用纯文本 所以你可以使用简单的:

<script defer src="data:text/javascript,

//do something with b-lazy plugin, lightbox plugin and then with flexslider

lightbox.option({
  resizeDuration: 200,
  wrapAround: true
})

">

是的,这有点奇怪,但是 <script type="module"> 默认情况下会延迟,没有其他选项可以 按照确切的顺序混合 :

  • 模块外部文件 - 默认延迟
  • 模块内联脚本 - 默认延迟
  • 外部文件 - 可选延迟
  • 内联脚本 - 只有这个 hack - 据我所知(没有 libraries/frameworks)

您也可以使用 type="module":

<meta charset="utf-8">

<script type="module">
let t = document.getElementById('top');
console.log(t);
</script>

<h1 id="top">Top Questions</h1>

https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type

我检查了所有建议的解决方案,但都有其缺点。所以我自己发明了。

将此内联脚本放入您的 head 标记中或紧跟在 body 标记开始之后:

<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>

这个衬里将收集您想要延迟的所有内联脚本,并在文档完全加载后分别 运行 它们。现在任何时候你需要 运行 延迟的内联脚本,只需像这样注册它:

<script>
  alert('This alert will show immediately.');

  Defer.push(function() {
   alert('This alert will show only after document is loaded.');
   // You can use anything which is not loaded yet, like jQuery
   $(".selector").doSomeJqueryStuff();
  });

  // You can use it as many times as you like and in any place of your DOM.
  Defer.push(function() {
    // Any inline code you want to defer
  });
</script>

此内联脚本将 运行 仅在加载文档后才会出现。这意味着您可以 运行 内联 jQuery 脚本,让您的 jQuery 留在 DOM.

的末尾

您可以使用此数据 url 作为 src 属性

data:application/javascript,eval(document.currentScript.textContent)

它采用当前脚本标签并评估其内容,就好像它在外部文件中一样。 它也适用于惰性属性。 它使用 IE 浏览器不支持的 document.currentScript

<script defer src="https://cdn.jsdelivr.net/npm/vue"></script>
<script defer src="data:application/javascript,eval(document.currentScript.textContent)">
    console.log('defered', typeof Vue); // function
</script>
<script>
    console.log('not defered', typeof Vue); // undefined
</script>

如果问题是 jQuery 变量 $ 没有定义,也许你可以创建一个伪造的 $ 函数,returns 一个等待 DOMContentLoaded 的就绪函数?

我所有的内联脚本都有 $(document).ready(..... 问题是 $ 没有定义,因为头脚本被推迟了。

所以,只需在 head 的内联脚本中添加一个假的 $ 即可:

<script type="text/javascript">
var $ = function(element) {
    return {
        ready: function(callback) {
            // in case the document is already rendered
            if (document.readyState!="loading") callback();
            // modern browsers
            else if (document.addEventListener) 
                document.addEventListener("DOMContentLoaded", callback);
            // IE <= 8
            else document.attachEvent("onreadystatechange", function(){    
                if (document.readyState=="complete") callback();
            });
        }
    };
};
</script>

有一种不那么晦涩的方式来完成延迟,它不需要回调、承诺或数据 url ......尽管它在后台做了一些 DOM 操作。微型库(109 字节 compressed/gziped)https://www.npmjs.com/package/deferscript 让您来做这件事。下面的例子是根据原来的post.

<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer>
</script>
<script src="./deferscript.js" defer>
    (function($) {
        $(document).ready(function() {
            //do something with b-lazy plugin, lightbox plugin and then with flexslider
        });
    })(jQuery);
</script>

您所要做的就是插入一个值为 ./deferscript.jssrc 属性。