загружать скрипты асинхронно - PullRequest
115 голосов
/ 11 октября 2011

Я использую несколько плагинов, пользовательских виджетов и некоторых других библиотек из 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 зависят от других. но я загрузил их в правильном порядке, используя функцию тайм-аута, когда одна завершена, я продолжил со следующей, и страница все еще ведет себя странно например, я не могу нажимать на ссылки и т.д. ... анимации все еще работают, хотя ..

1009 * В любом случае * Вот о чем я думал ... Я считаю, что браузеры имеют кеш, поэтому загрузка страницы в первый раз занимает много времени, а в следующий раз - быстро. так что я думаю о том, чтобы заменить мою страницу index.html страницей, которая загружает все эти файлы асинхронно. когда Ajax завершит загрузку всех этих файлов, перенаправьте на страницу, которую я планирую использовать. при использовании этой страницы загрузка не займет много времени, так как файлы должны быть уже включены в кеш браузера. на моей странице индекса (страница, где файлы .js и .css загружаются асинхронно), я не забочусь об ошибках. Я просто покажу загрузчик и перенаправлю страницу, когда закончу ... Является ли эта идея хорошей альтернативой? или я должен продолжать пытаться реализовать асинхронные методы? EDIT

способ загрузки всего асинхронного выглядит так:

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. хотя страница почему-то заморожена ...

Ответы [ 19 ]

157 голосов
/ 11 октября 2011

Пара решений для асинхронной загрузки:

//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, поскольку возвращаемое обещание будет обрабатывать обратный вызов. То, что раньше было loadScript(src, callback), теперь будет loadScript(src).then(callback).

Это дает дополнительный бонус: он способен обнаруживать и обрабатывать сбои, например, можно позвонить ...

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

... и он будет корректно обрабатывать сбои CDN.

29 голосов
/ 24 августа 2013

Новый атрибут HTML5 'async' должен помочь.defer также поддерживается в большинстве браузеров, если вы заботитесь о IE.

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

7 голосов
/ 12 октября 2011

Я загружал сценарии асинхронно (html 5 имеет эту функцию), когда все сценарии, для которых загрузка была завершена, перенаправил страницу в index2.html, где 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>

Еще одна приятная вещь в том, что я могу разместить загрузчик на странице, и когда страница будет завершена, загрузка загрузчика уйдет и через миллисекундыновая страница будет запущена.

6 голосов
/ 11 февраля 2013

Несколько примечаний:

  • s.async = true не очень корректно для типа документа HTML5, правильное значение равно 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);
})();
6 голосов
/ 26 декабря 2013

Пример из 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>
3 голосов
/ 09 мая 2016

Благодаря HTML5 вы можете теперь объявить сценарии, которые вы хотите загрузить асинхронно, добавив «async» в тег:

<script async>...</script>

Примечание: Атрибут async предназначен только для внешних сценариев (и должениспользуется только при наличии атрибута src).

Примечание. Существует несколько способов выполнения внешнего скрипта:

  • Если присутствует асинхронный: скрипт выполняется асинхронно состальная часть страницы (сценарий будет выполняться, пока страница продолжит синтаксический анализ)
  • Если асинхронизация отсутствует, а отсрочка присутствует: сценарий выполняется после завершения анализа страницы
  • Если нет ни асинхронного, ни отсроченного: скрипт извлекается и выполняется немедленно, прежде чем браузер продолжит синтаксический анализ страницы

См. Это: http://www.w3schools.com/tags/att_script_async.asp

2 голосов
/ 03 августа 2017

Вот отличное современное решение для асинхронной загрузки скрипта, хотя оно обращается только к скрипту js с async false .

Есть замечательная статья, написанная на www.html5rocks.com - Глубокое погружение в мутные воды загрузки сценариев .

Обдумав множество возможных решений, автор пришел к выводу, что добавление js-скриптов в конец элемента body является наилучшим способом избежать блокирования рендеринга страницы при помощи js-скриптов.

Тем временем автор добавил еще одно хорошее альтернативное решение для тех людей, которые отчаянно пытаются загружать и выполнять сценарии асинхронно.

Учитывая, что у вас есть четыре сценария с именем 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);
});

Теперь, Спецификация говорит : Скачивайте вместе, выполняйте по порядку, как только загрузите все.

Firefox <3.6, Opera говорит: </strong> Я понятия не имею, что это за «асинхронная» вещь, но так получилось, что я выполняю сценарии, добавленные через JS, в порядке их добавления.

Safari 5.0 говорит: Я понимаю «асинхронный», но не понимаю, как установить для него «ложный» с JS. Я выполню ваши сценарии, как только они приземлятся, в любом порядке.

IE <10 говорит: </strong> Нет понятия об «асинхронности», но есть обходной путь, использующий «onreadystatechange».

Все остальное говорит: Я твой друг, мы собираемся сделать это по книге.

Теперь полный код с IE <10: </strong>

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>');
  }
}
2 голосов
/ 11 июля 2013

Я бы завершил ответ 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);
    }
1 голос
/ 11 октября 2011

Одна из причин, по которой ваши скрипты могут загружаться так медленно, заключается в том, что вы запускали все свои скрипты во время загрузки страницы, например:

callMyFunctions();

вместо:

$(window).load(function() {
      callMyFunctions();
});

Этот второй бит скрипта ожидает, пока браузер полностью загрузит весь ваш код Javascript, прежде чем он начнет выполнять любой из ваших скриптов, давая пользователю понять, что страница загрузилась быстрее.

Если вы хотите улучшить взаимодействие с пользователем за счет сокращения времени загрузки, я бы не стал использовать опцию «экран загрузки». По моему мнению, это было бы гораздо более раздражающим, чем просто медленная загрузка страницы.

1 голос
/ 11 октября 2011

Вам может понравиться эта статья в вики: http://ajaxpatterns.org/On-Demand_Javascript

Объясняет, как и когда использовать такую ​​технику.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...