Kontext eines nachgeladenen Scriptes
JürgenB
- javascript
Hallo,
besteht die Möglichkeit, ein nachgeladenes Script nicht im globalen Kontext laufen zu lassen, sondern im Kontext einer anderen Funktion, z.B. der, die das Nachladen ausgelöst hat?
Ich habe, um den Namespace sauber zu halten, das Script in eine anonyme Funktion gebettet. Werden von hier aus jetzt weitere Scripte nachgeladen, liegen diese leider wieder im globalen Namespace.
Als Lösung fällt mir z.Zt. nur AJAX und eval ein, gibt es da besseres?
Gruß, Jürgen
Hallo,
Ich habe, um den Namespace sauber zu halten, das Script in eine anonyme Funktion gebettet. Werden von hier aus jetzt weitere Scripte nachgeladen, liegen diese leider wieder im globalen Namespace.
Das ist nicht zu vermeiden, ohne sich auf den Kopf zu stellen. Sinnvoller wäre es, mit einer richtigen Modularisierung zu arbeiten. Das kann vom einfachen Revealing Module Pattern bis zu Lösungen wie RequireJS reichen. Scripte sollten sich in bestehende Module einklinken oder eigene erzeugen – andernfalls kann man nicht sinnvoll Code nachladen, ohne dass das globale Objekt verschont wird.
Als Lösung fällt mir z.Zt. nur AJAX und eval ein, gibt es da besseres?
Damit kannst du wahrscheinlich nicht erreichen, was du vorhast. Beispiel:
(function () {
// Hier kannst du direkt eval aufrufen und 'var' (sowie Function Declarations) in dem Code erzeugen lokale Variable in diesem Function-Scope. Aber der Code kann auch globale Variablen erzeugen:
eval('var foo = 123; bar = 456;');
alert(typeof foo); // Number
alert(typeof window.foo); // undefined
alert(typeof bar); // Number, wegen Scope Chain
alert(typeof window.bar); // Number
// Bei weiterer Verschachtelung wird es schwieriger:
function loadScript () {
var xhr = ...;
// Hier kannst Code laden und du eval nutzen, aber mit var deklarierte Variablen werden im loadScript-Scope angelegt
eval(xhr.responseText);
// Angenommen xhr.responseText == 'var quux = 789;'
alert(typeof quux); // Number
alert(typeof window.quux); // undefined
}
loadScript();
// Hier kannst du schon nicht mehr darauf zugreifen
alert(typeof quux); // undefined
alert(typeof window.quux); // undefined
})();
Du könntest natürlich das eval irgendwie in der obersten IIFE unterbringen, damit es in allen darin verschachtelten Funktionen verfügbar ist. Dann müsstest du mit synchronem Ajax arbeiten und die Scripte beim ersten Ausführen der IIFE laden – das ist wahrscheinlich nicht Sinn der Sache, oder?
Zum Thema ist http://perfectionkills.com/global-eval-what-are-the-options/ ganz interessant, auch wenn es dort eigentlich um das Gegenteil von dem geht, was du vorhast.
Mathias
Hallo Mathias,
vielen Dank für deine Antwort. Die Links werde ich mir mal ansehen.
Wenn ich dich richtig verstanden habe, bekomme ich die Scripte also nur über eval in einen anderen Kontext, wobei das synchrone Nachladen bei meiner Anwendung kein Problem ist.
Beim kurzen Stöbern habe ich sofort eine Frage: wenn sich die nachgeladenen Module/Funktionen in das "Hauptmodul" integrieren sollen, müssen diese nicht den Namen das Hauptmoduls kennen?
Gruß, Jürgen
Hallo,
Wenn ich dich richtig verstanden habe, bekomme ich die Scripte also nur über eval in einen anderen Kontext, wobei das synchrone Nachladen bei meiner Anwendung kein Problem ist.
Ja. Mit einem direkten eval-Aufruf bekommt man ein anderes Script lokale Variablen erzeugen lassen, wenn es denn brav 'var' verwendet. Außerhalb der jeweiligen Funktion, in der das eval steht, sind diese Variablen aber nicht ohne weiteres verwendbar.
wenn sich die nachgeladenen Module/Funktionen in das "Hauptmodul" integrieren sollen, müssen diese nicht den Namen das Hauptmoduls kennen?
Ja, es muss zumindest ein gemeinsames bekanntes Objekt geben.
Aber Modul A kann ja Modul B laden und darauf zugreifen, wenn beide gemäß dem Revealing Module Pattern ausschließlich die globalen Namen »A« und »B« besetzen.
Bei RequireJS sind es die Methoden require() und define(), die allen bekannt sind. Sie halten die Module intern (also privat) vor, sodass keine weiteren
globalen Variablen nötig sind. Die Module selbst sind in diesen Funktionen gekapselt, die define übergeben werden. Schematisch ist das eine IIFE als Closure (ich habe nicht geprüft, ob es wirklich so umgesetzt ist):
(function (global) {
var derModulSpeicher = {};
global.require = function closure () {...};
global.define = function closure () {...};
})(this);
Mathias
Hi,
Als Lösung fällt mir z.Zt. nur AJAX und eval ein, gibt es da besseres?
Eine weitere Möglichkeit – wenn auch vielleicht eher theoretischer Natur – wäre die Erzeugung eines Iframes, und dann das Script in diesem zu laden.
Das erspart das Laden per AJAX und das Ausführen der Scripte per eval; sogar die Einbindung von Scripten von Fremddomains wäre damit möglich (was du bei „handelsüblichem“ AJAX erst mal nicht hast), denn die Dokumente der Iframes gehören ja zu deiner Domain.
Dafür müssen deine Scripte dann mit dem parent-Dokument und untereinander über Dokument-Grenzen hinweg kommunizieren, wenn sie etwas von einander wollen – und ob dieser Zusatzaufwand (obwohl natürlich überschaubar) es Wert ist, hängt dann wohl vom konkreten Einsatzszenario ab.
MfG ChrisB
Hallo ChrisB,
auch dir vielen Dank für deine Idee, auch wenn diese etwas abenteuerlich klingt.
Gruß, Jürgen