异步加载脚本

IT技术 javascript ajax html asynchronous
2021-01-20 18:18:24

我正在使用 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

我找到了几个使我能够异步导入 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);
    })();

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

无论如何

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

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


编辑

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

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

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

6个回答

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

//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);
}

如果页面上已经有 jQuery,只需使用:

$.getScript(url, successCallback)*

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

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

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

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

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


对于任何可以使用现代功能(例如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);
    });
}

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

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

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

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

我不得不使用 t.parentNode.insertBefore(而不是 t.parent)。
2021-03-15 18:18:24
@MuhammadUmer,如果您在document.write文档加载之前编写附加脚本标记,则脚本将作为加载页面的一部分同步执行,并且不会单独包含任何异步回调行为。如果您正在创建脚本元素并将其添加到页面,那么您将有权添加事件侦听器以异步触发。tl; dr:这取决于您如何使用 JS 加载脚本。
2021-03-21 18:18:24
我改变并将孩子附加到头部标签而不是身体...... ..var x = document.getElementsByTagName('head')[0]; x.appendChild(s);
2021-03-24 18:18:24
@TonoNam,您可以只使用document.head.appendChild,但是将脚本添加到 中存在一些利基问题<head>没有什么会阻止脚本加载和执行。
2021-03-24 18:18:24
我即将开始尝试。但是,如果我在将页面重定向到其他页面时将这些脚本加载到其他页面上,并且该页面恰好具有相同的脚本,则它们应该不会花时间加载。他们应该在缓存中吗?那么我不应该实施该技术吗?
2021-03-27 18:18:24

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

异步 - HTML

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

defer - HTML

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

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

@MohammedShareefC 如果您都没有指定,您会得到与同步行为非常相似的东西;浏览器在下载和运行脚本时会阻止进一步处理。因此有效的默认行为是同步。但是因为这不利于性能,浏览器将尽可能地解决它。他们可以继续解析、加载和应用 CSS 等……但是他们不能开始运行下一个脚本片段。所以东西会阻塞。通过设置异步,你告诉浏览器可以继续,你将确保你自己让它工作。
2021-03-14 18:18:24
defer 属性使浏览器将脚本的执行推迟到文档被加载和解析并准备好进行操作之后。async 属性使浏览器尽快运行脚本,但不会在下载脚本时阻止文档解析
2021-03-16 18:18:24
@StijndeWitt 如果既没有指定 async 也没有指定 defer 怎么办?
2021-03-20 18:18:24
根据我阅读stackoverflow.com/questions/2774373/...后的理解, asyn 不是用于加载,而是用于确定何时运行包含的 javascript。
2021-03-27 18:18:24
“asyn不是用来加载的,而是用来决定什么时候运行所包含的javascript”这并不矛盾;在所有情况下,浏览器都会尽快开始加载脚本。但是没有 asyncdefer它会在加载时阻止渲染。设置async将告诉它不要阻止并尽快运行脚本。设置defer将告诉它不要阻止而是等待运行脚本直到页面完成。通常,只要脚本没有任何依赖项(例如 jQuery),尽快运行脚本是没有问题的。如果一个脚本依赖于另一个脚本,请使用onLoad等待它。
2021-04-01 18:18:24

当完成加载的所有脚本我将页面重定向到 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>

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

@MohammedShareefC 具有讽刺意味。仅用于预加载内容的特殊页面听起来并不是 SEO 明智的好主意。预加载很棒,但您无需重定向即可实现。只需将链接粘贴在隐藏的 div 中,浏览器就会自动为您处理。然后不要重定向,而只是呈现实际页面。COPY-PASTERS 阅读此内容:请在使用任何此 HTML 之前将 ISO-88591 编码更改为 UTF-8,以便我们其他人可以很快摆脱编码地狱。谢谢!
2021-03-22 18:18:24
2021-03-22 18:18:24

来自谷歌的例子

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

几个注意事项:

  • s.async = true对于 HTML5 doctype 不是很正确,正确的是s.async = 'async'(实际上使用true 是正确的,感谢amn下面评论中指出
  • 使用超时来控制顺序不是很好也很安全,而且你还使加载时间变得更大,等于所有超时的总和!

由于最近有异步加载文件的原因,但为了按顺序,我建议在您的示例中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);
})();
“由于最近有异步加载文件的原因,但为了按顺序,我建议在您的示例中使用更多功能驱动的方式[...]”。我知道异步通常“更好”,但是您指的是什么“最近”原因?
2021-03-15 18:18:24
我改变了'if (!files.length) completed();' 添加'返回':'if (!files.length) {completed(); 返回;}'
2021-03-15 18:18:24
s.async = true正确的该属性asyncw3.org/TR/html5/scripting-1.html#attr-script-async 的W3C 的 HTML 5 建议中明确指定为布尔值您将async 属性async实现HTMLScriptElementDOM 接口的对象公开属性混淆了确实,当通过类似 操作元素的相应属性时Element.setAttribute,您应该使用,element.setAttribute("async", "async")因为所有 HTML 属性都首先是文本。
2021-04-01 18:18:24