Javascript 异步加载脚本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7718935/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 03:20:13  来源:igfitidea点击:

load scripts asynchronously

javascriptajaxhtmlasynchronous

提问by Tono Nam

I am using several plugins, custom widgets and some other libraries from JQuery. as a result I have several .js and .css files. I need to create a loader for my site because it takes some time to load. it will be nice if I can display the loader before importing all the:

我正在使用 JQuery 中的几个插件、自定义小部件和其他一些库。结果我有几个 .js 和 .css 文件。我需要为我的网站创建一个加载器,因为加载需要一些时间。如果我可以在导入所有内容之前显示加载程序,那就太好了:

<script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="js/myFunctions.js"></script>
<link type="text/css" href="css/main.css" rel="stylesheet" />
... 
....
 etc

I have found several tutorials that enable me to import a JavaScript library asynchronously. for example I can do something like:

我找到了几个教程,可以让我异步导入 JavaScript 库。例如,我可以执行以下操作:

  (function () {
        var s = document.createElement('script');
        s.type = 'text/javascript';
        s.async = true;
        s.src = 'js/jquery-ui-1.8.16.custom.min.js';
        var x = document.getElementsByTagName('script')[0];
        x.parentNode.insertBefore(s, x);
    })();

for some reason when I do the same thing for all my files the pages does not work. I have been trying for so long to try to find where the problem is but I just cannot find it. First I thought that it was probably because some javascript functions depended on the others. but I loaded them in the right order using the time out function when one completed I proceeded with the next and the page still behaves weird. for example I am not able to click on links etc... animations still work though..

出于某种原因,当我对所有文件执行相同操作时,页面不起作用。我已经尝试了很长时间试图找到问题所在,但我就是找不到它。首先我认为这可能是因为某些 javascript 函数依赖于其他函数。但是当一个完成时我使用超时功能以正确的顺序加载它们我继续下一个并且页面仍然表现得很奇怪。例如,我无法点击链接等...动画仍然有效...

Anyways

无论如何

Here is what I have been thinking... I believe browsers have a cache that's why it takes a long time to load the page for the first time and the next time it is quick. so what I am thinking of doing is replacing my index.html page with a page that loads all this files asynchronously. when ajax is done loading all those files redirect to the page that I plan on using. when using that page it should not take long to load since the files should alredy be included on the cache of the browser. on my index page (page where .js and .css file get loaded asynchronously) I don't care of getting errors. I will just be displaying a loader and redirecting the page when done...

这就是我一直在想的……我相信浏览器有一个缓存,这就是为什么第一次加载页面需要很长时间,而下一次加载很快。所以我想做的是用异步加载所有这些文件的页面替换我的 index.html 页面。当 ajax 完成加载所有这些文件时,重定向到我打算使用的页面。使用该页面时,加载时间应该不会太长,因为这些文件应该已经包含在浏览器的缓存中。在我的索引页(.js 和 .css 文件异步加载的页面)上,我不在乎出现错误。我将只显示一个加载器并在完成后重定向页面...

Is this idea a good alternative? or should I keep trying on implementing the asynchronously methods?

这个想法是一个很好的选择吗?还是我应该继续尝试实现异步方法?



EDIT

编辑

the way I load everything async is like:

我加载所有异步内容的方式如下:

importScripts();

function importScripts()
{
    //import: jquery-ui-1.8.16.custom.min.js
    getContent("js/jquery-1.6.2.min.js",function (code) {
                var s = document.createElement('script');
                s.type = 'text/javascript';
                //s.async = true;
                s.innerHTML=code;
                var x = document.getElementsByTagName('script')[0];
                x.parentNode.insertBefore(s, x);
                setTimeout(insertNext1,1);
            });


    //import: jquery-ui-1.8.16.custom.min.js
    function insertNext1()
    {
        getContent("js/jquery-ui-1.8.16.custom.min.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext2,1);
                });
    }

    //import: jquery-ui-1.8.16.custom.css
    function insertNext2()
    {

        getContent("css/custom-theme/jquery-ui-1.8.16.custom.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext3,1);
                });
    }

    //import: main.css
    function insertNext3()
    {

        getContent("css/main.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext4,1);
                });
    }

    //import: jquery.imgpreload.min.js
    function insertNext4()
    {
        getContent("js/farinspace/jquery.imgpreload.min.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext5,1);
                });
    }


    //import: marquee.js
    function insertNext5()
    {
        getContent("js/marquee.js",function (code) {
                    var s = document.createElement('script');
                    s.type = 'text/javascript';
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext6,1);
                });
    }


    //import: marquee.css
    function insertNext6()
    {

        getContent("css/marquee.css",function (code) {
                    var s = document.createElement('link');
                    s.type = 'text/css';
                    s.rel ="stylesheet";
                    s.innerHTML=code;
                    var x = document.getElementsByTagName('script')[0];
                    x.parentNode.insertBefore(s, x);
                    setTimeout(insertNext,1);
                });
    }



    function insertNext()
    {
        setTimeout(pageReadyMan,10);        
    }
}


// get the content of url and pass that content to specified function
function getContent( url, callBackFunction )
{
     // attempt to create the XMLHttpRequest and make the request
     try
     {
        var asyncRequest; // variable to hold XMLHttpRequest object
        asyncRequest = new XMLHttpRequest(); // create request object

        // register event handler
        asyncRequest.onreadystatechange = function(){
            stateChange(asyncRequest, callBackFunction);
        } 
        asyncRequest.open( 'GET', url, true ); // prepare the request
        asyncRequest.send( null ); // send the request
     } // end try
     catch ( exception )
     {
        alert( 'Request failed.' );
     } // end catch
} // end function getContent

// call function whith content when ready
function stateChange(asyncRequest, callBackFunction)
{
     if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
     {
           callBackFunction(asyncRequest.responseText);
     } // end if
} // end function stateChange

and the weird part is that all the style's work plus all the javascript functions. the page is frozen for some reason though...

奇怪的是,所有样式的工作加上所有 javascript 函数。页面由于某种原因被冻结了...

回答by zzzzBov

A couple solutions for async loading:

异步加载的几种解决方案:

//this function will work cross-browser for loading scripts asynchronously
function loadScript(src, callback)
{
  var s,
      r,
      t;
  r = false;
  s = document.createElement('script');
  s.type = 'text/javascript';
  s.src = src;
  s.onload = s.onreadystatechange = function() {
    //console.log( this.readyState ); //uncomment this line to see which ready states are called.
    if ( !r && (!this.readyState || this.readyState == 'complete') )
    {
      r = true;
      callback();
    }
  };
  t = document.getElementsByTagName('script')[0];
  t.parentNode.insertBefore(s, t);
}

If you've already got jQuery on the page, just use:

如果您已经在页面上安装了 jQuery,请使用:

$.getScript(url, successCallback)*

$.getScript(url, successCallback)*

Additionally, it's possible that your scripts are being loaded/executed before the document is done loading, meaning that you'd need to wait for document.readybefore events can be bound to the elements.

此外,您的脚本可能在文档加载完成之前被加载/执行,这意味着您需要等待document.ready事件才能绑定到元素。

It's not possible to tell specifically what your issue is without seeing the code.

如果不查看代码,就无法具体说明您的问题是什么。

The simplest solution is to keep all of your scripts inline at the bottom of the page, that way they don't block the loading of HTML content while they execute. It also avoids the issue of having to asynchronously load each required script.

最简单的解决方案是将所有脚本内嵌在页面底部,这样它们在执行时就不会阻止加载 HTML 内容。它还避免了必须异步加载每个所需脚本的问题。

If you have a particularly fancy interaction that isn't always used that requires a larger script of some sort, it could be useful to avoid loading that particular script until it's needed (lazy loading).

如果您有一个并不总是使用的特别花哨的交互,它需要某种更大的脚本,那么避免在需要之前加载该特定脚本(延迟加载)可能会很有用。

* scripts loaded with $.getScriptwill likely not be cached

*加载的脚本$.getScript可能不会被缓存



For anyone who can use modern features such as the Promiseobject, the loadScriptfunction has become significantly simpler:

对于任何可以使用现代功能(例如Promise对象)的人来说,该loadScript功能已经变得非常简单:

function loadScript(src) {
    return new Promise(function (resolve, reject) {
        var s;
        s = document.createElement('script');
        s.src = src;
        s.onload = resolve;
        s.onerror = reject;
        document.head.appendChild(s);
    });
}

Be aware that this version no longer accepts a callbackargument as the returned promise will handle callback. What previously would have been loadScript(src, callback)would now be loadScript(src).then(callback).

