【问题标题】:Possible to defer loading of jQuery?可以推迟 jQuery 的加载吗?
【发布时间】:2011-08-16 16:52:00
【问题描述】:

让我们面对现实吧,jQuery/jQuery-ui 下载量很大。

Google 建议使用deferred loading of JavaScript 来加快初始渲染速度。我的页面使用 jQuery 来设置一些位于页面下方的选项卡(主要是在初始视图之外),我想将 jQuery 推迟到页面呈现之后。

Google 的延迟代码在页面加载后通过钩子到正文 onLoad 事件向 DOM 添加一个标签:

<script type="text/javascript">

 // Add a script element as a child of the body
 function downloadJSAtOnload() {
 var element = document.createElement("script");
 element.src = "deferredfunctions.js";
 document.body.appendChild(element);
 }

 // Check for browser support of event handling capability
 if (window.addEventListener)
 window.addEventListener("load", downloadJSAtOnload, false);
 else if (window.attachEvent)
 window.attachEvent("onload", downloadJSAtOnload);
 else window.onload = downloadJSAtOnload;

</script>

我想以这种方式推迟 jQuery 的加载,但是当我尝试它时,我的 jQuery 代码找不到 jQuery(对我来说并非完全出乎意料):

$(document).ready(function() {
    $("#tabs").tabs();
});

所以,看来我需要找到一种方法来推迟我的 jQuery 代码的执行,直到 jQuery 被加载。如何检测到添加的标签已经完成加载和解析?

作为推论,asynchronous loading 似乎也可能包含答案。

有什么想法吗?

【问题讨论】:

  • 为什么不在页面底部而不是头部包含 jQuery(和您自己的 JS 文件)?
  • Google 说(在我提供的第一个链接中),“必须先下载、解析和执行脚本,然后浏览器才能开始呈现网页”。这意味着在页面底部加载 jQuery 意味着在 jQuery 被解析和执行之前页面仍然不会被渲染。有没有办法异步加载它并让它在页面加载后完成它的工作?
  • 你不能defer你所有的外部脚本吗?它们应该仍按列出的顺序执行。
  • @nilskp 对于这个问题,似乎有一个现有的和公认的最佳实践,这就是我提出它的原因,而不是尝试自己推出。各种浏览器在这一领域存在大量陷阱。
  • 如果您推迟所有脚本,它们将(或至少应该)以相同的顺序加载,只需在浏览器认为最佳时加载即可。然而,异步将允许 js 文件以浏览器认为合适的任何顺序加载。因此,如果您推迟包括 JQuery 库在内的所有脚本文件,您应该不会有任何问题。

标签: javascript jquery pagespeed


【解决方案1】:

试试这个,这是我不久前从 jQuerify 小书签编辑的。我经常使用它来加载 jQuery 并在加载后执行一些东西。您当然可以将那里的 url 替换为您自己的 url 到您的自定义 jquery。

(function() {
      function getScript(url,success){
        var script=document.createElement('script');
        script.src=url;
        var head=document.getElementsByTagName('head')[0],
            done=false;
        script.onload=script.onreadystatechange = function(){
          if ( !done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') ) {
            done=true;
            success();
            script.onload = script.onreadystatechange = null;
            head.removeChild(script);
          }
        };
        head.appendChild(script);
      }
        getScript('http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js',function(){
            // YOUR CODE GOES HERE AND IS EXECUTED AFTER JQUERY LOADS
        });
    })();

我真的会将 jQuery 和 jQuery-UI 合并到一个文件中并使用一个 url。如果您真的想单独加载它们,只需链接 getScripts:

getScript('http://myurltojquery.js',function(){
        getScript('http://myurltojqueryUI.js',function(){
              //your tab code here
        })
});

