可以推迟加载jQuery吗?


69

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

Google建议推迟加载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。如何检测添加的标记已完成加载和解析?

因此,似乎异步加载也可能包含一个答案。

有什么想法吗?


为什么不只在页面底部而不是页面顶部包含jQuery(和您自己的JS文件)?
凯文·恩尼斯

2
Google说(在我提供的第一个链接中),“必须先下载,解析和执行脚本,然后浏览器才能开始呈现网页”。这意味着在页面底部加载jQuery意味着在解析并执行jQuery之前,该页面仍不会呈现。有没有一种方法可以异步加载它并在页面加载后让它完成工作?
凯文·赖斯

1
不能defer全部使用外部脚本吗?它们应该仍然按照列出的顺序执行。
nilskp 2012年

2
@nilskp似乎对于这个问题已经存在并接受了最佳实践,这就是我提出它的原因,而不是尝试自己提出。所有各种浏览器都在该区域存在许多陷阱。
凯文·赖斯

如果推迟所有脚本,它们将(或至少应该)以相同的顺序加载,只是在浏览器认为最佳选择时加载它们即可。异步但是,将允许js文件以浏览器认为合适的任何顺序加载。因此,如果推迟所有脚本文件(包括JQuery库),则不会有任何问题。
Phill Healey

Answers:


53

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

(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身体负荷事件的回调
符号

5
我已经成功地使用了它。我还修改了success();第10行的if(success && getClass.call(success) == '[object Function]') { success(); }当它不为null且为函数时,执行成功函数。这使您可以仅调用getScript("myScript.js")而无需函数调用。
Dan Atkinson

1
@Geniusknight是的,它是getScript()!:)它可以用于延迟多个延迟,如果存在依赖关系,则在尝试运行任何依赖于此的子脚本之前,需要为它们进行编码以确保已加载它们。使用多维数组将是一种相当简单的方法。
Dan Atkinson 2014年

14

由于这是一个重要主题上的排名最高的问题,因此我要大胆地根据@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"]);

9

这是对异步/延迟javascript加载现代方法的很好描述。但不适用于内联脚本

<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 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>

3
element.addEventListener("load", function () {
    $('#tabs').tabs()
}, false);

试试看


是的 知道了!因此,我认为此答案与“与”符号的某种组合将是最终的解决方案。看来我将需要链式加载jQuery,然后是jQuery-UI,然后是依赖于这两者的页面代码。
凯文·P·赖斯