请注意,此版本不再接受callback参数,因为返回的承诺将处理回调。以前会是loadScript(src, callback)现在loadScript(src).then(callback)

This has the added bonus of being able to detect and handle failures, for example one could call...

这具有能够检测和处理故障的额外好处,例如可以调用...

loadScript(cdnSource)
    .catch(loadScript.bind(null, localSource))
    .then(successCallback, failureCallback);

...and it would handle CDN outages gracefully.

...它会优雅地处理 CDN 中断。

回答by Donald Porter

HTML5's new 'async' attribute is supposed to do the trick. 'defer' is also supported in most browsers if you care about IE.

HTML5 的新 'async' 属性应该可以解决问题。如果您关心 IE,大多数浏览器也支持“延迟”。

async - The HTML

异步 - HTML

<script async src="siteScript.js" onload="myInit()"></script>

defer - The HTML

defer - HTML

<script defer src="siteScript.js" onload="myInit()"></script>

While analyzing the new adsense ad unit code I noticed the attribute and a search lead me here: http://davidwalsh.name/html5-async

在分析新的 Adsense 广告单元代码时,我注意到了该属性,搜索将我带到这里:http: //davidwalsh.name/html5-async

回答by Kernel Med

Example from google

来自谷歌的例子

<script type="text/javascript">
  (function() {
    var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
    po.src = 'https://apis.google.com/js/plusone.js?onload=onLoadCallback';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
  })();
</script>

回答by Tono Nam

I loaded the scripts asynchronously (html 5 has that feature) when all the scripts where done loading I redirected the page to index2.html where index2.html uses the same libraries. Because browsers have a cache once the page redirects to index2.html, index2.html loads in less than a second because it has all it needs to load the page. In my index.html page I also load the images that I plan on using so that the browser place those images on the cache. so my index.html looks like:

当完成加载的所有脚本我将页面重定向到 index2.html 时,我异步加载脚本(html 5 具有该功能),其中 index2.html 使用相同的库。因为一旦页面重定向到 index2.html,浏览器就会有一个缓存,index2.html 会在不到一秒的时间内加载,因为它拥有加载页面所需的一切。在我的 index.html 页面中,我还加载了我计划使用的图像,以便浏览器将这些图像放在缓存中。所以我的 index.html 看起来像:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <title>Project Management</title>

    <!-- the purpose of this page is to load all the scripts on the browsers cache so that pages can load fast from now on -->

    <script type="text/javascript">

        function stylesheet(url) {
            var s = document.createElement('link');
            s.type = 'text/css';
            s.async = true;
            s.src = url;
            var x = document.getElementsByTagName('head')[0];
            x.appendChild(s);
        }

        function script(url) {
            var s = document.createElement('script');
            s.type = 'text/javascript';
            s.async = true;
            s.src = url;
            var x = document.getElementsByTagName('head')[0];
            x.appendChild(s);
        }

        //load scritps to the catche of browser
        (function () {            
                stylesheet('css/custom-theme/jquery-ui-1.8.16.custom.css');
                stylesheet('css/main.css');
                stylesheet('css/marquee.css');
                stylesheet('css/mainTable.css');

                script('js/jquery-ui-1.8.16.custom.min.js');
                script('js/jquery-1.6.2.min.js');
                script('js/myFunctions.js');
                script('js/farinspace/jquery.imgpreload.min.js');
                script('js/marquee.js');            
        })();

    </script>

    <script type="text/javascript">
       // once the page is loaded go to index2.html
        window.onload = function () {
            document.location = "index2.html";
        }
    </script>

</head>
<body>

<div id="cover" style="position:fixed; left:0px; top:0px; width:100%; height:100%; background-color:Black; z-index:100;">Loading</div>

<img src="images/home/background.png" />
<img src="images/home/3.png"/>
<img src="images/home/6.jpg"/>
<img src="images/home/4.png"/>
<img src="images/home/5.png"/>
<img src="images/home/8.jpg"/>
<img src="images/home/9.jpg"/>
<img src="images/logo.png"/>
<img src="images/logo.png"/>
<img src="images/theme/contentBorder.png"/>

</body>
</html>

another nice thing about this is that I may place a loader in the page and when the page is done loading the loader will go away and in a matte of milliseconds the new page will be running.

