同步动态加载 JavaScript

IT技术 javascript
2021-02-19 06:29:53

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

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

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 
    }
}
6个回答

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

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

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

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

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

请注意,这不适用于尝试从禁止file://XHR over 的 Chrome 等浏览器上同步加载 JS 的(公认的边缘情况)file://
2021-04-19 06:29:53
这不会等待脚本同步执行。
2021-04-19 06:29:53
我最终进行了重构,以便 onload/onreadystate 更改起作用。但这是我原来问题的正确答案。
2021-05-10 06:29:53
看起来这在跨域失败。有没有办法动态同步注入 3rd 方脚本?蒂亚。
2021-05-11 06:29:53
主线程上的同步 XMLHttpRequest 已被弃用,因为它会对最终用户的体验产生不利影响。如需更多帮助xhr.spec.whatwg.org
2021-05-13 06:29:53

接受的答案正确的。

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

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

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

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

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

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

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

这个实用程序可以被使用

Utilities.requireFiles(["url1", "url2",....], function(){
    //Call the init function in the loaded file.
    })
这是异步,不是同步!
2021-04-26 06:29:53
这也适用于嵌套项目吗?假设您需要在所需文件中添加更多文件?
2021-04-28 06:29:53
在加载指定的脚本时调用回调函数。还不够好吗?
2021-05-05 06:29:53
异步不是同步。不回答 OP。
2021-05-09 06:29:53

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

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

示例源 ( addobjects.js):

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

this.exports = AddTwoObjects;

并像这样使用它:

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

接受的答案不正确:

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

此处尚未提及的最简单、最优雅的解决方案是使用 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)
      })
    }

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

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