【问题标题】:How to defer inline Javascript?如何推迟内联 Javascript?
【发布时间】:2017-05-14 16:20:21
【问题描述】:

我有以下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 代码中模仿延迟行为?如果需要,我可以把它放在正文标签的末尾。

【问题讨论】:

  • 将您的内联 javascript 移动到外部文件,然后将其推迟 :)
  • 如果您不想使用 html5,也可以通过编程方式内联外部 javascript
  • @mike510a 由于某种原因,我无法将其放入外部文件中。它是由 php 生成的,我不了解 php,所以我无法更改该代码。
  • 你不必推迟你的 jQuery 库。它不必在文档的头部——你可以将它移动到正文的末尾——只要它来了在内联脚本之前并且不被延迟它会没事的。
  • 那么请找一些了解 php 的人(最好是编写您正在使用的代码的人)。或者只是将defer 放在任何地方。

标签: javascript jquery html deferred-execution


【解决方案1】:

具有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 只会立即执行回调,这没有实际区别。

【讨论】:

  • 感谢您的回答。确定在所有延迟脚本都加载完毕后会触发DOMContentLoaded 事件吗?
  • 是的,这是肯定的。
  • 虽然这是一个很好的解决方案,但它仍然是渲染阻塞,这可能首先破坏了延迟 jQuery 的目的。对于真正的非阻塞执行,您最好使用 this trick 模拟内联 javascript 块上的 'defer' 属性。
  • 我必须说我从来没有遇到过显示 JS 在 DOM 就绪的情况下运行但 CSSOM 尚未就绪的问题。也许(!)如果 JS 访问 CSS 属性,这可能会阻止 JS 执行,直到 CSSOM 准备好,但我真的不知道。这可能是一个新问题的主题(但请检查之前是否没有被问过)。
  • 在某些情况下,如果您这样做,代码将不再工作。例如,如果代码有document.write,那么它的行为将完全不同。
【解决方案2】:

您可以从脚本中创建一个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>

【讨论】:

  • 这超出了评估;不仅很难找到错误,还必须阅读 base64 :)
  • 不需要base64编码,直接用这个:data:text/javascript,alert('hello world')。大多数人没有意识到 dataURL 本身并不需要编码为 base64。工作示例:jsbin.com/vazuruxica/edit?html,output 我建议对您的答案进行编辑:)
【解决方案3】:

来自 MDN 文档:

推迟
此布尔属性设置为向浏览器指示脚本将在文档被解析之后但在触发DOMContentLoaded 之前执行。 defer 属性只能用于外部脚本。

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

【讨论】:

  • 但是如何处理内联脚本呢?为什么他们不按照声明的顺序?
  • @user31782 这是因为(jQuery) 部分要求在访问之前定义jQuery - 并且推迟脚本意味着在该脚本中定义任何内容,直到其他所有内容之后(包括内联代码)
  • 那是因为你有 IIFE 语法。它会立即运行,并且不会等待 dom 加载。
  • 在您最后一次编辑时,它仍然会抛出错误jQuery is not defined
【解决方案4】:

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

#noLib #vanillaJS

建议不要在跨浏览器生产上使用

直到 MS IE 消亡,MS Edge 将采用 Chromium 开源 ;)

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

推迟

spec script#attr-defer (MDN web docs): "如果 src 属性不存在(即对于内联脚本),则不能使用此属性,在这种情况下它将无效。)"

数据_URI

规范Data_URI

正确键入“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
})

">

是的,这有点奇怪,但 &lt;script type="module"&gt; 默认会延迟,没有其他选项可以按确切顺序混合以下内容

  • 模块外部文件 - 默认延迟
  • 模块内联脚本 - 默认延迟
  • 外部文件 - 可选延迟
  • 内联脚本 - 仅使用此 hack - 据我所知(没有库/框架)

【讨论】:

  • 非常好的解决方案。这是否适用于所有模块浏览器(Edge 非 Chromium 除外)?
【解决方案5】:

延迟/异步脚本标签不够好

众所周知,您应该使用 &lt;script src=".." async defer&gt;(或在分配 src 之前设置 script.async = true,当您从 JS 执行此操作时)和/或将您的脚本放在页面的最底部,以便尽可能快地加载页面并呈现给用户。

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

如果您的页面只是一个使用一些 JavaScript 增强的 HTML 页面,那么您只需 &lt;script async&gt; 就可以了。浏览器解析和执行这些脚本需要时间,而且每一次 UI 的变化都可能会重排你的布局,让你的加载速度变慢,没有人喜欢盯着一张白纸看;用户不耐烦,很快就会离开。

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

【讨论】:

    【解决方案6】:

    你也可以使用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

    【讨论】:

      【解决方案7】:

      您可以将此数据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>

      【讨论】:

        【解决方案8】:

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

        把这个内联脚本放到你的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 的末尾。

        【讨论】:

          【解决方案9】:

          如果问题是 jQuery 变量 $ 没有定义,也许你可以创建一个假的 $ 函数,返回一个等待 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>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-02-15
            • 1970-01-01
            • 2018-05-31
            • 2013-12-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多