загружать скрипты асинхронно

82

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

Является ли эта идея хорошей альтернативой? или мне следует продолжать попытки применения асинхронных методов?


ИЗМЕНИТЬ

то, как я загружаю все async, выглядит так:

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

Теги:
asynchronous

17 ответов

127
Лучший ответ

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

//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 во время их выполнения. Это также позволяет избежать необходимости асинхронной загрузки каждого требуемого script.

Если у вас особенно необычное взаимодействие, которое не всегда используется, для чего требуется более крупный script, может быть полезно избежать загрузки этого конкретного script, пока он не понадобится (ленивая загрузка).

* скрипты, загруженные с помощью $.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 изящно.

  • 0
    Я собираюсь начать испытывать это. но если я загружаю эти сценарии на другую страницу, когда перенаправляю страницу на другую страницу, и на этой странице появляются одинаковые сценарии, им не нужно загружать время. они должны быть в кеше, верно? поэтому я не должен применять эту технику вместо этого?
  • 0
    Синхронизация и асинхронная загрузка используют кеш браузера.
Показать ещё 11 комментариев
17

Новый атрибут "async" HTML5 должен сделать трюк. "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

  • 0
    из моего понимания после прочтения stackoverflow.com/questions/2774373/… asyn не для загрузки, но для определения, когда запускать содержащийся javascript.
  • 6
    Атрибут defer заставляет браузер откладывать выполнение скрипта до тех пор, пока документ не будет загружен и проанализирован и готов к работе. Атрибут async заставляет браузер запускать сценарий как можно быстрее, но не блокирует синтаксический анализ документа во время загрузки сценария.
Показать ещё 3 комментария
8

Я загрузил сценарии асинхронно (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>

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

  • 8
    и никто не может продолжать задаваться вопросом, насколько низок ваш PageRank.
  • 0
    @Dementic Вы имеете в виду мета-теги?
Показать ещё 2 комментария
6

Пример из 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>
6

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

  • s.async = true не очень корректен для HTML5 doctype, правильный s.async = 'async' (фактически использование true является правильным, благодаря amn, указав это ниже comment)
  • Использование тайм-аутов для управления заказом не очень хорошо и безопасно, и вы также увеличиваете время загрузки, чтобы равняться сумме всех тайм-аутов!

Поскольку существует некоторая причина для загрузки файлов асинхронно, но по порядку я рекомендую немного более функциональный путь по вашему примеру (удалите 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);
})();
  • 3
    s.async = true - это правильно . Свойство async явно указано как логическое значение в рекомендации W3C по HTML 5 по адресу w3.org/TR/html5/scripting-1.html#attr-script-async . Вы async атрибут async со свойством async предоставляемым объектами, реализующими интерфейс DOM HTMLScriptElement . Действительно, при манипулировании соответствующим атрибутом элемента через что-то вроде Element.setAttribute , вы должны использовать element.setAttribute("async", "async") как все атрибуты HTML являются в первую очередь текстовыми.
  • 0
    «Так как в последнее время есть причина загружать файлы асинхронно, но по порядку, я бы порекомендовал немного более функционально управляемый способ по сравнению с вашим примером [...]». Я знаю, что асинхронность обычно "лучше", но на какую "недавнюю" причину вы ссылаетесь?
2

Я бы выполнил 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

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

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

После рассмотрения многих возможных решений автор пришел к выводу, что добавление 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);
});

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

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

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

IE < 10 говорит: Не знаю, как "асинхронно", но есть обходное решение, использующее "onreadystatechange".

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

Теперь полный код с 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>');
  }
}
1

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

<script async>...</script>

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

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

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

Смотрите это: http://www.w3schools.com/tags/att_script_async.asp

  • 0
    Пожалуйста, не используйте w3schools в качестве учебного материала.
1

Вы можете использовать LABJS или RequreJS

Script загрузчики, такие как LABJS, RequireJS, улучшат скорость и качество вашего кода.

1

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

callMyFunctions();

вместо:

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

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

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

1

Я бы посоветовал вам взглянуть на Modernizr. Его небольшая небольшая библиотека, в которую вы можете асинхронно загружать свой javascript с функциями, которые позволяют вам проверить, загружен ли файл и выполнить script в другом, который вы указали.

Вот пример загрузки 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'
  }
]);
1

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

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

  • 0
    К сожалению, ссылка кажется мертвой. :(
  • 0
    @ Эрик, потому что это древний метод динамической загрузки кода: D
0

Считаете ли вы использование Fetch Injection? Я обработал библиотеку с открытым исходным кодом под названием fetch-inject для обработки таких случаев. Здесь ваш загрузчик может выглядеть как lib:

fetcInject([
  'js/jquery-1.6.2.min.js',
  'js/marquee.js',
  'css/marquee.css',
  'css/custom-theme/jquery-ui-1.8.16.custom.css',
  'css/main.css'
]).then(() => {
  'js/jquery-ui-1.8.16.custom.min.js',
  'js/farinspace/jquery.imgpreload.min.js'
})

Для обнаружения обратной связи с обратной совместимостью и возврата к XHR Injection или Script DOM Elements или просто встраивайте теги в страницу с помощью document.write.

0

Вот мое настраиваемое решение для устранения JavaScript-рендеринга:

// put all your JS files here, in correct order
const libs = {
  "jquery": "https://code.jquery.com/jquery-2.1.4.min.js",
  "bxSlider": "https://cdnjs.cloudflare.com/ajax/libs/bxslider/4.2.5/jquery.bxslider.min.js",
  "angular": "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.2/angular.min.js",
  "ngAnimate": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.min.js"
}
const loadedLibs = {}
let counter = 0

const loadAsync = function(lib) {
  var http = new XMLHttpRequest()
  http.open("GET", libs[lib], true)
  http.onload = () => {
    loadedLibs[lib] = http.responseText
    if (++counter == Object.keys(libs).length) startScripts()
  }
  http.send()
}

const startScripts = function() {
  for (var lib in libs) eval(loadedLibs[lib])
  console.log("allLoaded")
}

for (var lib in libs) loadAsync(lib)

Короче говоря, он асинхронно загружает все ваши скрипты и затем выполняет их соответственно.

Github repo: https://github.com/mudroljub/js-async-loader

  • 1
    Не работает No 'Access-Control-Allow-Origin' header is present on the requested resource
  • 1
    Это проблема CORS. stackoverflow.com/questions/20035101/...
0

Посмотрите https://github.com/stephen-lazarionok/async-resource-loader. В нем есть пример, который показывает, как загрузить JS, CSS и несколько файлов одним выстрелом.

  • 1
    Нуждается в jquery ...
  • 0
    Можно немного изменить его, чтобы избавиться от зависимости jquery
0

Ну, x.parentNode возвращает элемент HEAD, поэтому вы вставляете script непосредственно перед тегом head. Может быть, проблема.

Попробуйте x.parentNode.appendChild().

-2

Я бы предложил сначала изучить мини файлы и посмотреть, дает ли это вам достаточно большой прирост скорости. Если ваш хост работает медленно, можно попробовать поставить статический контент на CDN.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню