【问题标题】:JavaScript: How to download JS asynchronously?JavaScript:如何异步下载 JS?
【发布时间】:2010-05-10 14:18:40
【问题描述】:

On my web site,我正在尝试以尽可能快的速度加载页面。

我注意到我的 JavaScript 似乎没有异步加载。图片链接如下。

alt text http://img249.imageshack.us/img249/2452/jsasynch2.png

我的网站的工作原理是它需要加载两个外部 JavaScript 文件:

  • Google 地图 v3 JavaScript,以及
  • JQuery JavaScript

然后,我在 HTML 中有内联 JavaScript,在下载上述两个文件之前无法执行。

一旦加载了这些外部 javascript 文件,它就可以动态地呈现页面。我的页面在加载 Google Maps 和 JQuery 之前无法加载的原因是 - 我的页面基于用户的地理位置(使用 Gmaps)然后将根据他们所在的位置(例如纽约,旧金山等)。这意味着,不同城市的两个人查看我的网站会看到不同的首页。

问题:如何让我的 JavaScript 文件异步下载,以便我的整体页面加载时间最快?

更新

如果我要以某种方式异步下载 Google-maps 和 JQuery,我将如何创建一个事件,一旦 Google-maps 和 JQuery 都已下载,因为我的页面很难依赖这些文件来执行。

更新 2

尽管下面有 3 个答案,但仍然没有一个能真正回答我遇到的问题。任何帮助将不胜感激。

【问题讨论】:

  • 您应该将所有内联 javascript 移出到不引人注目的 javascript 中,这样可以解决您的一些问题。
  • 关于您的更新,只需创建一个在您的 每个 脚本加载时调用的函数,并让它在 second 调用它的时间。 IE。第一次调用时,设置一个变量,第二次(当它注意到变量已设置时),运行你的代码。
  • @tloflin,您刚才描述的是创建“瀑布效果”,这不是异步
  • @Teddyk,如果您希望代码等到满足某个条件(例如加载两个脚本),根据定义,这不是“异步”。你希望它以什么方式异步?您应该能够使其与脚本本身一样异步。
  • @Tloflin,我希望他们同时开始下载。这就是我所说的异步

标签: javascript google-maps asynchronous


【解决方案1】:

HTTP 下载通常被浏览器限制为每个域同时下载两个。这就是为什么有些网站在www.domain.tlathe images and javascript on static.domain.tla 上有动态内容。

但是浏览器对脚本的行为略有不同,while a script is downloading, however, the browser won't start any other downloads, even on different hostnames.

标准解决方案是将脚本移至页面底部,但有一个解决方法可能对您有用,也可能对您无效:Insert the script DOM element using Javascript

【讨论】:

  • 这也是我的第一个想法,但是这两个脚本实际上来自不同的域。毕竟我只想说:这是浏览器特定的行为。在 Firefox 中,每个域的下载量可以在 about:config 中配置为 network.http.max-connections。默认为 15。
  • @BalusC:下载 javascript 文件会阻止所有其他下载,无论域如何。此外,默认值太根深蒂固,无法依赖用户更改它们;)
  • 您也链接了 YSlow 的创建者,声明您可以完成异步 JS 下载 - stevesouders.com/blog/2009/04/27/… 但是,鉴于我的要求,Gmaps 和 JQuery 都需要先下载,我不知道如何使用史蒂夫的例子​​。 因此,我不相信你的回答是正确的。
  • @Teddyk:我会使用 DOM 注入,因为所有其他变通方法似乎更骇人听闻,而且更容易在跨浏览器支持方面给您带来更多问题。
  • @voyager,但是如果使用 DOM 注入,在执行内联 JS 代码之前,如何创建一个可以在下载(google-maps 和 jquery)两者后触发的事件?我说的有道理吗?再次提醒一下 - 在 Gmaps 和 Jquery 都下载之前,我的页面无法呈现。我想做的只是加快下载过程,因为我的页面对这两个文件有很强的依赖关系。
【解决方案2】:

您可以使用类似的东西,它在大多数浏览器中都运行良好。它至少在 IE6 中存在一些问题,但我真的没有时间调查它们。

var require = function (scripts, loadCallback) {
    var length        = scripts.length;
    var first         = document.getElementsByTagName("script")[0];
    var parentNode    = first.parentNode;
    var loadedScripts = 0;
    var script;

    for (var i=0; i<length; i++) {
        script = document.createElement("script");
        script.async = true;
        script.type = "text/javascript";
        script.src = scripts[i];

        script.onload = function () {
            loadedScripts++;

            if (loadedScripts === length) {
                loadCallback();
            }
        };

        script.onreadystatechange = function () {
            if (script.readyState === "complete") {
                loadedScripts++;

                if (loadedScripts === length) {
                    loadCallback();
                }
            }
        };

        parentNode.insertBefore(script, first);
    }
};


require([
    "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
    "http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
    "http://ajax.googleapis.com/ajax/libs/yui/2.7.0/build/yuiloader/yuiloader-min.js"
], function () {
    console.log(jQuery);
    console.log($);
    console.log(YAHOO);
});

【讨论】:

  • 但是如果我需要等到 2 个 JS 文件下载完成,我的回调函数会是什么?
  • 数组中的所有URL都加载完毕后触发回调。如果您将两个 URL 放入该数组中,则回调将在它们都加载后触发。可能有更复杂的案例,但我并不假装我已经提供了一个详尽的解决方案。它只是满足您需求的起点。
【解决方案3】:

有人让我对这个帖子发表评论,但那是在@lonut 发布回复之前。 @lonut 的代码是一个很好的解决方案,但我有一些 cmets(关键和不那么关键):

首先,@lonut 的代码假定脚本对其他脚本没有负载依赖关系。这有点难以解释,所以让我们使用 jquery.min.js 和prototype.js 的简单示例。假设我们有一个简单的页面,它只加载这两个脚本,如下所示:

<script src="jquery.min.js"></script>
<script src="prototype.js"></script>

请记住 - 页面中没有其他内容 - 没有其他 JavaScript 代码。如果您加载该页面,则下载两个脚本并且一切都很好。现在,如果删除 jquery.min.js 脚本会发生什么?如果你从prototype.js 中得到错误,因为它试图引用在jquery.min.js 中定义的符号,那么prototype.js 对jquery.min.js 有负载依赖——你不能加载prototype.js,除非jquery.min.js 有已经加载。但是,如果您没有收到任何错误,则可以按照您希望的任何顺序加载这两个脚本。假设您的外部脚本之间没有负载依赖关系,@lonut 的代码很棒。如果您确实有负载依赖关系 - 它会变得非常困难,您应该阅读 Even Faster Web Sites 中的第 4 章。

其次,@lonut 代码的一个问题是某些版本的 Opera 会调用 loadCallback 两次(一次来自 onload 处理程序,第二次来自 onreadystatechange 处理程序)。只需添加一个标志以确保 loadCallback 只被调用一次。

第三,当今大多数浏览器为每个主机名打开超过 2 个连接。见Roundup on Parallel Connections

【讨论】:

  • Sounders,我非常很荣幸您对我的帖子发表评论。话虽如此,我的问题有解决方案吗?如果有帮助,我在stackoverflow.com/questions/2804494/… 发布了与此问题直接相关的第二个问题,可能会为您提供更多详细信息。再次,非常感谢,我对你在 StackOverlow 上感到不知所措:)
  • 确实,我故意忽略了第一个问题,因为我认为可以通过在回调中再次调用require 来解决。没有做任何测试,但似乎它应该工作。关于第二个问题,我知道有些浏览器会触发两次回调,但由于我有最新的 Opera 版本(并且 IE6 运行良好),因此无法触发该错误。无论如何,感谢您的反馈!
【解决方案4】:

LABjs 动态脚本加载器专为此类情况而设计。例如,您可以这样做:

$LAB
  .script("googlemaps.js")
  .script("jquery.js")
  .wait(function(){
     // yay, both googlemaps and jquery have been loaded, so do something!
  });

如果情况稍微复杂一些,并且您有一些相互依赖的脚本,正如 Steve Souders 所提到的,那么您可以这样做:

$LAB
  .script("jquery.js")
  .wait() // make sure jquery is executed first
  .script("plugin.jquery.js")
  .script("googlemaps.js")
  .wait(function(){
     // all scripts are ready to go!
  });

无论哪种情况,LABjs 都会下载所有脚本(“jquery.js”、“googlemaps.js”和“plugin.jquery.js”)并行 em>,至少在浏览器允许的范围内。但是通过在链中明智地使用 .wait(),LABjs 将确保它们以正确的顺序执行。也就是说,如果链中的两个脚本之间没有 .wait() ,它们将各自尽快执行(意味着它们之间的顺序不确定)。如果链中的两个脚本之间存在 .wait(),那么第一个脚本将在第二个脚本之前执行,即使它们是并行加载的。