1
IE <9不支持addEventListener :(
brunoais 2012年

2

将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>

3
这似乎是“次佳”的解决方案,但实际上并非如此。Google确实允许加载其他资源,但在我提供的第一个链接中,Google说:“必须先下载,解析和执行脚本,然后浏览器才能开始呈现网页”。这意味着在页面底部加载jQuery意味着在解析并执行jQuery之前,该页面仍不会呈现。有没有一种方法可以异步加载它并在页面加载让它完成工作?
凯文·赖斯

2
这将是超级容易测试。使自己成为一个漂亮的10MB JS大文件,里面装满废话,并将其包含在页面底部。查看是否先加载其他内容。
凯文·恩尼斯

肯尼斯-是,其他内容会先加载,但是问题与用户体验有关。特别是在移动浏览器上,解析JavaScript的时间延迟了用户与页面进行交互的时间。在Chrome开发人员工具中,jQuery的“评估脚本”时间轴是DOMContentLoaded和Load事件发生前的一半时间。在发生这些事件之后,我才能使用Google代码(在问题中)延迟的脚本不会加载或执行。我希望jQuery在页面渲染后加载/解析。
凯文·莱斯

@Kevin,该链接还指出:“ ...对脚本下所有元素的处理被阻止,直到浏览器从磁盘加载代码并执行它为止。” 至少使用桌面浏览器,用户可以在页面完全加载之前开始与页面进行交互。从初始请求到用户可以与页面进行交互(不一定是DOMContentLoaded事件)且页面底部没有脚本的加载时间是多少?

我肯定会(最好)尽最大可能地对这两种方法进行“基准测试”,以确保异步加载速度更快。看起来杰里米(Jeremy)拥有您最初想要的代码。祝好运!

2

在某些情况下,加载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()


感谢您提供这个新答案。
凯文·赖斯

2

我将这段代码添加到异步/延迟的jquery script标签之后,它定义了一个临时函数$,该函数会在加载完所有内容后累积需要运行的内容,然后在此时使用$将被覆盖以执行功能。使用这段代码,无需在文档中进一步更改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>

1
工作完美只是兼而有之defer async。谢谢
Jodyshop '19

1

在我看来,您要做的就是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,但同时也没有加载页面代码。让我尝试一下...我怀疑确保先加载哪个会有问题。
凯文·P·赖斯

@Kevin R:大概第一个执行到DOM的对象是第一个执行的对象,但是也许由于您的代码最有可能比jQuery小,所以它会被首先引用。使用用户代理服务器端并以这种方式控制包含的文件怎么办?我知道,这不是有史以来最好的事情,但是如果您担心移动浏览器,那么可能值得研究。
理查德·马斯克

首先,我可能全都想推迟jQuery的加载,但这似乎是合理的,这使Google PageSpeed感到满意。通过tylermwashburn的答案链接事件似乎是按顺序加载所有内容的方式。另外,请查看JSL JavaScript Loader,它看起来与我要执行的操作类似。非常感谢!
凯文·赖斯

1

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

<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在页面加载后加载/评估/解析/执行。
凯文·P·赖斯

编辑了我的响应,以在window.load / onload事件触发后完成加载脚本
Jeremy Battle

杰里米(Jeremy),再次感谢-我将尝试使用'async'属性...不过,请参见下面发布的代码'Drackir',这正是我所尝试的。结果:是的,jQuery加载了延迟的jQuery,但是“选项卡”代码(与Drackir的显示方式相同)没有等待延迟的jQuery.js加载,因此我得到了“ jquery undefined”。不知何故,我需要一个回调或事件来知道何时加载和解析jquery.js。
凯文·莱斯

有点黑,但是从技术上讲,您可以执行繁忙等待循环,然后在运行$(“#tabs”)。tabs();之前检查是否已加载jQuery 。如果不是,请执行setTimeout并再次调用该函数。

1
凯文,再编辑一次。为我工作了,让我知道它是否适用于您的标签页。
杰里米战役,

1

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

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;

感谢您的贡献!
凯文·P·赖斯

1
<!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>

1

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


1

Google Analytics(分析)使用了一个简单的技巧。

制备

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

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

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

2.jquery-ready.js使用以下内容创建一个新的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之后才执行jquery-ready.js脚本。

用法

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

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

干净的解决方案。我还没有尝试过,但是等待jQuery的插件会有问题吗?
Reigel

您应该在所有插件之后包含jquery-ready.js。在这种情况下,只有在加载了所有插件后才会调用jQueryReady
Seagull

0

似乎您只需要<script defer>http : //www.w3schools.com/tags/att_script_defer.asp


按照该链接:“ Internet Explorer仅支持defer属性。” OP在评论中提到他们使用的是Chrome,因此我认为这是个大问题。
理查德·马斯克

8
仅供参考,有一种运动使人们停止推荐w3schools.com。 w3fools.com

我正在创建必须提供广泛的跨浏览器支持的公共站点。而且,问题不是由延迟引起的,而是知道何时加载延迟代码(jQuery)并准备对其调用函数。
凯文·赖斯

0

看一看 jQuery.holdReady()

“保留或释放jQuery的ready事件的执行。” (jQuery 1.6及更高版本)

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


1
感谢您的评论。似乎holdReady()不会延迟jQuery的加载,而只是延迟了Ready事件。为了澄清我的问题:在呈现页面之前,浏览器花了一定时间下载和解析所有.js文件-移动浏览器解析JS代码特别慢。我希望在下载.js文件之前先渲染页面。因此,用户可以快速查看页面,并且可以在下载并解析.js文件时开始消化内容。因此,我希望推迟.js文件的实际下载。
凯文·P·赖斯

我明白你在说什么。上面的某些脚本可能有助于实现这一目标。尝试使用Chrome的“审核”工具找出如何提高页面速度
mikeycgto 2011年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.