асинхронная загрузка скриптов
Я использую несколько плагинов, пользовательские виджеты и некоторые другие библиотеки из jQuery. в результате у меня их несколько .js and .файл 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 зависят от других. но я загрузил их в правильном порядке, используя функцию тайм-аута, когда один из них был завершен, я перешел к следующему, и страница по-прежнему ведет себя странно. например, я не могу нажимать на ссылки и т. д... анимация все еще работает хотя..
в любом случае
вот что я подумал... Я считаю, что браузеры имеют кэш, поэтому он занимает много времени для загрузки страницы в первый раз, и в следующий раз это быстро. так что я думаю о том, чтобы заменить мой индекс.html-страница со страницей, которая загружает все эти файлы асинхронно. когда ajax закончит загрузку, все эти файлы перенаправляются на страницу, которую я планирую использовать. при использовании этой страницы загрузка не займет много времени, так как файлы должны alredy быть включены в кэш браузера. на моей индексной странице (страница где .js and .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 ответов:
несколько решений для асинхронной загрузки:
//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 во время выполнения. Это также позволяет избежать проблемы асинхронной загрузки каждого необходимого скрипта.
если у вас есть особенно причудливое взаимодействие, которое не всегда используется, которое требует более крупного сценария какого-то рода, было бы полезно избегать загрузки этого конкретного сценария, пока он не понадобится (lazy погрузочный.)
*скрипты, загружаемые с
$.getScriptскорее всего, не будут кэшироваться
для тех, кто может использовать современные функции, такие как
Promise
новый атрибут 'async' HTML5 должен сделать трюк. 'отложить' также поддерживается в большинстве браузеров, если вы заботитесь о IE.
асинхронный-HTML
<script async src="siteScript.js" onload="myInit()"></script>отложить-HTML
<script defer src="siteScript.js" onload="myInit()"></script>при анализе нового кода рекламного блока adsense я заметил атрибут и поиск привел меня сюда:http://davidwalsh.name/html5-async
Я загрузил скрипты асинхронно (html 5 имеет эту функцию), когда все скрипты, где выполнена загрузка, я перенаправил страницу на index2.html где index2.html использует те же библиотеки. Потому что браузеры имеют кэш, как только страница перенаправляется на index2.html, index2.html загружается менее чем за секунду, потому что у него есть все, что нужно для загрузки страницы. В моем указателе.html-страница я также загружаю изображения, которые я планирую использовать, чтобы браузер разместил эти изображения в кэше. так что мой индекс.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>еще одна приятная вещь в этом заключается в том, что я могу разместить загрузчик на странице, и когда страница будет загружена, загрузчик исчезнет, и через несколько миллисекунд будет запущена новая страница.
пример из 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>
несколько примечаний:
s.async = trueне очень корректно для HTML5 doctype, правильноs.async = 'async'(фактически используяtrueправильно, спасибо amn кто указал на это в комментарий чуть ниже)- использование таймаутов для управления заказом не очень хорошо и безопасно, и вы также делаете время загрузки намного больше, чтобы равняться сумме всех таймаутов!
так как есть недавняя причина для загрузки файлы асинхронно, но для того, чтобы я рекомендовал бы немного более функциональный способ над вашим примером (remove
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); })();
Я бы завершил ответ 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); }
вы можете найти эту статью Вики интересной:http://ajaxpatterns.org/On-Demand_Javascript
Он объясняет, как и когда использовать такой метод.
одна из причин, почему ваши скрипты могут загружаться так медленно, если вы запускаете все свои скрипты во время загрузки страницы, например:
callMyFunctions();вместо:
$(window).load(function() { callMyFunctions(); });этот второй бит скрипта ждет, пока браузер полностью загрузит весь ваш код Javascript, прежде чем он начнет выполнять любой из ваших сценариев, заставляя пользователя думать, что страница загрузилась быстрее.
Если вы хотите улучшить опыт пользователя с помощью уменьшая время загрузки, я бы не пошел на опцию "экран загрузки". На мой взгляд, это было бы гораздо более раздражающим, чем просто более медленная загрузка страницы.
Я бы предложил вам взглянуть на Modernizr. Это небольшая легкая библиотека, которую вы можете асинхронно загружать в javascript с функциями, которые позволяют вам проверить, загружен ли файл и выполнить сценарий в другом, который вы укажете.
вот пример загрузки 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' } ]);
благодаря HTML5, теперь вы можете объявить скрипты, которые вы хотите загрузить асинхронно, добавив "async" в теге:
<script async>...</script>Примечание: атрибут async предназначен только для внешних скриптов (и должен использоваться только при наличии атрибута src).
Примечание: существует несколько способов выполнения внешнего скрипта:
- если присутствует асинхронность: скрипт выполняется асинхронно с остальной частью страницы (скрипт будет выполняться в то время как страница продолжает разбор)
- если асинхронности нет и defer присутствует: скрипт выполняется, когда страница закончила разбор
- если нет ни асинхронности, ни отсрочки: скрипт извлекается и выполняется немедленно, прежде чем браузер продолжит разбор страницы
смотрите это:http://www.w3schools.com/tags/att_script_async.asp
вот отличное современное решение для асинхронной загрузки скрипта, хотя оно касается только сценария 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 Я понятия не имею, что это за "асинхронная" вещь, но так уж получилось, что я выполняю скрипты, добавленные через JS в том порядке, в котором они добавлены.
Safari 5.0 говорит: Я понимаю "асинхронность", но не понимаю, как установить его на" false " с помощью JS. Я выполню ваши сценарии, как только они приземлятся, в любом порядке.
IE нет идеи об "асинхронности", но есть обходной путь с помощью"onreadystatechange".
все остальное говорит: Я твой друг, мы сделаем это по инструкции.
теперь полный код с IE
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>'); } }
Ну
x.parentNodeвозвращает элемент HEAD, поэтому вы вставляете скрипт непосредственно перед тегом head. Может в этом и проблема.попробовать
x.parentNode.appendChild()вместо.
проверьте это https://github.com/stephen-lazarionok/async-resource-loader. он имеет пример, который показывает, как загрузить JS, CSS и несколько файлов с одним выстрелом.
вы рассматривали использовать впрыску выборки? Я свернул библиотеку с открытым исходным кодом под названием 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, или просто встроите теги на страницу с помощью
document.write.
вот мое пользовательское решение для устранения блокировки рендеринга 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 РЕПО: https://github.com/mudroljub/js-async-loader
Я написал небольшой пост, чтобы помочь с этим, вы можете прочитать больше здесь https://timber.io/snippets/asynchronously-load-a-script-in-the-browser-with-javascript/, но я прикрепил вспомогательный класс ниже. Он автоматически ожидает загрузки скрипта и возвращает указанный атрибут окна, как только это произойдет.
export default class ScriptLoader { constructor (options) { const { src, global, protocol = document.location.protocol } = options this.src = src this.global = global this.protocol = protocol this.isLoaded = false } loadScript () { return new Promise((resolve, reject) => { // Create script element and set attributes const script = document.createElement('script') script.type = 'text/javascript' script.async = true script.src = `${this.protocol}//${this.src}` // Append the script to the DOM const el = document.getElementsByTagName('script')[0] el.parentNode.insertBefore(script, el) // Resolve the promise once the script is loaded script.addEventListener('load', () => { this.isLoaded = true resolve(script) }) // Catch any errors while loading the script script.addEventListener('error', () => { reject(new Error(`${this.src} failed to load.`)) }) }) } load () { return new Promise(async (resolve, reject) => { if (!this.isLoaded) { try { await this.loadScript() resolve(window[this.global]) } catch (e) { reject(e) } } else { resolve(window[this.global]) } }) } }использование выглядит так:
const loader = new Loader({ src: 'cdn.segment.com/analytics.js', global: 'Segment', }) // scriptToLoad will now be a reference to `window.Segment` const scriptToLoad = await loader.load()
вот небольшая функция ES6, если кто-то хочет использовать ее в React например
import {uniqueId} from 'lodash' // optional /** * @param {String} file The path of the file you want to load. * @param {Function} callback (optional) The function to call when the script loads. * @param {String} id (optional) The unique id of the file you want to load. */ export const loadAsyncScript = (file, callback, id) => { const d = document if (!id) { id = uniqueId('async_script') } // optional if (!d.getElementById(id)) { const tag = 'script' let newScript = d.createElement(tag) let firstScript = d.getElementsByTagName(tag)[0] newScript.id = id newScript.async = true newScript.src = file if (callback) { // IE support newScript.onreadystatechange = () => { if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') { newScript.onreadystatechange = null callback(file) } } // Other (non-IE) browsers support newScript.onload = () => { callback(file) } } firstScript.parentNode.insertBefore(newScript, firstScript) } else { console.error(`The script with id ${id} is already loaded`) } }
Я бы предложил сначала изучить минимизацию файлов и посмотреть, дает ли это вам достаточно большой прирост скорости. Если ваш хост работает медленно, попробуйте поместить это статическое содержимое на CDN.
Comments