另一个好处是我可以在页面中放置一个加载器,当页面加载完成时,加载器将消失,新页面将在几毫秒内运行。

回答by shaman.sir

Several notes:

几个注意事项:

  • s.async = trueis not very correct for HTML5 doctype, correct is s.async = 'async'(actually using trueis correct, thanks to amnwho pointed it out in the commentjust below)
  • Using timeouts to control the order is not very good and safe, and you also make the loading time much larger, to equal the sum of all timeouts!
  • s.async = true对于 HTML5 doctype 不是很正确,正确的是s.async = 'async'(实际上使用true是正确的,感谢amn在下面的评论中指出)
  • 使用超时来控制顺序不是很好和安全,而且你还使加载时间变得更大,等于所有超时的总和!

Since there is a recent reason to load files asynchronously, but in order, I'd recommend a bit more functional-driven way over your example (remove console.logfor production use :) ):

由于最近有异步加载文件的原因,但为了按顺序,我建议在您的示例中console.log使用更多功能驱动的方式(删除用于生产用途:)):

(function() {
    var prot = ("https:"===document.location.protocol?"https://":"http://");

    var scripts = [
        "path/to/first.js",
        "path/to/second.js",
        "path/to/third.js"
    ];

    function completed() { console.log('completed'); }  // FIXME: remove logs

    function checkStateAndCall(path, callback) {
        var _success = false;
        return function() {
            if (!_success && (!this.readyState || (this.readyState == 'complete'))) {
                _success = true;
                console.log(path, 'is ready'); // FIXME: remove logs
                callback();
            }
        };
    }

    function asyncLoadScripts(files) {
        function loadNext() { // chain element
            if (!files.length) completed();
            var path = files.shift();
            var scriptElm = document.createElement('script');
            scriptElm.type = 'text/javascript';
            scriptElm.async = true;
            scriptElm.src = prot+path;
            scriptElm.onload = scriptElm.onreadystatechange = \
                checkStateAndCall(path, loadNext); // load next file in chain when
                                                   // this one will be ready 
            var headElm = document.head || document.getElementsByTagName('head')[0];
            headElm.appendChild(scriptElm);
        }
        loadNext(); // start a chain
    }

    asyncLoadScripts(scripts);
})();

回答by odroz

Thanks to HTML5, you can now declare the scripts that you want to load asynchronously by adding "async" in the tag:

感谢 HTML5,您现在可以通过在标签中添加“async”来声明要异步加载的脚本:

<script async>...</script>

Note: The async attribute is only for external scripts (and should only be used if the src attribute is present).

注意: async 属性仅用于外部脚本(并且只有在 src 属性存在时才应使用)。

Note: There are several ways an external script can be executed:

注意:有几种方法可以执行外部脚本:

  • If async is present: The script is executed asynchronously with the rest of the page (the script will be executed while the page continues the parsing)
  • If async is not present and defer is present: The script is executed when the page has finished parsing
  • If neither async or defer is present: The script is fetched and executed immediately, before the browser continues parsing the page
  • 如果存在 async:脚本与页面的其余部分异步执行(脚本将在页面继续解析时执行)
  • 如果 async 不存在并且 defer 存在:当页面完成解析时执行脚本
  • 如果 async 或 defer 都不存在:在浏览器继续解析页面之前,立即获取并执行脚本

See this: http://www.w3schools.com/tags/att_script_async.asp

看到这个:http: //www.w3schools.com/tags/att_script_async.asp

回答by asmmahmud

Here is a great contemporary solution to the asynchronous script loading though it only address the js script with async false.

这是异步脚本加载的一个很好的当代解决方案,尽管它只使用async false处理 js 脚本。

There is a great article written in www.html5rocks.com- Deep dive into the murky waters of script loading.

www.html5rocks.com 中有一篇很棒的文章 -深入了解脚本加载的阴暗面

After considering many possible solutions, the author concluded that adding js scripts to the end of body element is the best possible way to avoid blocking page rendering by js scripts.

在考虑了很多可能的解决方案后,作者得出结论,在 body 元素的末尾添加 js 脚本是避免 js 脚本阻塞页面渲染的最佳方法。

In the mean time, the author added another good alternate solution for those people who are desperate to load and execute scripts asynchronously.

同时,作者为那些急于异步加载和执行脚本的人添加了另一个很好的替代解决方案。

