Можно ли вызвать .js
файл синхронно, а затем сразу же использовать его?
<script type="text/javascript">
var head = document.getElementsByTagName('head').item(0);
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'http://mysite/my.js');
head.appendChild(script);
myFunction(); // Fails because it hasn't loaded from my.js yet.
window.onload = function() {
// Works most of the time but not all of the time.
// Especially if my.js injects another script that contains myFunction().
myFunction();
};
</script>
Это упрощено. В моей реализации материал createElement находится в функции. Я подумал о том, чтобы добавить что-нибудь в функцию, которая могла бы проверять, была ли создана конкретная переменная, прежде чем возвращать управление. Но тогда все еще остается проблема, что делать при включении js с другого сайта, который я не могу контролировать.
Мысли?
Редактировать:
На данный момент я принял лучший ответ, потому что он дает хорошее объяснение происходящего. Но если у кого-то есть предложения, как это улучшить, я открыт для них. Вот пример того, что я хотел бы сделать.
// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');
myFunc1('blarg');
myFunc2('bleet');
Я просто не хочу слишком много знать внутреннее устройство и просто могу сказать: «Я хочу использовать этот модуль, и теперь я воспользуюсь его кодом».
источник
eval()
каждый файл в указанном порядке, в противном случае просто сохраните ответ).Ответы:
Вы можете создать свой
<script>
элемент с помощью обработчика «onload», который будет вызываться, когда скрипт будет загружен и оценен браузером.var script = document.createElement('script'); script.onload = function() { alert("Script loaded and ready"); }; script.src = "http://whatever.com/the/script.js"; document.getElementsByTagName('head')[0].appendChild(script);
Вы не можете делать это синхронно.
edit - было указано, что, как и положено, IE не запускает событие «load» для
<script>
загружаемых / оцениваемых тегов. Таким образом, я полагаю, что следующее, что нужно сделать, это получить сценарий с помощью XMLHttpRequest, а затемeval()
его самостоятельно. (Или, я полагаю, поместите текст в<script>
тег, который вы добавляете; среда выполненияeval()
зависит от локальной области видимости, поэтому она не обязательно будет делать то, что вы хотите.)edit - По состоянию на начало 2013 года я настоятельно рекомендовал бы изучить более надежный инструмент загрузки скриптов, такой как Requirejs . Есть много особых случаев, о которых нужно беспокоиться. Для действительно простых ситуаций есть yepnope , который теперь встроен в Modernizr .
источник
eval()
. Однако отладка - это кошмар, потому что в сообщении об ошибкеeval()
появляется строка , а не сама ошибкаЭто некрасиво, но работает:
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); </script> <script type="text/javascript"> functionFromOther(); </script>
Или же
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); window.onload = function() { functionFromOther(); }; </script>
Скрипт должен быть включен либо в отдельный
<script>
тег, либо раньшеwindow.onload()
.Так не пойдет:
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); functionFromOther(); // Error </script>
То же самое можно сделать с созданием узла, как это сделал Pointy, но только в FF. У вас нет гарантии, когда скрипт будет готов в других браузерах.
Я очень ненавижу это, будучи приверженцем XML. Но это работает предсказуемо. Вы могли бы легко обернуть эти уродливые
document.write()
s, чтобы вам не приходилось на них смотреть. Вы даже можете провести тесты и создать узел и добавить его, а затем вернуться к работеdocument.write()
.источник
application/xhtml+xml
.document.write("<SCR" + "IPT>" + "...")
.<head>
которых загружаются несколько других зависимостей (или частные файлы).Это уже поздно, но в будущем для тех, кто хотел бы это сделать, вы можете использовать следующее:
function require(file,callback){ var head=document.getElementsByTagName("head")[0]; var script=document.createElement('script'); script.src=file; script.type='text/javascript'; //real browsers script.onload=callback; //Internet explorer script.onreadystatechange = function() { if (this.readyState == 'complete') { callback(); } } head.appendChild(script); }
Некоторое время назад я сделал короткое сообщение в блоге http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -гружено /
источник
Дуглас Крокфорд ( блог YUI )
Хорошо, пристегни свои сиденья, потому что это будет ухабистая поездка. Все больше и больше людей спрашивают о динамической загрузке скриптов через javascript, кажется, это горячая тема.
Основные причины, по которым это стало таким популярным:
О модульности : очевидно, что управление зависимостями на стороне клиента должно осуществляться прямо на стороне клиента. Если требуется определенный объект, модуль или библиотека, мы просто запрашиваем его и загружаем динамически.
Обработка ошибок : если ресурс выходит из строя, у нас все еще есть возможность заблокировать только те части, которые зависят от затронутого скрипта, или, возможно, даже дать ему еще одну попытку с некоторой задержкой.
Производительность стала конкурентным преимуществом между веб-сайтами, теперь это фактор ранжирования в поиске. Что могут делать динамические сценарии, так это имитировать асинхронное поведение в отличие от способа блокировки по умолчанию того, как браузеры обрабатывают сценарии. Скрипты блокируют другие ресурсы, скрипты блокируют дальнейший анализ HTML-документа, скрипты блокируют пользовательский интерфейс. Теперь с помощью динамических тегов сценария и его кросс-браузерных альтернатив вы можете выполнять реальные асинхронные запросы и выполнять зависимый код только тогда, когда они доступны. Ваши скрипты будут загружаться параллельно даже с другими ресурсами, и рендеринг будет безупречным.
Причина, по которой некоторые люди придерживаются синхронного написания сценариев, заключается в том, что они к нему привыкли. Они думают, что это способ по умолчанию, это самый простой способ, а некоторые даже могут подумать, что это единственный способ.
Но единственное, о чем мы должны заботиться, когда нужно решить этот вопрос относительно дизайна приложения, - это опыт конечного пользователя . И в этой области асинхронность обыграть невозможно. Пользователь получает немедленные ответы (или произносит обещания), а обещание всегда лучше, чем ничего. Пустой экран пугает людей. Разработчики не должны лениться, чтобы улучшить воспринимаемую производительность .
И напоследок несколько слов о грязной стороне. Что вам нужно сделать, чтобы он работал в разных браузерах:
источник
Приведенные выше ответы указали мне правильное направление. Вот общая версия того, что у меня получилось:
var script = document.createElement('script'); script.src = 'http://' + location.hostname + '/module'; script.addEventListener('load', postLoadFunction); document.head.appendChild(script); function postLoadFunction() { // add module dependent code here }
источник
postLoadFunction()
называется?script.addEventListener('load', postLoadFunction);
означает, что функция postLoadFunction вызывается при загрузке скрипта.У меня возникли следующие проблемы с существующими ответами на этот вопрос (и варианты этого вопроса в других потоках stackoverflow):
Или, чуть точнее:
Мое последнее решение, которое загружает скрипт перед возвратом, И все скрипты правильно доступны в отладчике (по крайней мере, для Chrome), выглядит следующим образом:
ВНИМАНИЕ: следующий код, ВЕРОЯТНО, должен использоваться только в режиме разработки. (Для режима выпуска я рекомендую предварительную упаковку и минификацию БЕЗ динамической загрузки скриптов или, по крайней мере, без eval).
//Code User TODO: you must create and set your own 'noEval' variable require = function require(inFileName) { var aRequest ,aScript ,aScriptSource ; //setup the full relative filename inFileName = window.location.protocol + '//' + window.location.host + '/' + inFileName; //synchronously get the code aRequest = new XMLHttpRequest(); aRequest.open('GET', inFileName, false); aRequest.send(); //set the returned script text while adding special comment to auto include in debugger source listing: aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n'; if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!** { //create a dom element to hold the code aScript = document.createElement('script'); aScript.type = 'text/javascript'; //set the script tag text, including the debugger id at the end!! aScript.text = aScriptSource; //append the code to the dom document.getElementsByTagName('body')[0].appendChild(aScript); } else { eval(aScriptSource); } };
источник
function include(file){ return new Promise(function(resolve, reject){ var script = document.createElement('script'); script.src = file; script.type ='text/javascript'; script.defer = true; document.getElementsByTagName('head').item(0).appendChild(script); script.onload = function(){ resolve() } script.onerror = function(){ reject() } }) /*I HAVE MODIFIED THIS TO BE PROMISE-BASED HOW TO USE THIS FUNCTION include('js/somefile.js').then(function(){ console.log('loaded'); },function(){ console.log('not loaded'); }) */ }
источник
Это похоже на достойный обзор динамической загрузки скриптов: http://unixpapa.com/js/dyna.html
источник
Я привык к тому, что на моем веб-сайте есть несколько файлов .js, которые зависят друг от друга. Чтобы загрузить их и убедиться, что зависимости оцениваются в правильном порядке, я написал функцию, которая загружает все файлы, а затем, когда они все получены,
eval()
их. Главный недостаток в том, что поскольку это не работает с CDN. Для таких библиотек (например, jQuery) лучше включать их статически. Обратите внимание, что динамическая вставка узлов сценария в HTML не гарантирует, что сценарии будут оцениваться в правильном порядке, по крайней мере, не в Chrome (это было основной причиной написания этой функции).function xhrs(reqs) { var requests = [] , count = [] , callback ; callback = function (r,c,i) { return function () { if ( this.readyState == 4 ) { if (this.status != 200 ) { r[i]['resp']="" ; } else { r[i]['resp']= this.responseText ; } c[0] = c[0] - 1 ; if ( c[0] == 0 ) { for ( var j = 0 ; j < r.length ; j++ ) { eval(r[j]['resp']) ; } } } } } ; if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) { requests.length = reqs.length ; } else { requests.length = 1 ; reqs = [].concat(reqs); } count[0] = requests.length ; for ( var i = 0 ; i < requests.length ; i++ ) { requests[i] = {} ; requests[i]['xhr'] = new XMLHttpRequest () ; requests[i]['xhr'].open('GET', reqs[i]) ; requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ; requests[i]['xhr'].send(null); } }
Я не понял, как ссылаться на одно и то же значение без создания массива (для подсчета). В противном случае я думаю, что это говорит само за себя (когда все загружено,
eval()
каждый файл в указанном порядке, в противном случае просто сохраните ответ).Пример использования:
xhrs( [ root + '/global.js' , window.location.href + 'config.js' , root + '/js/lib/details.polyfill.min.js', root + '/js/scripts/address.js' , root + '/js/scripts/tableofcontents.js' ]) ;
источник
Как ни странно, у меня есть то, что вы хотите, но я хочу чего-то более близкого к тому, что было у вас.
Я загружаю вещи динамически и асинхронно, но с таким
load
обратным вызовом (используя dojo и xmlhtpprequest)dojo.xhrGet({ url: 'getCode.php', handleAs: "javascript", content : { module : 'my.js' }, load: function() { myFunc1('blarg'); }, error: function(errorMessage) { console.error(errorMessage); } });
Более подробное объяснение см. Здесь
Проблема в том, что где-то в этой строке код искажается, и если с вашим кодом что-то не так, в
console.error(errorMessage);
инструкции будет указана строка, гдеeval()
находится, а не фактическая ошибка. Это ТАКАЯ большая проблема, которую я пытаюсь преобразовать обратно в<script>
операторы (см. Здесь .источник
<script>
тегам и использовал соглашения (вместе с некоторыми пакетами сборки), чтобы просто упаковать свои js таким образом, чтобы это имело смысл.Это работает для современных «вечнозеленых» браузеров, поддерживающих async / await и fetch .
Этот пример упрощен без обработки ошибок, чтобы показать основные принципы работы.
// This is a modern JS dependency fetcher - a "webpack" for the browser const addDependentScripts = async function( scriptsToAdd ) { // Create an empty script element const s=document.createElement('script') // Fetch each script in turn, waiting until the source has arrived // before continuing to fetch the next. for ( var i = 0; i < scriptsToAdd.length; i++ ) { let r = await fetch( scriptsToAdd[i] ) // Here we append the incoming javascript text to our script element. s.text += await r.text() } // Finally, add our new script element to the page. It's // during this operation that the new bundle of JS code 'goes live'. document.querySelector('body').appendChild(s) } // call our browser "webpack" bundler addDependentScripts( [ 'https://code.jquery.com/jquery-3.5.1.slim.min.js', 'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js' ] )
источник
webpack
... 1. для каждого скрипта он отправляет anew HTTP request
, 2. Это также не будет проверять зависимости между ними, 3. Не все браузеры поддерживаютasync/await
и 4. Производительность, если она утомительна, тогда нормальная. Было бы хорошо добавить это вhead