Czy można wywołać .js
plik synchronicznie, a następnie natychmiast go użyć?
<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>
To jest uproszczone. W mojej implementacji element createElement jest w funkcji. Pomyślałem o dodaniu do funkcji czegoś, co mogłoby sprawdzić, czy pewna zmienna została utworzona przed zwróceniem kontroli. Ale nadal pojawia się problem, co zrobić, dołączając js z innej witryny, nad którą nie mam kontroli.
Myśli?
Edytować:
Przyjąłem na razie najlepszą odpowiedź, ponieważ daje dobre wyjaśnienie tego, co się dzieje. Ale jeśli ktoś ma jakieś sugestie, jak to poprawić, jestem otwarty na niego. Oto przykład tego, co chciałbym zrobić.
// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');
myFunc1('blarg');
myFunc2('bleet');
Chciałbym po prostu uniknąć zbyt dużej znajomości wewnętrznych elementów i móc po prostu powiedzieć: „Chcę użyć tego modułu, a teraz użyję trochę kodu z niego”.
źródło
eval()
każdy plik w podanej kolejności, w przeciwnym razie po prostu zapisz odpowiedź).Odpowiedzi:
Możesz stworzyć swój
<script>
element za pomocą procedury obsługi "onload", która zostanie wywołana po załadowaniu i ocenie skryptu przez przeglądarkę.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);
Nie możesz tego zrobić synchronicznie.
edit - zwrócono uwagę, że zgodnie z formą IE nie wywołuje zdarzenia „load” na
<script>
ładowanych / ocenianych tagach. Tak więc przypuszczam, że następną rzeczą do zrobienia byłoby pobranie skryptu z XMLHttpRequest, a następnieeval()
samodzielne pobranie. (Lub, jak przypuszczam, umieść tekst w<script>
dodawanym tagu; na środowisko wykonawczeeval()
ma wpływ lokalny zasięg, więc niekoniecznie robi to, co chcesz).edycja - Od początku 2013 r. zdecydowanie radzę przyjrzeć się bardziej niezawodnemu narzędziu do ładowania skryptów, takim jak Requirejs . Istnieje wiele specjalnych przypadków, o które należy się martwić. W naprawdę prostych sytuacjach istnieje Yepnope , który jest teraz wbudowany w Modernizr .
źródło
eval()
. Jednak debugowanie to koszmar, b / c komunikat o błędzie informuje, żeeval()
pojawia się linia , a nie rzeczywisty błądTo nie jest ładne, ale działa:
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); </script> <script type="text/javascript"> functionFromOther(); </script>
Lub
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); window.onload = function() { functionFromOther(); }; </script>
Skrypt należy umieścić w osobnym
<script>
tagu lub wcześniejwindow.onload()
.To nie zadziała:
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); functionFromOther(); // Error </script>
To samo można zrobić z utworzeniem węzła, tak jak zrobił to Pointy, ale tylko w FF. Nie masz żadnej gwarancji, że skrypt będzie gotowy w innych przeglądarkach.
Będąc purystą XML, naprawdę tego nienawidzę. Ale działa przewidywalnie. Możesz łatwo owinąć te brzydkie,
document.write()
więc nie musisz na nie patrzeć. Możesz nawet wykonać testy i utworzyć węzeł, dołączyć go, a następnie ponownie włączyćdocument.write()
.źródło
application/xhtml+xml
.document.write("<SCR" + "IPT>" + "...")
.<head>
których ładuje się kilka innych zależności (lub plików prywatnych).Jest to dość późno, ale w przyszłości dla każdego, kto chciałby to zrobić, możesz użyć następujących opcji:
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); }
Zrobiłem na ten temat krótki post na blogu jakiś czas temu http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -załadowany/
źródło
Douglas Crockford ( blog YUI )
W porządku, zapnij siedzenia, bo to będzie wyboista jazda. Coraz więcej osób pyta o dynamiczne ładowanie skryptów przez javascript, wydaje się, że to gorący temat.
Główne powody, dla których stało się to tak popularne, to:
O modułowości : oczywiste jest, że zarządzanie zależnościami po stronie klienta powinno być obsługiwane bezpośrednio po stronie klienta. Jeśli potrzebny jest jakiś obiekt, moduł lub biblioteka, po prostu o niego prosimy i ładujemy dynamicznie.
Obsługa błędów : jeśli zasób się nie powiedzie, nadal mamy szansę zablokować tylko te części, które zależą od skryptu, którego dotyczy problem, lub może nawet spróbować ponownie z pewnym opóźnieniem.
Wydajność stała się przewagą konkurencyjną między stronami internetowymi, a obecnie jest czynnikiem rankingowym wyszukiwania. To, co mogą zrobić skrypty dynamiczne, to naśladować zachowanie asynchroniczne w przeciwieństwie do domyślnego blokowania sposobu obsługi skryptów przez przeglądarki. Skrypty blokują inne zasoby, skrypty blokują dalszą analizę dokumentu HTML, blok skryptów interfejs użytkownika. Teraz, dzięki dynamicznym znacznikom skryptów i ich alternatywom dla różnych przeglądarek, możesz wykonywać rzeczywiste żądania asynchroniczne i wykonywać zależny kod tylko wtedy, gdy są dostępne. Twoje skrypty będą ładowane równolegle nawet z innymi zasobami, a renderowanie będzie bezbłędne.
Powodem, dla którego niektórzy ludzie trzymają się skryptów synchronicznych, jest to, że są do tego przyzwyczajeni. Myślą, że jest to sposób domyślny, łatwiejszy, a niektórzy mogą nawet pomyśleć, że to jedyny sposób.
Ale jedyną rzeczą, o którą powinniśmy się troszczyć, kiedy trzeba podjąć decyzję dotyczącą projektu aplikacji, jest doświadczenie użytkownika końcowego . A w tej dziedzinie asynchroniczności nie da się pokonać. Użytkownik otrzymuje natychmiastowe odpowiedzi (lub obiecuje), a obietnica jest zawsze lepsza niż nic. Pusty ekran przeraża ludzi. Programiści nie powinni być leniwi, aby poprawić postrzeganą wydajność .
I na koniec kilka słów o brudnej stronie. Co należy zrobić, aby działał w różnych przeglądarkach:
źródło
Odpowiedzi powyżej wskazały mi właściwy kierunek. Oto ogólna wersja tego, co mam działające:
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 }
źródło
postLoadFunction()
wezwany?script.addEventListener('load', postLoadFunction);
oznacza, że funkcja postLoadFunction jest wywoływana podczas ładowania skryptu.Miałem następujący problem (y) z istniejącymi odpowiedziami na to pytanie (i odmianami tego pytania w innych wątkach stackoverflow):
Albo nieco dokładniej:
Moje ostateczne rozwiązanie, które ładuje skrypt przed powrotem, ORAZ ma wszystkie skrypty odpowiednio dostępne w debugerze (przynajmniej dla Chrome) jest następujące:
OSTRZEŻENIE: Poniższy kod PRAWDOPODOBNIE powinien być używany tylko w trybie programowania. (W trybie „wydania” zalecam wstępne pakowanie i minifikację BEZ dynamicznego ładowania skryptów lub przynajmniej bez 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); } };
źródło
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'); }) */ }
źródło
Wygląda to na przyzwoity przegląd dynamicznego ładowania skryptów: http://unixpapa.com/js/dyna.html
źródło
Jestem przyzwyczajony do posiadania wielu plików .js w mojej witrynie internetowej, które są od siebie zależne. Aby je załadować i upewnić się, że zależności są oceniane we właściwej kolejności, napisałem funkcję, która ładuje wszystkie pliki, a następnie, gdy wszystkie zostaną odebrane,
eval()
je. Główną wadą jest to, że ponieważ nie działa to z CDN. W przypadku takich bibliotek (np. JQuery) lepiej jest uwzględniać je statycznie. Zwróć uwagę, że dynamiczne wstawianie węzłów skryptów w HTML nie gwarantuje, że skrypty są oceniane we właściwej kolejności, przynajmniej nie w Chrome (to był główny powód napisania tej funkcji).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); } }
Nie wymyśliłem, jak tworzyć odwołania do tej samej wartości bez tworzenia tablicy (dla licznika). W przeciwnym razie myślę, że jest to oczywiste (kiedy wszystko jest załadowane,
eval()
każdy plik w podanej kolejności, w przeciwnym razie po prostu zapisz odpowiedź).Przykład użycia:
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' ]) ;
źródło
Jak na ironię, mam to, czego chcesz, ale chcesz czegoś bliższego temu, co miałeś.
load
Ładuję rzeczy dynamicznie i asynchronicznie, ale z takim wywołaniem zwrotnym (używając dojo i xmlhtpprequest)dojo.xhrGet({ url: 'getCode.php', handleAs: "javascript", content : { module : 'my.js' }, load: function() { myFunc1('blarg'); }, error: function(errorMessage) { console.error(errorMessage); } });
Bardziej szczegółowe wyjaśnienie można znaleźć tutaj
Problem polega na tym, że gdzieś wzdłuż linii kod jest oceniany i jeśli coś jest nie tak z twoim kodem,
console.error(errorMessage);
instrukcja wskaże wiersz, w którymeval()
jest, a nie rzeczywisty błąd. To jest TAKI duży problem, do którego faktycznie próbuję wrócić<script>
instrukcje (patrz tutaj .źródło
<script>
tagów i używając konwencji (wraz z niektórymi pakietami kompilacji), aby po prostu spakować mój js w sposób, który ma sens.Działa to w przypadku nowoczesnych „wiecznie zielonych” przeglądarek, które obsługują async / await i fetch .
Ten przykład jest uproszczony, bez obsługi błędów, aby pokazać podstawowe zasady w działaniu.
// 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' ] )
źródło
webpack
... 1. dla każdego skryptu wysyła anew HTTP request
, 2. To również nie sprawdzi zależności między nimi, 3. Nie wszystkie przeglądarki obsługująasync/await
i 4. Wydajność jest nudna, a potem normalna. Byłoby dobrze,head