56

我正在尝试动态加载 JS 脚本,但不能选择使用 jQuery。

我检查了 jQuery 源代码以了解getScript是如何实现的,以便我可以使用该方法使用本机 JS 加载脚本。但是,getScript 只调用 jQuery.get()

而且我一直无法找到 get 方法的实现位置。

所以我的问题是,

使用本机 JavaScript 实现我自己的 getScript 方法的可靠方法是什么?

谢谢!

4

8 回答 8

98

这是一个具有回调功能的 jQuery getScript 替代方案:

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);
}
于 2015-01-17T17:40:34.743 回答
36

您可以像这样获取脚本:

(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'));
于 2013-05-30T15:08:41.997 回答
13

用这个

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);
于 2013-05-30T15:09:51.727 回答
13

首先,感谢@Mahn 的回答。我用 ES6 重写了他的解决方案,并承诺,如果有人需要,我将把我的代码粘贴到这里:

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);
  });
}

用法:

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');
});

并且代码是eslinted的。

于 2018-04-11T16:44:20.983 回答
6

这完善了以前的 ES6 解决方案,并且适用于所有现代浏览器

加载和获取脚本作为 Promise

const getScript = url => new Promise((resolve, reject) => {
  const script = document.createElement('script')
  script.src = url
  script.async = true

  script.onerror = reject

  script.onload = script.onreadystatechange = function() {
    const loadState = this.readyState

    if (loadState && loadState !== 'loaded' && loadState !== 'complete') return

    script.onload = script.onreadystatechange = null

    resolve()
  }

  document.head.appendChild(script)
})

用法

getScript('https://dummyjs.com/js')
.then(() => {
  console.log('Loaded', dummy.text())
})
.catch(() => {
  console.error('Could not load script')
})

也适用于 JSONP 端点

const callbackName = `_${Date.now()}`
getScript('http://example.com/jsonp?callback=' + callbackName)
.then(() => {
  const data = window[callbackName];

  console.log('Loaded', data)
})

此外,请注意列出的一些 AJAX 解决方案,因为它们与现代浏览器中的 CORS 策略绑定在一起https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

于 2020-05-20T00:55:17.850 回答
5

这里有一些很好的解决方案,但很多都已经过时了。@Mahn有一个很好的,但正如评论中所述,它不能完全替代,$.getScript()因为回调不接收数据。我已经编写了自己的函数来替换它,$.get()并在我需要它为脚本工作时登陆这里。我能够使用@Mahn 的解决方案,并与我当前的$.get()替代品一起对其进行一些修改,并提出一些效果很好且易于实现的东西。

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();
}

现在我们有了一个替代品$.get()$.getScript()它的工作原理也很简单:

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;
    }
}
于 2019-06-08T19:49:36.320 回答
4

Mozilla 开发者网络提供了一个异步工作的示例,并且不使用 HTMLScriptTag 中不存在的“onreadystatechange”(来自@ShaneX 的回答):

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;
}

示例用法:

prefixScript("myScript1.js");
prefixScript("myScript2.js", function () { alert("The script \"myScript2.js\" has been correctly loaded."); });

但是应该考虑@Agamemnus 的评论:onloadFunction调用时脚本可能没有完全加载。可以使用计时器setTimeout(func, 0)让事件循环完成添加到文档的脚本。事件循环最终调用计时器后面的函数,此时脚本应该可以使用了。

但是,也许应该考虑返回一个 Promise,而不是提供两个用于异常和成功处理的函数,这将是 ES6 的方式。这也将导致不需要计时器,因为 Promise 由事件循环处理 - 因为在处理 Promise 时,脚本已经由事件循环完成。

实现包括 Promises 在内的 Mozilla 方法,最终代码如下所示:

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!"); });
于 2019-04-01T09:24:03.927 回答
0

window.addEventListener('DOMContentLoaded',
       function() {
           var head = document.getElementsByTagName('HEAD')[0];
           var script = document.createElement('script');
           script.src = "/Content/index.js";
           head.appendChild(script);
       });

于 2020-07-07T10:45:45.010 回答