4

不是重复的,因为我还没有在其他线程上找到令人满意的答案:


寻找原生 Javascript答案,没有 jQuery,没有 requireJS,等等:)


整个问题的总结:

我想异步加载脚本但已下令执行

我试图强制插入的脚本元素中的代码完全按照它们添加到 dom 树的顺序执行。

也就是说,如果我插入两个脚本标签,第一个和第二个,无论谁先完成加载,第一个中的任何代码都必须在第二个之前触发。

我在插入头部时尝试了async属性和defer属性,但似乎没有遵守。

我试过 element.setAttribute("defer", "") 和 element.setAttribute("async", false) 和其他组合。

我目前遇到的问题是在包含外部脚本时必须做的,但这也是我在有延迟的情况下执行的唯一测试。

第二个脚本是本地脚本,总是在第一个脚本之前触发,即使它后来被插入到 dom 树( head )中。

A)请注意,我仍在尝试将两个脚本元素插入 DOM。当然上述可以通过先插入来实现,让它完成并插入第二个,但我希望有另一种方法,因为这可能会很慢。

我的理解是 RequireJS 似乎就是这样做的,所以它应该是可能的。但是,requireJS 可能会按照 A) 中的描述进行操作。

如果您想直接在 firebug 中尝试代码,只需复制并粘贴:

    function loadScript(path, callback, errorCallback, options) {
            var element = document.createElement('script');
            element.setAttribute("type", 'text/javascript');
            element.setAttribute("src", path);

            return loadElement(element, callback, errorCallback, options);
    }

    function loadElement(element, callback, errorCallback, options) {
            element.setAttribute("defer", "");
            // element.setAttribute("async", "false");

            element.loaded = false;

            if (element.readyState){  // IE
                    element.onreadystatechange = function(){
                            if (element.readyState == "loaded" || element.readyState == "complete"){
                                    element.onreadystatechange = null;

                                    loadElementOnLoad(element, callback);
                            }
                    };
            } else {                 // Others
                    element.onload = function() {
                            loadElementOnLoad(element, callback);
                    };
            }

            element.onerror = function() {
                    errorCallback && errorCallback(element);
            };

            (document.head || document.getElementsByTagName('head')[0] || document.body).appendChild(element);

            return element;
    }

    function loadElementOnLoad(element, callback) {
            if (element.loaded != true) {
                    element.loaded = true;
                    if ( callback ) callback(element);
            }
    }




loadScript("http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js",function() {
  alert(1);  
})

loadScript("http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",function() {
  alert(2);  
})

如果您像萤火虫一样尝试上面的代码,大多数情况下它会触发 2,然后是 1。我想确保 1 和 2,但将两者都包含在头部。

4

5 回答 5

2

好的,我想我现在想出了一个解决方案。

诀窍是我们跟踪要加载的每个脚本及其顺序,因为我们将它们插入到 dom 树中。然后,他们的每个回调都会相应地注册到他们的元素。

然后我们跟踪所有加载完成的时间,当他们完成加载时,我们遍历堆栈并触发他们的回调。

var stack = [];
stack.loaded = 0;
function loadScriptNew(path, callback) {
        var o = { callback: callback };
        stack.push(o);

        loadScript(path, function() {
                o.callbackArgs = arguments;
                stack.loaded++;
                executeWhenReady();
        });
}

function executeWhenReady() {
        if ( stack.length == stack.loaded ) {
                while(stack.length) {
                        var o = stack.pop();
                        o.callback.apply(undefined, o.callbackArgs);
                }

                stack.loaded = 0;
        }
}

// 以上是问题中代码中添加的内容。

function loadScript(path, callback) {
        var element = document.createElement('script');
        element.setAttribute("type", 'text/javascript');
        element.setAttribute("src", path);

        return loadElement(element, callback);
}

function loadElement(element, callback) {
        element.setAttribute("defer", "");
        // element.setAttribute("async", "false");

        element.loaded = false;

        if (element.readyState){  // IE
                element.onreadystatechange = function(){
                        if (element.readyState == "loaded" || element.readyState == "complete"){
                                element.onreadystatechange = null;

                                loadElementOnLoad(element, callback);
                        }
                };
        } else {                 // Others
                element.onload = function() {
                        loadElementOnLoad(element, callback);
                };
        }

        (document.head || document.getElementsByTagName('head')[0] || document.body).appendChild(element);

        return element;
}

function loadElementOnLoad(element, callback) {
        if (element.loaded != true) {
                element.loaded = true;
                if ( callback ) callback(element);
        }
}

loadScriptNew("http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js",function() {
        alert(1);
});

loadScriptNew("http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",function() {
        alert(2);
});

好的,你们中的一些人可能会争辩说问题中缺少信息,我会给你,这里我们实际上只是解决回调。你说的对。脚本中的代码仍然以错误的顺序执行,但回调是现在。

但是对我来说这已经足够好了,因为我打算包装在方法调用中加载的所有代码,alá AMD,例如 require 或 define 调用,并将在那里放入堆栈,然后在回调中触发它们。

我仍然希望 Asad 和他的 iframe 解决方案,我相信这可能会为这个问题提供最好的答案。不过对我来说,这个解决方案将解决我的问题:)

于 2013-07-31T22:20:27.020 回答
1

如果我插入两个脚本标签,第一个和第二个,无论谁先完成加载,第一个中的任何代码都必须在第二个之前触发。我尝试过使用 async 属性和 defer 属性

不,asyncdefer不会在这里帮助你。每当您将脚本元素动态插入 DOM 时,它们都会异步加载和执行。你不能对此做任何事情。

我的理解是 RequireJS 似乎就是这样做的

不。即使使用 RequireJS,脚本也是异步执行的,而且不是按顺序执行的。只有那些脚本中的模块初始化函数只是define()d,而不是执行。然后,Requirejs 会查看它们的依赖关系何时满足,并在稍后加载其他模块时执行它们。

当然,您可以重新发明轮子,但您必须使用类似 requirejs 的结构。

于 2013-07-31T19:45:50.783 回答
1

经过一段时间摆弄它,这就是我想出的。对脚本的请求会立即发送,但它们仅按指定的顺序执行。

算法:

该算法是维护一棵需要执行的脚本的树(我没有时间实现它:现在它只是列表的退化情况)。几乎同时发送所有这些请求。每次加载脚本时,都会发生两件事:1) 将脚本添加到已加载脚本的平面列表中,以及 2) 从根节点向下,因为每个分支中已加载但尚未执行的脚本数量为执行。

很酷的一点是,并非所有脚本都需要加载才能开始执行。

实施:

出于演示目的,我在scriptsToExecute数组上向后迭代,以便在请求 forCFInstall之前发送 for 请求angularJS。这并不一定意味着CFInstall将在之前加载angularJS,但它发生的可能性更大。不管怎样,angularJS总是会在之前进行评估CFInstall

请注意,就创建 iframe 元素和分配负载处理程序而言,我使用 jQuery 让我的生活更轻松,但您可以在没有 jQuery 的情况下编写此代码:

// The array of scripts to load and execute

var scriptsToExecute = [
    "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js?t=" + Date.now(), 
    "http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js?t=" + Date.now()
];


// Loaded scripts are stored here

var loadedScripts = {};


// For demonstration purposes, the requests are sent in reverse order.
// They will still be executed in the order specified in the array.

(function start() {
    for (var i = scriptsToExecute.length - 1; i >= 0; i--) {
        (function () {
            var addr = scriptsToExecute[i];
            requestData(addr, function () {
                console.log("loaded " + addr);
            });
        })();
    }
})();


// This function executes as many scripts as it currently can, by
// inserting script tags with the corresponding src attribute. The
// scripts aren't reloaded, since they are in the cache. You could
// alternatively eval `script.code`

function executeScript(script) {
    loadedScripts[script.URL] = script.code

    while (loadedScripts.hasOwnProperty(scriptsToExecute[0])) {
        var scriptToRun = scriptsToExecute.shift()
        var element = document.createElement('script');
        element.setAttribute("type", 'text/javascript');
        element.setAttribute("src", scriptToRun);

        $('head').append(element);

        console.log("executed " + scriptToRun);
    }
}


// This function fires off a request for a script

function requestData(path, loadCallback) {
    var iframe = $("<iframe/>").load(function () {
        loadCallback();
        executeScript({
            URL: $(this).attr("src"),
            code: $(this).html()
        });
    }).attr({"src" : path, "display" : "none"}).appendTo($('body'));
}

你可以在这里看到一个演示。观察控制台。

于 2013-07-31T20:49:42.053 回答
1

我在这里像草稿一样发布
这不起作用,因为跨域警察
这里的想法是首先获取所有脚本,当它们在内存中时,按顺序执行它们。

function loadScript(order, path) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET",path,true);
    xhr.send();
    xhr.onreadystatechange = function(){
        if(xhr.readyState  == 4){
            if(xhr.status >= 200 && xhr.status < 300 || xhr == 304){
                loadedScripts[order] = xhr.responseText;
            }
            else {
                //deal with error
                loadedScripts[order] = 'alert("this is a failure to load script '+order+'");';
                // or  loadedScripts[order] = '';  // this smoothly fails
            }
            alert(order+' - '+xhr.status+' > '+xhr.responseText);                // this is to show the completion order.  Careful, FF stacks aletrs so you see in reverse.
            // am I the last one ???
            executeAllScripts();
        }
    };
}

function executeAllScripts(){
    if(loadedScripts.length!=scriptsToLoad.length) return;
    for(var a=0; a<loadedScripts.length; a++) eval(loadedScripts[a]);
    scriptsToLoad = [];
}


var loadedScripts = [];
var scriptsToLoad = [
   "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js",
   "http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",
   "http://nowhere.existing.real_script.com.ar/return404.js"
];

// load all   even in reverse order ... or randomly
for(var a=0; a<scriptsToLoad.length; a++) loadScript(a, scriptsToLoad[a]);
于 2013-07-31T20:58:42.157 回答
0

你不能使用你的回调嵌套加载吗?

IE:

loadScript("http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js",function() {
    alert(1);
    loadScript("http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",function() {
        alert(2);  
    })
})
于 2013-07-31T18:42:03.843 回答