异步JavaScript动态包含

Asynchronously JavaScript dynamic includes

本文关键字:包含 动态 JavaScript 异步      更新时间:2023-09-26

我有很多JavaScript文件,以三个为例:App.js、ChildApp.js(不止一个)和AjaxManager.js,而我添加的是AjaxManger.js。

ChildApp扩展了App,并且App依赖于AjaxManager。

应用程序中的构造函数:

function App() {
    this.ajaxManager=new AjaxManager();//Need AjaxManager.js to import, especially when new ChildApp is called.
    ....
}

ChildApp的构造函数:

function ChildApp's() {
    App.call(this); //
}
ChildApp.prototype = new App(); 
...

在遗留代码中,有几十个地方包含App.js,我不想复制和粘贴之前包含AjaxManager.js的代码。

但如果我没有很好地包括AjaxManager,我将在时出错

var app=new ChildApp();//ReferenceError: AjaxManager is not defined

因此,我在App.js的开始中使用以下代码:

if(typeof AjaxManager =='undefined'){
    console.log('AjaxManager is not loaded , getting the script');
    $.ajax({
        url: .....//Url to load
        dataType: "script",
        async:false// How can I use async =true 
    });
}

但是我必须对请求使用async:false,这在控制台中向我显示了一个警告:主线程上的同步XMLHttpRequest已被弃用,因为它对最终用户的体验有不利影响

否则我会有错误:

ReferenceError: AjaxManager is not defined

我如何使用异步请求来加载脚本,并确保一切正常,但不会破坏我现有的功能。

更新:

  1. 在我问这个问题之前,我在App.js的开头或函数App()的开头尝试了$.getScript(url),但同样的错误,我想应该在回调中放置一些与构造函数App相关的东西,但我不知道如何放置。

  2. 我希望我只能修改app.js或ajaxmanager.js,因为调用new app()或new ChildApp()的地方太多了

不要使用async: false。这是一种非常糟糕的做法,因为它会阻塞UI线程,直到请求完成。

相反,您可以使用$.getScript来缩短代码,并使用回调处理程序来执行依赖于正在加载的脚本的任何代码。试试这个:

if (typeof AjaxManager =='undefined') {
    $.getScript('ajaxManager.js', processPage);
}
else {
     // AjaxManager already loaded
     processPage();
}
function processPage() {
    // put your logic that relies on AjaxManager here 
}

如果您想用jQuery按特定顺序加载三个脚本,可以使用$.getScript()并将每个脚本链接到前一个脚本的已完成promise中。

$.getScript('AjaxManager.js').then(function() {
    return $.getScript('App.js');
}).then(function() {
    return $.getScript('ChildApp.js');
});

或者,如果你想把它变成一个函数,你可以把一系列脚本传递给:

function loadScripts(array) {
    return array.reduce(function(p, scriptFile) {
        return p.then(function() {
            return $.getScript(scriptFile);
        });
    }, $.Deferred().resolve().promise());
}
loadScripts('AjaxManager.js', 'App.js', 'ChildApp.js');
// The order of the scripts in the array is the order they will be loaded.

如果您想测试它们以前是否已加载,那么您也可以为每个脚本传递一个测试符号,并将其合并到加载逻辑中。

function loadScripts(array) {
    return array.reduce(function(p, obj) {
        return p.then(function() {
            if (typeof window[obj.testSymbol] === "undefined") {
                return $.getScript(scriptFile);
             } else {
                 return $.Deferred().resolve().promise();
             }
        });
    }, $.Deferred().resolve().promise());
}
loadScripts(
    {file: 'AjaxManager.js', testSymbol: 'AjaxManager'}, 
    {file: 'App.js', testSymbol: 'App'}, 
    {file: 'ChildApp.js', testSymbol: 'ChildApp'}
);    

这将允许您根据需要多次尝试加载任何给定的脚本,而它实际上只加载一次。


仅供参考,$.getScript()是一个异步操作。它调用在加载完成时传递给它的回调,或者解析它返回的promise。如果您希望某些操作只在$.getScript()完成加载后进行,那么您必须将该代码放入回调中。

因此,如果您想在App()构造函数中加载AjaxManager脚本,那么在构造函数返回之前,您不能这样做。在Javascript中,不建议您在构造函数中启动异步操作,因为没有好的方法来处理错误,并且在异步操作完成之前,您可能会有一个部分初始化的对象。

有关构造函数中异步操作的一些注释,请参阅本答案的后半部分。