【讨论】:

    【解决方案5】:

    这是我设法在 jquery 移动设备上异步加载 gmap 的方法: 首先,您可以加载 jquery(即使用上面 Ionuț G. Stan 发布的 require 函数) 然后你可以利用 gmaps 中的 callback 参数来做以下事情:

    <!DOCTYPE html>
    <html>  
    <body>
     <script type="text/javascript">
          var require = function (scripts, loadCallback) {
          var length        = scripts.length;
          var first         = document.getElementsByTagName("script")[0];
          var parentNode    = first.parentNode;
          var loadedScripts = 0;
          var script;
    
          for (var i=0; i<length; i++) {
              script = document.createElement("script");
              script.async = true;
              script.type = "text/javascript";
              script.src = scripts[i];
    
              script.onload = function () {
                  loadedScripts++;
    
                  if (loadedScripts === length) {
                      loadCallback();
                  }
              };
    
              script.onreadystatechange = function () {
                  if (script.readyState === "complete") {
                      loadedScripts++;
    
                      if (loadedScripts === length) {
                          loadCallback();
                      }
                  }
              };
              parentNode.insertBefore(script, first);
            }
           };
    
           require([
          "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",], function () {
    
             $.ajax({
                type: "GET",
                url: 'http://maps.googleapis.com/maps/api/js?v=3&sensor=false&callback=setMyMap',
                dataType: "script"
             });
    
           });
    
          function setMyMap() {
    
    
                           console.log('your actions here');
    
    
    
               var coords = new google.maps.LatLng(40.5439532,-3.6441775);
               var mOptions = {
                  zoom: 8,
                  center: coords,
                  mapTypeId: google.maps.MapTypeId.ROADMAP
               }
               var map = new google.maps.Map(document.getElementById("gmap"), mOptions);
          }
     </script>
    
     <div id="gmap" style="width:299px; height:299px"></div>
    
    </body>
    

    重点是加载 jquery async(无论您选择什么方法),然后在该回调上使用 gmaps 脚本字符串的 callback 参数中的启动方法对 gmaps 进行新的异步调用。

    希望对你有帮助

    【讨论】:

      【解决方案6】:

      无论它们以什么顺序下载,脚本都应该按照它们在页面上出现的顺序解析/执行(除非您使用 DEFER)。 p>

      因此,您可以将两个 Google 地图放在首位,然后是 JQuery。然后,在页面正文中的某处:

      <script language="Javascript">
      
          function InitPage() {
             // Do stuff that relies on JQuery and Google, since this script should
             // not execute until both have already loaded.
             }
      
          $(InitPage);   // this won't execute until JQuery is ready
      
      </script>
      

      但这确实有一个缺点,即在加载页面开头时阻塞了其他连接,这对于页面性能来说并不是那么棒。

      相反,您可以将 JQuery 保留在 HEAD 中,但使用 JQuery 的 Javascript 加载功能而不是 Google JSAPI, InitPage() 函数加载 Google 脚本。然后在 that 回调函数执行时开始渲染。与上述相同,但使用此 InitPage() 函数:

        function InitPage() {
           $.getScript('Google Maps Javascript URL', function() {
              // Safe to start rendering now
              });
      

      【讨论】:

      • 这还会是异步的吗?因为在 JQuery 首先下载之前似乎没有开始下载谷歌地图。
      • 是的,但是 JQuery 是一个快速加载。上面的解决方案解决了依赖问题,而不是异步网络连接。您可以使用顶部的 document.write hack 开始异步加载 JQuery 和 Google,并尝试在您的主页代码上使用 DEFER,但这并不能保证您的主脚本会等待其他脚本。为此,您需要自己的“就绪”函数来测试 JQuery 和 Google Maps 对象,等待几毫秒并在它们都不可用时再次尝试,如果它们不可用则超时并出现适当的错误'在合理的时间段内加载。
      【解决方案7】:

      将您的 javascript 包含 (&lt;script src="...) 从 HEAD 元素移动到您的 BODY 元素的末尾。通常,放在 HEAD 中的任何内容都会同步加载,而放在 BODY 中的任何内容都会异步加载。这对于脚本包含或多或少是正确的,但是如今大多数浏览器会阻止脚本下方的所有内容,直到它被加载 - 因此为什么将脚本包含在正文底部是最佳做法。

      这里是 YUI guildline,它更详细地解释了它: http://developer.yahoo.net/blog/archives/2007/07/high_performanc_5.html

      这也是为什么样式表应该在头部,而javascript应该在正文中的原因。因为我们不想在样式异步加载时看到我们的页面从意大利面条变成漂亮,我们不想在页面加载时等待我们的 javascript。

      【讨论】:

        【解决方案8】:

        使用 requireJS 可以实现您心中的目标。 RequireJS 异步下载 js 资源。它是一个非常简单和有用的库来实现。请在此处阅读更多内容。 http://requirejs.org/

        【讨论】:

          猜你喜欢
          • 2011-11-24
          • 1970-01-01
          • 1970-01-01
          • 2018-09-20
          • 1970-01-01
          • 2016-08-18
          • 1970-01-01
          • 2013-11-18
          • 2020-11-20
          相关资源
          最近更新 更多