Considering you've four scripts named script1.js, script2.js, script3.js, script4.jsthen you can do it with applying async = false:

考虑到您命名了四个脚本,script1.js, script2.js, script3.js, script4.js那么您可以通过应用 async = false 来实现

[
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

Now, Spec says: Download together, execute in order as soon as all download.

现在,Spec 说:一起下载,全部下载后按顺序执行。

Firefox < 3.6, Opera says:I have no idea what this “async” thing is, but it just so happens I execute scripts added via JS in the order they're added.

Firefox < 3.6,Opera 说:我不知道这个“异步”是什么,但碰巧我按照添加的顺序执行通过 JS 添加的脚本。

Safari 5.0 says:I understand “async”, but don't understand setting it to “false” with JS. I'll execute your scripts as soon as they land, in whatever order.

Safari 5.0 说:我理解“async”,但不理解用 JS 将其设置为“false”。你的脚本一落地,我就会立即执行,无论顺序如何。

IE < 10 says:No idea about “async”, but there is a workaround using “onreadystatechange”.

IE < 10 说:不知道“异步”,但有一个使用“onreadystatechange”的解决方法。

Everything else says:I'm your friend, we're going to do this by the book.

其他一切都说:我是你的朋友,我们将按照书本去做。

Now, the full code with IE < 10 workaround:

现在,使用 IE < 10 解决方法的完整代码:

var scripts = [
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];

// Watch scripts load in IE
function stateChange() {
  // Execute as many scripts in order as we can
  var pendingScript;
  while (pendingScripts[0] && ( pendingScripts[0].readyState == 'loaded' || pendingScripts[0].readyState == 'complete' ) ) {
    pendingScript = pendingScripts.shift();
    // avoid future loading events from this script (eg, if src changes)
    pendingScript.onreadystatechange = null;
    // can't just appendChild, old IE bug if element isn't closed
    firstScript.parentNode.insertBefore(pendingScript, firstScript);
  }
}

// loop through our script urls
while (src = scripts.shift()) {
  if ('async' in firstScript) { // modern browsers
    script = document.createElement('script');
    script.async = false;
    script.src = src;
    document.head.appendChild(script);
  }
  else if (firstScript.readyState) { // IE<10
    // create a script and add it to our todo pile
    script = document.createElement('script');
    pendingScripts.push(script);
    // listen for state changes
    script.onreadystatechange = stateChange;
    // must set src AFTER adding onreadystatechange listener
    // else we'll miss the loaded event for cached scripts
    script.src = src;
  }
  else { // fall back to defer
    document.write('<script src="' + src + '" defer></'+'script>');
  }
}

回答by Tibo

I would complete zzzzBov's answer with a check for the presence of callback and allow passing of arguments:

我会通过检查是否存在回调来完成 zzzzBov 的回答,并允许传递参数:

    function loadScript(src, callback, args) {
      var s, r, t;
      r = false;
      s = document.createElement('script');
      s.type = 'text/javascript';
      s.src = src;
      if (typeof(callback) === 'function') {
        s.onload = s.onreadystatechange = function() {
          if (!r && (!this.readyState || this.readyState === 'complete')) {
            r = true;
            callback.apply(args);
          }
        };
      };
      t = document.getElementsByTagName('script')[0];
      t.parent.insertBefore(s, t);
    }

回答by Shawn Mclean

I would suggest you take a look at Modernizr. Its a small light weight library that you can asynchronously load your javascript with features that allow you to check if the file is loaded and execute the script in the other you specify.

我建议你看看Modernizr。它是一个小型轻量级库,您可以异步加载您的 javascript,其功能允许您检查文件是否已加载并在您指定的另一个中执行脚本。

Here is an example of loading jquery:

下面是一个加载 jquery 的例子:

Modernizr.load([
  {
    load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js',
    complete: function () {
      if ( !window.jQuery ) {
            Modernizr.load('js/libs/jquery-1.6.1.min.js');
      }
    }
  },
  {
    // This will wait for the fallback to load and
    // execute if it needs to.
    load: 'needs-jQuery.js'
  }
]);

回答by tere?ko

You might find this wiki article interesting : http://ajaxpatterns.org/On-Demand_Javascript

您可能会发现这篇 wiki 文章很有趣:http: //ajaxpatterns.org/On-Demand_Javascript

It explains how and when to use such technique.

它解释了如何以及何时使用这种技术。