Javascript, выполнить код сценариев, чтобы вставить в дерево
НЕ ДУБЛИКАТ, КАК Я ДОЛЖЕН НАЙТИ УДОВЛЕТВОРИТЕЛЬНЫЙ ОТВЕТ НА ДРУГИЕ РЕЗЬБЫ:
Ищите собственные Javascript ответы, нет jQuery, no requireJS и т.д., пожалуйста:)
РЕЗЮМЕ ВСЕГО ВОПРОСА:
Я хочу асинхронно загружать скрипты, но заказывать исполнение
Я пытаюсь обеспечить, чтобы код во вставленных элементах Script выполнялся точно в том же порядке, поскольку они были добавлены в дерево dom.
То есть , если я вставляю два Script тега, первый и второй, любой код в первом должен стрелять до второго, независимо от того, кто заканчивает загрузку первым.
Я пробовал с атрибутом async и отложить при вставке в голову, но, похоже, не подчиняется.
Я попытался использовать element.setAttribute( "defer", ") и element.setAttribute(" async", false) и другие комбинации.
Проблема, которую я испытываю в настоящее время, должна выполняться при включении внешнего script, но это также единственный тест, который я выполнил там, где есть латентность.
Второй script, который является локальным , всегда запускается перед первым, даже если он вставлен впоследствии в дереве dom (head).
A) Примечание, что я все еще пытаюсь вставить оба элемента Script в 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);
})
Если вы попробуете приведенный выше код в виде firebug, чаще всего он будет стрелять 2, а затем 1. Я хочу обеспечить 1, а затем 2, но включить оба в голову.
Ответы
Ответ 1
Хорошо, я думаю, что у меня есть решение.
Фокус в том, что мы отслеживаем каждый загружаемый script и их порядок, когда мы вставляем их в дерево 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);
});
Хорошо, некоторые из вас могут утверждать, что в вопросе отсутствует информация, которую я вам дам, и здесь мы фактически решаем обратный вызов. Ты прав. Код в script все еще выполняется в неправильном порядке, но обратный вызов теперь.
Но для меня это достаточно хорошо, так как я намерен обернуть весь код, загружаемый в вызов метода, например AMD, например вызов или определение вызова, и будет помещен в стек, а затем запустить их в обратном вызове вместо.
Я все еще надеюсь на Асада и его решение iframe, которое, я считаю, может дать лучший ответ на этот вопрос. Для меня, однако, это решение решит мои проблемы:)
Ответ 2
если я вставляю два тега script, первый и второй, любой код сначала должен стрелять до второго, независимо от того, кто первым заканчивает загрузку. Я попытался с атрибутом async и отменил атрибут
Нет, async
и defer
вам не помогут. Всякий раз, когда вы динамически вставляете script элементы в DOM, они загружаются и выполняются асинхронно. Вы ничего не можете сделать против этого.
Я понимаю, что RequireJS, похоже, делает именно это
Нет. Даже с RequireJS скрипты выполняются асинхронно и не в порядке. Только функции инициализатора модуля в этих сценариях просто define()
d, а не выполняются. Requirejs тогда смотрит, когда их зависимости удовлетворяются и исполняет их позже, когда загружаются другие модули.
Конечно, вы можете изобрести колесо, но вам придется идти с подобной структурой.
Ответ 3
Через некоторое время возиться с ним, вот что я придумал. Запросы для скриптов отправляются немедленно, но они выполняются только в указанном порядке.
Алгоритм:
Алгоритм состоит в том, чтобы поддерживать дерево (у меня не было времени реализовать это: прямо сейчас это просто вырожденный случай списка) скриптов, которые нужно выполнить. Запросы на все они отправляются почти одновременно. Каждый раз, когда загружается script, происходят две вещи: 1) script добавляется в плоский список загружаемых сценариев и 2) идет вниз от корня node, столько скриптов в каждой ветки, которая загружается но не были выполнены.
Приятная вещь об этом заключается в том, что не все сценарии необходимо загружать для начала выполнения.
Реализация:
Для демонстрационных целей я повторяю назад массив scriptsToExecute
, так что запрос для CFInstall
отправляется перед запросом для angularJS
. Это не обязательно означает, что CFInstall
будет загружаться до angularJS
, но есть больше шансов на это. Независимо от этого, angularJS
всегда будет оцениваться до CFInstall
.
Обратите внимание, что я использовал jQuery, чтобы упростить мою жизнь, создавая элемент iframe и назначая обработчик нагрузки, но вы можете записать это без 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'));
}
Здесь вы можете увидеть демо здесь. Соблюдайте консоль.
Ответ 4
Я размещаю здесь, как черновик
Это не работает, потому что междоменная полиция
Здесь идея состоит в том, чтобы сначала получить все сценарии и когда они находятся в памяти, выполните их в порядке.
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]);
Ответ 5
Не можете ли вы загружать загрузку с помощью обратных вызовов ur?
т
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);
})
})