I'm trying to load JS scripts dynamically, but using jQuery is not an option.
I checked jQuery source to see how getScript was implemented so that I could use that approach to load scripts using native JS. However, getScript only calls jQuery.get()
and I haven't been able to find where the get method is implemented.
So my question is,
What's a reliable way to implement my own getScript method using native JavaScript?
Thanks!
You can fetch scripts like this:
(function(document, tag) {
var scriptTag = document.createElement(tag), // create a script tag
firstScriptTag = document.getElementsByTagName(tag)[0]; // find the first script tag in the document
scriptTag.src = 'your-script.js'; // set the source of the script to your script
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag); // append the script to the DOM
}(document, 'script'));
Here's a jQuery getScript alternative with callback functionality:
function getScript(source, callback) {
var script = document.createElement('script');
var prior = document.getElementsByTagName('script')[0];
script.async = 1;
script.onload = script.onreadystatechange = function( _, isAbort ) {
if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
script.onload = script.onreadystatechange = null;
script = undefined;
if(!isAbort && callback) setTimeout(callback, 0);
}
};
script.src = source;
prior.parentNode.insertBefore(script, prior);
}
Firstly, Thanks for @Mahn's answer. I rewrote his solution in ES6 and promise, in case someone need it, I will just paste my code here:
const loadScript = (source, beforeEl, async = true, defer = true) => {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
const prior = beforeEl || document.getElementsByTagName('script')[0];
script.async = async;
script.defer = defer;
function onloadHander(_, isAbort) {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
script = undefined;
if (isAbort) { reject(); } else { resolve(); }
}
}
script.onload = onloadHander;
script.onreadystatechange = onloadHander;
script.src = source;
prior.parentNode.insertBefore(script, prior);
});
}
Usage:
const scriptUrl = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit';
loadScript(scriptUrl).then(() => {
console.log('script loaded');
}, () => {
console.log('fail to load script');
});
and code is eslinted.
use this
var js_script = document.createElement('script');
js_script.type = "text/javascript";
js_script.src = "http://www.example.com/script.js";
js_script.async = true;
document.getElementsByTagName('head')[0].appendChild(js_script);
Mozilla Developer Network provides an example that works asynchronously and does not use 'onreadystatechange' (from @ShaneX's answer) that is not really present in a HTMLScriptTag:
function loadError(oError) {
throw new URIError("The script " + oError.target.src + " didn't load correctly.");
}
function prefixScript(url, onloadFunction) {
var newScript = document.createElement("script");
newScript.onerror = loadError;
if (onloadFunction) { newScript.onload = onloadFunction; }
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
}
Sample usage:
prefixScript("myScript1.js");
prefixScript("myScript2.js", function () { alert("The script \"myScript2.js\" has been correctly loaded."); });
But @Agamemnus' comment should be considered: The script might not be fully loaded when onloadFunction
is called. A timer could be used setTimeout(func, 0)
to let the event loop finalize the added script to the document. The event loop finally calls the function behind the timer and the script should be ready to use at this point.
However, maybe one should consider returning a Promise instead of providing two functions for exception & success handling, that would be the ES6 way. This would also render the need for a timer unnecessary, because Promises are handled by the event loop - becuase by the time the Promise is handled, the script was already finalized by the event loop.
Implementing Mozilla's method including Promises, the final code looks like this:
function loadScript(url)
{
return new Promise(function(resolve, reject)
{
let newScript = document.createElement("script");
newScript.onerror = reject;
newScript.onload = resolve;
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
});
}
loadScript("test.js").then(() => { FunctionFromExportedScript(); }).catch(() => { console.log("rejected!"); });
There are some good solutions here but many are outdated. There is a good one by @Mahn but as stated in a comment it is not exactly a replacement for $.getScript()
as the callback does not receive data. I had already written my own function for a replacement for $.get()
and landed here when I need it to work for a script. I was able to use @Mahn's solution and modify it a bit along with my current $.get()
replacement and come up with something that works well and is simple to implement.
function pullScript(url, callback){
pull(url, function loadReturn(data, status, xhr){
//If call returned with a good status
if(status == 200){
var script = document.createElement('script');
//Instead of setting .src set .innerHTML
script.innerHTML = data;
document.querySelector('head').appendChild(script);
}
if(typeof callback != 'undefined'){
//If callback was given skip an execution frame and run callback passing relevant arguments
setTimeout(function runCallback(){callback(data, status, xhr)}, 0);
}
});
}
function pull(url, callback, method = 'GET', async = true) {
//Make sure we have a good method to run
method = method.toUpperCase();
if(!(method === 'GET' || method === 'POST' || method === 'HEAD')){
throw new Error('method must either be GET, POST, or HEAD');
}
//Setup our request
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4
//Once the request has completed fire the callback with relevant arguments
//you should handle in your callback if it was successful or not
callback(xhr.responseText, xhr.status, xhr);
}
};
//Open and send request
xhr.open(method, url, async);
xhr.send();
}
Now we have a replacement for $.get()
and $.getScript()
that work just as simply:
pullScript(file1, function(data, status, xhr){
console.log(data);
console.log(status);
console.log(xhr);
});
pullScript(file2);
pull(file3, function loadReturn(data, status){
if(status == 200){
document.querySelector('#content').innerHTML = data;
}
}
来源:https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript