同步动态加载 JavaScript

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2879509/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:19:21  来源:igfitidea点击:

Dynamically loading JavaScript synchronously

javascript

提问by Eric Schoonover

I'm using the module pattern, one of the things I want to do is dynamically include an external JavaScript file, execute the file, and then use the functions/variables in the file in the return { }of my module.

我正在使用模块模式,我想做的一件事是动态包含一个外部 JavaScript 文件,执行该文件,然后在return { }我的模块的文件中使用该文件中的函数/变量。

I can't figure out how to do this easily. Are there any standard ways of performing a pseudo synchronous external script load?

我无法弄清楚如何轻松做到这一点。是否有执行伪同步外部脚本加载的标准方法?

function myModule() {
    var tag = document.createElement("script");
    tag.type = "text/javascript";
    tag.src = "http://some/script.js";
    document.getElementsByTagName('head')[0].appendChild(tag);

    //something should go here to ensure file is loaded before return is executed

    return {
        external: externalVariable 
    }
}

采纳答案by Sean Kinsey

There is only one way to synchronously load and execute a script resource, and that is using a synchronous XHR

只有一种方法可以同步加载和执行脚本资源,那就是使用同步 XHR

This is an example of how to do this

这是如何执行此操作的示例

// get some kind of XMLHttpRequest
var xhrObj = createXMLHTTPObject();
// open and send a synchronous request
xhrObj.open('GET', "script.js", false);
xhrObj.send('');
// add the returned content to a newly created script tag
var se = document.createElement('script');
se.type = "text/javascript";
se.text = xhrObj.responseText;
document.getElementsByTagName('head')[0].appendChild(se);

But you shouldn't in general use synchronous requests as this will block everything else. But that being said, there are of course scenarios where this is appropriate.

但是您通常不应该使用同步请求,因为这会阻塞其他所有内容。但话虽如此,当然也有适合的场景。

I would probably refactor the containing function into an asynchronous pattern though using an onload handler.

尽管使用 onload 处理程序,我可能会将包含函数重构为异步模式。

回答by Neil

The accepted answeris NOTcorrect.

接受的答案正确的。

Loading a file synchronously is not the same as executing the file synchronously - which is what the OP requested.

同步加载文件与同步执行文件不同 - 这是 OP 所要求的。

The accepted answer loads the file sync, but does nothing more than append a script tag to the DOM. Just because appendChild()has returned does not in anyway guarantee that the script has finished executing and it's members are initialised for use.

接受的答案加载文件同步,但只是将脚本标记附加到 DOM。仅仅因为appendChild()已经返回并不能保证脚本已经完成执行并且它的成员被初始化以供使用。

The only (see caveat) way to achieve the OPs question is to sync load the script over XHR as stated, then read as text and pass into either eval() or a new Function() call and wait for that function to return. This is the only way to guarantee the script is loaded ANDexecuted synchronously.

实现 OPs 问题的唯一(请参阅警告)方法是按照所述通过 XHR 同步加载脚本,然后作为文本读取并传递到 eval() 或新的 Function() 调用并等待该函数返回。这是保证脚本加载同步执行的唯一方法。

I make no comment as to whether this is a wise thing to do either from a UI or security perspective, but there are certainly use cases that justify a sync load & execute.

我不评论从 UI 或安全角度这是否是明智的做法,但肯定有用例证明同步加载和执行是合理的。

Caveat: Unless you're using web workers in which case just call loadScripts();

警告:除非您使用网络工作者,在这种情况下只需调用 loadScripts();

回答by Alan Joseph

This is the code that I'm using for multiple file load in my app.

这是我用于在我的应用程序中加载多个文件的代码。

Utilities.require = function (file, callback) {
    callback = callback ||
    function () {};
    var filenode;
    var jsfile_extension = /(.js)$/i;
    var cssfile_extension = /(.css)$/i;

    if (jsfile_extension.test(file)) {
        filenode = document.createElement('script');
        filenode.src = file;
        // IE
        filenode.onreadystatechange = function () {
            if (filenode.readyState === 'loaded' || filenode.readyState === 'complete') {
                filenode.onreadystatechange = null;
                callback();
            }
        };
        // others
        filenode.onload = function () {
            callback();
        };
        document.head.appendChild(filenode);
    } else if (cssfile_extension.test(file)) {
        filenode = document.createElement('link');
        filenode.rel = 'stylesheet';
        filenode.type = 'text/css';
        filenode.href = file;
        document.head.appendChild(filenode);
        callback();
    } else {
        console.log("Unknown file type to load.")
    }
};

Utilities.requireFiles = function () {
    var index = 0;
    return function (files, callback) {
        index += 1;
        Utilities.require(files[index - 1], callBackCounter);

        function callBackCounter() {
            if (index === files.length) {
                index = 0;
                callback();
            } else {
                Utilities.requireFiles(files, callback);
            }
        };
    };
}();

And this utilities can be used by

这个实用程序可以被使用

Utilities.requireFiles(["url1", "url2",....], function(){
    //Call the init function in the loaded file.
    })

回答by Jaggler3

The most Node.js-like implementation I could come up with was able to load JS files synchonously, and use them as objects/modules

我能想到的最像 Node.js 的实现能够同步加载 JS 文件,并将它们用作对象/模块

var scriptCache = [];
var paths = [];
function Import(path)
{
    var index = 0;
    if((index = paths.indexOf(path)) != -1) //If we already imported this module
    {
        return scriptCache [index];
    }

    var request, script, source;
    var fullPath = window.location.protocol + '//' + window.location.host + '/' + path;

    request = new XMLHttpRequest();
    request.open('GET', fullPath, false);
    request.send();

    source = request.responseText;

    var module = (function concealedEval() {
        eval(source);
        return exports;
    })();

    scriptCache.push(module);
    paths.push(path);

    return module;
}

An example source (addobjects.js):

示例源 ( addobjects.js):

function AddTwoObjects(a, b)
{
    return a + b;
}

this.exports = AddTwoObjects;

And use it like this:

并像这样使用它:

var AddTwoObjects = Import('addobjects.js');
alert(AddTwoObjects(3, 4)); //7
//or even like this:
alert(Import('addobjects.js')(3, 4)); //7

回答by jeremykentbgross

I had the following problem(s) with the existing answers to this question (and variations of this question on other stackoverflow threads):

我在这个问题的现有答案中遇到了以下问题(以及这个问题在其他 stackoverflow 线程上的变体):

  • None of the loaded code was debuggable
  • Many of the solutions required callbacks to know when loading was finished instead of truly blocking, meaning I would get execution errors from immediately calling loaded (ie loading) code.
  • 没有一个加载的代码是可调试的
  • 许多解决方案需要回调来知道加载何时完成而不是真正阻塞,这意味着我会从立即调用加载(即加载)代码中得到执行错误。

Or, slightly more accurately:

或者,稍微准确一点:

  • None of the loaded code was debuggable (except from the HTML script tag block, if and only if the solution added a script elements to the dom, and never ever as individual viewable scripts.)=> Given how many scripts I have to load (and debug), this was unacceptable.
  • Solutions using 'onreadystatechange' or 'onload' events failed to block, which was a big problem since the code originally loaded dynamic scripts synchronously using 'require([filename, 'dojo/domReady']);' and I was stripping out dojo.
  • 加载的代码都不是可调试的(除了 HTML 脚本标记块,当且仅当解决方案向 dom 添加了脚本元素,并且永远不会作为单独的可见脚本。)=> 鉴于我必须加载多少脚本(和调试),这是不可接受的。
  • 使用 'onreadystatechange' 或 'onload' 事件的解决方案未能阻止,这是一个大问题,因为代码最初使用 'require([filename, 'dojo/domReady']);' 同步加载动态脚本。我正在剥离道场。

My final solution, which loads the script before returning, AND has all scripts properly accessible in the debugger (for Chrome at least) is as follows:

我的最终解决方案,在返回之前加载脚本,并在调试器中正确访问所有脚本(至少对于 Chrome)如下:

WARNING: The following code should PROBABLY be used only in 'development' mode.(For 'release' mode I recommend prepackaging and minification WITHOUT dynamic script loading, or at least without eval).

警告:以下代码可能仅用于“开发”模式。(对于“发布”模式,我建议在不加载动态脚本或至少不加载 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);
    }
};

回答by jeremykentbgross

var xhrObj = new XMLHttpRequest();
xhrObj.open('GET', '/filename.js', false);
xhrObj.send(null);
eval(xhrObj.responseText);

If this is a cross-domain request, it will not work. In that case you have to upload the requested file to your server, or make a mirror php that outputs it, and require that php.

如果这是跨域请求,它将不起作用。在这种情况下,您必须将请求的文件上传到您的服务器,或者制作一个输出它的镜像 php,并要求该 php.ini 文件。

With jquery (works with cross-domain request too):

使用 jquery(也适用于跨域请求):

$.getScript('/filename.js',callbackFunction);

callbackFunctionwill be called synchronously.

callbackFunction将被同步调用。

For loading more scripts see thisthread.

要加载更多脚本,请参阅线程。

回答by Flimm

There actually is a way to load a list of scripts and execute themsynchronously. You need to insert each script tag into the DOM, explicitly setting its asyncattribute to false:

实际上有一种方法可以加载脚本列表同步执行它们。您需要将每个脚本标签插入到 DOM 中,并将其async属性显式设置为 false:

script.async = false;

Scripts that have been injected into the DOM are executed asynchronously by default, so you have to set the asyncattribute to false manually to work around this.

默认情况下,已注入 DOM 的脚本是异步执行的,因此您必须async手动将该属性设置为 false 以解决此问题。

Example

例子

<script>
(function() {
  var scriptNames = [
    "https://code.jquery.com/jquery.min.js",
    "example.js"
  ];
  for (var i = 0; i < scriptNames.length; i++) {
    var script = document.createElement('script');
    script.src = scriptNames[i];
    script.async = false; // This is required for synchronous execution
    document.head.appendChild(script);
  }
  // jquery.min.js and example.js will be run in order and synchronously
})();
</script>

<!-- Gotcha: these two script tags may still be run before `jquery.min.js`
     and `example.js` -->
<script src="example2.js"></script>
<script>/* ... */<script>

References

参考

回答by Nir O.

the accepted answer is not correct:

接受的答案不正确:

the script.async = false;directive only means that html parsing will be paused during script execution. this does not guarantee in which order javascript code will run. see https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/

script.async = false;指令仅意味着在脚本执行期间将暂停 html 解析。这并不能保证 javascript 代码的运行顺序。请参阅https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/

the easiest and most elegant solution which was yet to be mentioned here is using promises, like so:

此处尚未提及的最简单、最优雅的解决方案是使用 promise,如下所示:

    function loadScript(url) {
      return new Promise((resolve, reject) => {
        var script = document.createElement('script')
        script.src = url
        script.onload = () => {
          resolve()
        }
        script.onerror = () => {
          reject('cannot load script '+ url)
        }
        document.body.appendChild(script)
      })
    }

and then when you want to execute scripts in order:

然后当你想按顺序执行脚本时:

        loadScript('myfirstscript.js').then(() => {
          console.log('first script ran');
          loadScript('index.js').then(() => {
            console.log('second script ran');
          })
        })

回答by Phrogz

If you need to load an arbitrary number of scripts and only proceed when the last one is done, and you cannot use XHR (e.g. due to CORS limitations) you can do the following. It is not synchronous, but does allow a callback to occur exactly when the last file is done loading:

如果您需要加载任意数量的脚本并且仅在最后一个完成后继续,并且您不能使用 XHR(例如,由于 CORS 限制),您可以执行以下操作。它不是同步的,但允许在最后一个文件加载完成时发生回调:

// Load <script> elements for all uris
// Invoke the whenDone callback function after the last URI has loaded
function loadScripts(uris,whenDone){
  if (!uris.length) whenDone && whenDone();
  else{
    for (var wait=[],i=uris.length;i--;){
      var tag  = document.createElement('script');
      tag.type = 'text/javascript';
      tag.src  = uris[i];
      if (whenDone){
        wait.push(tag)
        tag.onload = maybeDone; 
        tag.onreadystatechange = maybeDone; // For IE8-
      }
      document.body.appendChild(tag);
    }
  }
  function maybeDone(){
    if (this.readyState===undefined || this.readyState==='complete'){
      // Pull the tags out based on the actual element in case IE ever
      // intermingles the onload and onreadystatechange handlers for the same
      // script block before notifying for another one.
      for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1);
      if (!wait.length) whenDone();
    }
  }
}

Edit: Updated to work with IE7, IE8, and IE9 (in quirks mode). These IE versions do not fire an onloadevent, but do for onreadystatechange. IE9 in standards mode fires both(with onreadystatechangefor all scripts firing before onloadfor any).

编辑:更新为适用于 IE7、IE8 和 IE9(在 quirks 模式下)。这些 IE 版本不会触发onload事件,而是为onreadystatechange. 标准模式下的 IE9 会同时触发两者onreadystatechange所有脚本都先触发onload)。

Based on this pagethere may be a small chance that old versions of IE will never send an onreadystatechangeevent with readyState=='complete'; if this is the case (I could not reproduce this problem) then the above script will fail and your callback will never be invoked.

基于此页面,旧版本的 IE 可能永远不会发送onreadystatechange带有readyState=='complete';的事件的可能性很小。如果是这种情况(我无法重现此问题),则上述脚本将失败并且您的回调将永远不会被调用。

回答by Amgad Fahmi

I know this is an old question, but maybe someone else read this and find it useful ! Just created a new components uses ES6 to load scripts dynamically in synchronous way. The Project details and source code are on GitHub https://github.com/amgadfahmi/scripty

我知道这是一个老问题,但也许其他人阅读了这个并发现它很有用!刚刚创建了一个新组件,使用 ES6 以同步方式动态加载脚本。项目详情和源代码在 GitHub https://github.com/amgadfahmi/scripty