【讨论】:

  • 看起来像是 tylermwashburn 建议的花哨版本。通过推迟一切,我认为它设置了一个依赖顺序问题:MyCode 依赖于 JQueryUI 依赖于 JQuery。因此,需要一些代码以正确的顺序加载每个代码。你给了我一些想法。
  • 其实这和 tylermwashburn 的建议完全不同。这对您所做的是加载 jQuery,然后加载 jQuery ui,然后执行您的 jquery/jqueryUI 相关代码,这就是您所要求的...此代码是跨浏览器的(这就是它看起来“花哨”的原因)和每次都为我工作。至于 DOMready 代码,我只是意识到它可能有点旧,我不会使用它。我想我在某个地方有一个更新的 domready...让我找到它。
  • 泰勒的建议很好。我要做的是使链接的 getscripts 成为正文加载事件的回调
  • 我已经成功地使用了它。我还将第 10 行的 success(); 修改为 if(success &amp;&amp; getClass.call(success) == '[object Function]') { success(); }only 如果它不是 null 并且它是一个函数,这将执行成功函数。这允许您只调用 getScript("myScript.js") 而无需函数调用。
  • @Geniusknight 是的,它是 getScript()! :) 它可用于延迟多个,如果存在依赖项,则需要为它们编写代码以确保在尝试运行任何依赖它的子脚本之前加载它们。使用多维数组是一种相当简单的方法。
【解决方案2】:

由于这是一个重要主题的排名靠前的问题,让我如此大胆地根据@valmarv 和@amparsand 先前的回答提供我自己的看法。

我正在使用多维数组来加载脚本。将它们之间没有依赖关系的那些组合在一起:

var dfLoadStatus = 0;
var dfLoadFiles = [
      ["http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"],
      ["http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/jquery-ui.min.js",
       "/js/somespecial.js",
       "/js/feedback-widget.js#2312195",
       "/js/nohover.js"]
     ];

function downloadJSAtOnload() {
    if (!dfLoadFiles.length) return;

    var dfGroup = dfLoadFiles.shift();
    dfLoadStatus = 0;

    for(var i = 0; i<dfGroup.length; i++) {
        dfLoadStatus++;
        var element = document.createElement('script');
        element.src = dfGroup[i];
        element.onload = element.onreadystatechange = function() {
        if ( ! this.readyState || 
               this.readyState == 'complete') {
            dfLoadStatus--;
            if (dfLoadStatus==0) downloadJSAtOnload();
        }
    };
    document.body.appendChild(element);
  }

}

if (window.addEventListener)
    window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
    window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;

它在加载后加载第一个 jquery,它会立即继续加载其他脚本。您可以通过添加到页面上任何位置的数组来轻松添加脚本:

dfLoadFiles.push(["/js/loadbeforeA.js"]);
dfLoadFiles.push(["/js/javascriptA.js", "/js/javascriptB.js"]);
dfLoadFiles.push(["/js/loadafterB.js"]);

【讨论】:

  • 对于
【解决方案3】:

这是对modern approach for async/defer javascript loading 的一个很好的描述。但它不适用于内联脚本

<script type="text/javascript" src="/jquery/3.1.1-1/jquery.min.js" defer></script>
<script type="text/javascript" defer>
    $(function () {   //  <- jquery is not yet initialized
      ...
    });
</script>

@nilskp 建议了最简单的异步加载解决方案 - externalize script:

<script type="text/javascript" src="/jquery/3.1.1-1/jquery.min.js" defer></script>
<script type="text/javascript" src="resources/js/onload.js" defer></script>

【讨论】:

    【解决方案4】:
    element.addEventListener("load", function () {
        $('#tabs').tabs()
    }, false);
    

    试试看。

    【讨论】:

    • 是的!你明白了!所以,我认为这个和“与号”答案的某种组合将是最终的解决方案。看来我需要链式加载 jQuery,然后是 jQuery-UI,然后是依赖于两者的页面代码。
    • IE
    【解决方案5】:

    我在 async/defered jquery 脚本标签之后添加了这段代码,这定义了一个临时函数 $ 它将在所有内容加载完成后累积需要运行的任何内容,然后一旦我们完成使用 $此时将被覆盖以执行功能。使用这段代码,无需在文档中进一步更改 jQuery onload 语法。

    <script defer async src="https://code.jquery.com/jquery-2.2.0.min.js">
    <script>
        var executeLater = [];
        function $(func) {
            executeLater.push(func);
        }
        window.addEventListener('load', function () {
            $(function () {
                for (var c = 0; c < executeLater.length; c++) {
                    executeLater[c]();
                }
            });
        })
    </script>
    

    ....然后...

    <script>
        $(function() {
            alert("loaded");
        });
    </script>
    

    【讨论】:

    • 完美工作刚刚添加了defer async。谢谢
    【解决方案6】:

    将 jQuery 和依赖于 jQuery 的代码放在 HTML 文件的末尾。

    编辑:更清楚一点

    <html>
    <head></head>
    <body>
        <!-- Your normal content here -->
        <script type="text/javascript" src="http://path/to/jquery/jquery.min.js"></script>
        <script>//Put your jQuery code here</script>
    </body>
    </html>
    

    【讨论】:

    • 这似乎是“次佳”的解决方案,但实际上并不相同。虽然它确实允许加载其他资源,但 Google 说(在我提供的第一个链接中),“必须先下载、解析和执行脚本,然后浏览器才能开始呈现网页”。这意味着在页面底部加载 jQuery 意味着在 jQuery 被解析和执行之前页面仍然不会被渲染。有没有办法异步加载它并让它在页面加载之后完成它的工作?
    • 这将非常容易测试。为自己制作一个漂亮的 10MB 大 JS 文件,里面装满了废话,并将其包含在页面底部。看看你的其他内容是否先加载。
    • kennis--是的,首先加载其他内容,但是,问题与用户体验有关。尤其是在移动浏览器上,解析 JavaScript 的时间会延迟用户与页面交互的时间。在 Chrome 开发人员工具中,jQuery 的“评估脚本”时间线是 DOMContentLoaded 和 Load 事件触发之前的一半时间。我能够使用 Google 代码(在问题中)延迟的脚本在这些事件之后才会加载或执行。我希望 jQuery 在页面呈现后加载/解析。
    • @Kevin,该链接还指出“......处理所有元素脚本下被阻止,直到浏览器从磁盘加载代码并执行它。”至少对于桌面浏览器,用户可以在页面完全加载之前开始与页面交互。从初始请求到用户可以与页面交互(不一定是 DOMContentLoaded 事件)的加载时间差是多少,您的脚本位于底部且根本没有脚本?
    • 我肯定会对这两种方法进行“基准测试”(尽你所能),以确保异步加载速度更快。看起来 Jeremy 有您最初寻找的代码。祝你好运!
    【解决方案7】:

    在某些情况下,您可以在加载 jquery 时触发事件。

    <script type="text/javascript">
        (function (window) {
    
            window.jQueryHasLoaded = false;
    
            document.body.addEventListener('jqueryloaded', function (e) {
                console.log('jqueryloaded ' + new Date() );
            }, false);
    
            function appendScript(script) {
                var tagS = document.createElement("script"), 
                    s = document.getElementsByTagName("script")[0];
                tagS.src = script.src;
                s.parentNode.insertBefore(tagS, s);
    
                if ( script.id == 'jquery' ) {
                    tagS.addEventListener('load', function (e) {
                        window.jQueryHasLoaded = true;
                        var jQueryLoaded = new Event('jqueryloaded');
                        document.body.dispatchEvent(jQueryLoaded);
                    }, false);
                }
            }
    
            var scripts = [
                {
                    'id': 'jquery',
                    'src': 'js/libs/jquery/jquery-2.0.3.min.js'
                },
                {
                    'src': 'js/myscript1.js'
                },
                {
                    'src': 'js/myscript2.js'
                }
            ];
    
            for (var i=0; i < scripts.length; i++) {
                appendScript(scripts[i]);
            }
    
        }(window));
    </script>
    

    然后将你的依赖包在一个函数中:

    // myscript1.js 
    (function(){ 
    
        function initMyjQueryDependency() {
            console.log('my code is executed after jquery is loaded!');
            // here my code that depends on jquery
        }
    
        if ( jQueryHasLoaded === true )
            initMyjQueryDependency();
        else
            document.body.addEventListener('jqueryloaded', initMyjQueryDependency, false);
    
    }());
    

    如果 jquery 在其他脚本之后完成加载,则在触发 jqueryloaded 事件时将执行您的依赖项。

    如果 jquery 已经加载,jQueryHasLoaded === true,你的依赖将被执行initMyjQueryDependency()

    【讨论】:

    • 感谢您提供这个新答案。
    【解决方案8】:

    在我看来,您所要做的就是 a) 将要在加载时运行的 jQuery 代码添加到 jQuery 文件的末尾,或者 b) 将其附加到 downloadJSAtOnload 函数,如下所示:

    <script type="text/javascript">
    
     // Add a script element as a child of the body
     function downloadJSAtOnload() {
     var element = document.createElement("script");
     element.src = "deferredfunctions.js";
     document.body.appendChild(element);
     $("#tabs").tabs(); // <==== NOTE THIS. This should theoretically run after the
                        // script has been appended, though you'll have to test this
                        // because I don't know if the JavaScript above will wait for
                        // the script to load before continuing
     }
    
     // Check for browser support of event handling capability
     if (window.addEventListener)
     window.addEventListener("load", downloadJSAtOnload, false);
     else if (window.attachEvent)
     window.attachEvent("onload", downloadJSAtOnload);
     else window.onload = downloadJSAtOnload;
    
    </script>
    

    【讨论】:

    • 这正是我尝试过的。在执行“选项卡”代码之前,它不会等待加载“deferredfunctions.js”(我用 jQuery.js 替换)。那么,如何让“标签”代码等待 jQuery?我可以把它放到同一个文件中,但如果我从 Google CDN 加载 jQuery 就不行了。
    • @Kevin R:加载 两个 脚本元素怎么样,其中第二个从您的服务器加载并包含您的 jQuery 代码?第一个当然是 jQuery 本身。
    • 将所有内容放入一个文件中确实有效。我之前尝试使用两个脚本元素来加载 jQuery/jQuery-UI,但也没有加载我的页面代码。让我试试……我怀疑确保先加载哪个会出现问题。
    • @Kevin R:大概第一个附加到 DOM 的代码将是第一个执行的,但也许由于您的代码很可能比 jQuery 小,它会首先被命中。使用用户代理服务器端并以这种方式控制包含的文件怎么样?我知道,这不是有史以来最好的事情,但如果您担心移动浏览器,可能值得研究一下。
    • 我可能一开始就试图推迟 jQuery 加载,但这似乎是合理的,这让Google PageSpeed 很高兴。通过 tylermwashburn 的答案链接事件似乎是按顺序加载所有内容的方法。另外,请查看JSL JavaScript Loader,这似乎与我正在尝试做的类似。非常感谢!
    【解决方案9】:

    以下代码应在窗口加载完成后加载您的脚本:

    <html>
    <head>
        <script>
        var jQueryLoaded = false;
        function test() {
            var myScript = document.createElement('script');
            myScript.type = 'text/javascript';
            myScript.async = true;
            myScript.src = jQueryLoaded ? 'http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.js' : 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/jquery-ui.min.js';
            document.body.appendChild(myScript);
    
            if(!jQueryLoaded){
                alert('jquery was loaded');
                jQueryLoaded = true;
                test();
            } else {
                alert('jqueryui was loaded');   
            }
        }
    
        if (window.addEventListener){
            alert('window.addEventListener');
            window.addEventListener("load", test, false);
        } else if (window.attachEvent){
            alert('window.attachEvent');
            window.attachEvent("onload", test);
        } else{
            alert('window.onload');
            window.onload = test;
        }
        </script>
    </head>
    <body>
    <p>Placeholder text goes here</p>
    </body>
    </html>
    

    在 Chrome、FF 和 IE9 中为我工作 - 如果有帮助,请告诉我

    【讨论】:

    • Jeremy,谢谢——不过请看我对 kennis 的回复(上图)。我希望 jQuery 在页面加载后加载/评估/解析/执行。
    • 编辑了我的响应以在 window.load/onload 事件触发后完成加载脚本
    • Jeremy,再次感谢 - 我将尝试“异步”属性...但是,请参阅下面发布的代码“Drackir”,这正是我尝试过的。结果:是的,jQuery 加载延迟,但“标签”代码(与 Drackir 显示它的方式相同)不等待延迟的 jQuery.js 加载,我得到“jquery undefined”。不知何故,我需要一个回调或事件来知道何时加载和解析 jquery.js。
    • 有点小技巧,但从技术上讲,你可以做一个忙等待循环,并在运行之前检查是否加载了 jQuery $("#tabs").tabs();。如果不是,请执行 setTimeout 并再次调用该函数。
    • 凯文,再编辑一次。为我工作让我知道它是否适用于您的标签。
    【解决方案10】:

    这是我的版本,它支持链接以确保脚本一个接一个地加载,基于 & 的代码:

    var deferredJSFiles = ['jquery/jquery', 'file1', 'file2', 'file3'];
    function downloadJSAtOnload() {
        if (!deferredJSFiles.length)
            return;
        var deferredJSFile = deferredJSFiles.shift();
        var element = document.createElement('script');
        element.src = deferredJSFile.indexOf('http') == 0 ? deferredJSFile : '/js/' + deferredJSFile + '.js';
        element.onload = element.onreadystatechange = function() {
            if (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')
                downloadJSAtOnload();
        };
        document.body.appendChild(element);
    }
    if (window.addEventListener)
        window.addEventListener('load', downloadJSAtOnload, false);
    else if (window.attachEvent)
        window.attachEvent('onload', downloadJSAtOnload);
    else
        window.onload = downloadJSAtOnload;
    

    【讨论】:

    • 感谢您的贡献!
    【解决方案11】:
    <!doctype html>
    <html>
        <head>
    
        </head>
        <body>
            <p>If you click on the "Hide" button, I will disappear.</p>
            <button id="hide" >Hide</button>
            <button id="show" >Show</button>
    
            <script type="text/javascript">
                function loadScript(url, callback) {
    
                    var script = document.createElement("script")
                    script.type = "text/javascript";
    
                    if (script.readyState) {  //IE
                        script.onreadystatechange = function() {
                            if (script.readyState == "loaded" ||
                                    script.readyState == "complete") {
                                script.onreadystatechange = null;
                                callback();
                            }
                        };
                    } else {  //Others
                        script.onload = function() {
                            callback();
                        };
                    }
    
                    script.src = url;
                    document.body.appendChild(script);
                }
                loadScript("http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js",
                        function() {
                            //YAHOO.namespace("mystuff");
                            $("#show").click(function() {
                                $("p").show();
                            });
                            $("#hide").click(function() {
                                $("p").hide();
                            });
    
                            //more...
                        });
            </script>
    
        </body>
    </html>
    

    【讨论】:

      【解决方案12】:

      我认为 Modernizr.load() 在这里值得一提 - 它非常好地处理依赖项加载

      【讨论】:

        【解决方案13】:

        Google Analytics 使用了一个简单的技巧。

        准备

        1.在 HTML 的头部添加一个小脚本

        <script>
            window.jQuery_store = [];
            window.jQueryReady = function (fn) { jQuery_store.push(fn); }
        </script>
        

        jQueryReady 函数只会将委托保存到一个数组中以备将来使用。

        2.创建一个新的JS脚本jquery-ready.js,包含下一个内容:

        // When jQuery is finaly ready
        $(function() {
            // Replace previous implementation
            window.jQueryReady = function(fn) {
                // jQuery is loaded call immediately
                fn();
            }
            
            // Call all saved callbacks
            for (var i = 0; i < window.jQuery_store.length; ++i) {
                try {
                    window.jQuery_store[i]();
                } catch (err) {
                    console.log(err);
                }
            }
        })
        

        加载此脚本后,它将:

        • 等到jQuery 可以安全使用
        • 将用一个新函数替换jQueryReady 函数,它只是立即调用委托(jQuery 在给定时间准备好)。
        • 遍历从以前的jQueryReady 调用中保存的函数。

        3. 将所有内容放在一起 仅在加载 jQuery 后才加载 jquery-ready.js。 在您的页脚中,您将拥有:

        <script defer src=".../jquery.min.js">
        <script defer src=".../bootstrap.min.js">
        ... any other plugins for jQuery you probably need
        <script defer src=".../jquery-ready.js">
        

        这确保 jquery-ready.js 脚本仅在 jQuery 执行后才会执行。

        用法

        您现在可以随时使用 jQueryReady 函数。

        jQueryReady(function() {
            // jQuery is safe to use here
            $("div.to-be-hidden").hide();
        })
        

        【讨论】:

        • 简洁的解决方案。这个我还没试过,但是等待jQuery的插件会有问题吗?
        • 你应该在所有插件之后包含 jquery-ready.js。在这种情况下,jQueryReady 只会在所有插件都加载完成后才会被调用
        【解决方案14】:

        看来你只需要&lt;script defer&gt; : http://www.w3schools.com/tags/att_script_defer.asp

        【讨论】:

        • 根据该链接:“只有 Internet Explorer 支持 defer 属性。” OP 在评论中提到他们正在使用 Chrome,所以我假设这是一个阻碍。
        • 仅供参考,有一个运动让人们停止推荐 w3schools.com。 w3fools.com
        • 我正在创建必须提供广泛的跨浏览器支持的公共站点。此外,问题不在于延迟——它知道何时加载延迟代码 (jQuery) 并准备好调用函数。
        【解决方案15】:

        看看jQuery.holdReady()

        "保持或释放 jQuery 的就绪事件的执行。" (jQuery 1.6+)

        http://api.jquery.com/jQuery.holdReady/

        【讨论】:

        • 感谢您的评论。看来 holdReady() 并没有推迟 jQuery 的加载,而只是延迟了 Ready 事件。澄清我的问题:浏览器会在页面呈现之前花费一定的时间下载和解析所有 .js 文件——移动浏览器解析 JS 代码的速度特别慢。我希望在 .js 文件甚至下载之前呈现页面。因此,用户可以在下载和解析 .js 文件的同时快速查看页面并开始消化内容。因此,我希望推迟 .js 文件的实际下载。
        • 我明白你在说什么。上面的一些脚本可能有助于做到这一点。尝试使用 Chrome 的审核工具来了解如何提高页面速度
        【解决方案16】:

        我想在 WordPress 网站上延迟加载 jQuery,所以我实际上无法使用它更新所有引用。 而是编写了一个小包装器来对 jQuery 调用进行排队,然后在它最终加载时调用。 把它放在头上,只有 250 字节左右,这意味着 jQuery 可以在不改变所有现有引用的情况下被延迟或异步加载。让我的加载时间变得更好。

        我的快速代码可能不适用于所有 jQuery 函数,但到目前为止,除了我尝试过的一个函数调用之外,它已经适用于所有函数。 通常我会做这样的自定义东西,我不会让它可用,但我想我会把这个 sn-p 放到网上。可在此处获取https://github.com/andrewtranter/jQuery_deferred_compat

        【讨论】:

          【解决方案17】:

          使用http://labjs.com 加载html 末尾的所有脚本,这是100% 的解决方案,我根据gtmetrix 规则对其进行了多次测试。例如http://gtmetrix.com/reports/interactio.cz/jxomHSLV

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-06-06
            • 2018-04-02
            • 2011-08-25
            • 2012-07-16
            • 1970-01-01
            • 1970-01-01
            • 2012-04-05
            • 1970-01-01
            相关资源
            最近更新 更多