function-Aufruf im requireModul

- javascript
Hallo Rolf
es hat alles gut funktioniert mit dem repuire Modul. Jetzt habe ich noch eine Frage:
alter Aufruf im aufrufendem Modul
direct1 = require("./direct.js"); direct1.m(arg1,arg2,Prom,D,M,N,Tfile,ERGEB,FEHLER2);
Ergänzt durch durch function_name add ( ist eine function im aufruf.Modul)
direct1 = require("./direct.js"); direct1.m(arg1,arg2,Prom,D,M,N,Tfile,ERGEB,FEHLER2,add);
function m in Modul direct1 wird ergänzt:
function m(arg1,arg2,Prom,D,M,N,Tfile,ERGEB,FEHLER2,add){
// und Aufruf von add
add_ergebnis=add(32,48)
console.log(ergebnis) // 80
} //ende m
geht das ??
und noch etwas komplizierter:
function m(arg1,arg2,Prom,D,M,N,Tfile,ERGEB,FEHLER2,add){ ... ...
add_ergebnis=add(32,48)
console.log(add_ergenis) // -16
function add(arg1,arg2) {..
// ein anderer Ablauf nicht Addition, sondern Substraktion
}
..
}
Meine Erkenntnis: add im function m Aufruf ist eine "Argument"Variable und erhält die Referenz zu add im aufrufenden Modul. function add innerhalb der function erzeugt einen function_körper, der im Heap gespeichert wird und erhält gleichzeitig die Referenz dahin. Der Aufruf add, der VOR der neuen function add liegt, hat trotzdem das Ergebnis -16
dann: module.exports.add=add
Empfang im aufrufendem Modul:add = direct1.add dadurch erhält add im aufrufendem Modul diese neue Referenz und erzeugt mit
add(36,48) ebenfalls -16
natürlich solange Modul direct1 nicht gelöscht wird
ich bin gespannt auf Deine Antwort
und schon mal Danke
Effel
Hallo effel,
ja, das geht so.
Die Funktion add
ist im aufrufenden Code definiert und bildet dort ein Funktionsobjekt. Da kannst du als Argument überallhin übergeben (äh, nein, nicht überall, es muss der gleiche Realm sein, anders gesagt: an einen Worker übergeben kannst Du es nicht).
Im direct.js
Modul kannst Du das Objekt dann zum Aufruf der Funktion verwenden.
Wenn Du add
per function-Schlüsselwort definierst, ist der Wert in ihrem Scope festgelegt und du kannst sie nicht mehr durch eine andere Funktion überschreiben.
Was Du machen kannst, ist per let eine Variable zu definieren und der eine anonyme Funktion zuzuweisen.
let add = function(x,y) { return x+y; };
direct1.m(..., add);
add = function(x,y) { return x-y; };
direct1.m(..., add);
Hier würde m beim ersten Mal mit der Additionsfunktion und beim zweiten Mal mit der Subtraktionsfunktion aufgerufen.
ABER so sollte man das nicht machen, es sei denn, man möchte Putins Spione verwirren. Besser wäre es schon, von vorneherein zwei Funktionen zu definieren und je nach Bedarf die eine oder andere zu übergeben. Innerhalb von m sollte sie dann auch nicht add
heißen, sondern einen sinnvollen Namen tragen. Du übergibst eine mathematische Operation, die durchzuführen ist, dann nenn das Ding operation
. Wenn es in deinem echten Businesskontext einen besseren Namen gibt, dann wähle den.
Beachte auch, dass Funktionsobjekte den Scope, in dem sie definiert wurden, als Closure mitnehmen. Falls Du in direct1.m das Funktionsobjekt speicherst, kann das dazu führen, dass der Scope der Funktion länger existiert als erwartet. Das kann zu Speicherlecks führen.
function foo(modul) {
const data = Array(10000);
const q = 7;
function add7(x,y) { return x+y+q; }
modul.m(..., add7);
}
foo(direct1);
Normalerweise ist es so, dass die lokalen Daten von foo() nach dem Ende von foo() freigegeben werden. Aber: die add-Funktion nimmt ihren Scope mit, denn innerhalb von add() kannst Du auf diesen Scope zugreifen. Deshalb kann add7 auf q zugreifen, obwohl die m-Funktion in direct.js nichts davon sieht.
FALLS jetzt die m-Funktion das add7-Funktionsobjekt irgendwo längerfristig speichert, dann hängt da der Scope von add7 hinten dran. Und dazu gehört nicht nur q, sondern auch das data-Array! Eine gute Runtime-Engine kann sowas erkennen und die data-Referenz aus der Closure herausoptimieren (C# tut sowas). Aber ich weiß nicht, ob die aktuellen JavaScript-Engines das können.
Das ist aber schon ein Spezialfall. Solange Du die Funktionsreferenz nicht über die Dauer des Aufrufs von m hinaus speicherst, ist das alles egal.
Rolf
Rolf,
ich danke sehr und werde mich erstmal eingehend damit beschäftigen.
Natürlich werde ich die verrückte function-Sache nicht machen, es hat mich nur interessiert. Aber nun werde ich alles durchdenken.
Ich komme aus der Zeit als man noch die Programme in Machinensprache schrieb und habe Probleme mit den theoretischen Begriffen:
Hier meine Erkenntnisse:
Der Heap ist ein Speicherbereich im Hauptspeicher, in dem alles das gespeichert ist, was über die Verarbeitungsbreite des Prozessors hinausght,
Der wichtigste Teil einer Referenz ist die Adresse, die dazu dient, den Speicherbereich im Heap zu erreichen.
Im Heap sind gespeichert: Arrays, Zeichenketten, function-körper u.ä
Ein Objekt ist im wesentlichen ein Speicherbereich im Heap.
Wenn man nicht aufpasst, reicht der Speicherbereich nicht mehr aus und es gibt die berüchtigen Überlauf.
Wenn eine Zeichenkette mit const eingerichtet wird, bleibt sie unverändert im Heap stehen - leider,
Der function-Name ist ähnlich wie ein Variablenname und enthält die Referenz, bzw die Adresse des functions-körper im Heap
Das Besondere: im Module direct1 functionsaufruf m kann diese Referenz an die "Argument"variable add weiter gegeben werden, aber kann dort nicht mehr überschrieben werden.
Ein Scope ist ein Gültigkeitsbereich, z.B. für Varaible.
Eigentlch gibt es im compilierten ereich keine Varaiblennamen mehr, sondern nur noch Adressen, auf die der prozessor zugreift.
So kämpfe ich mit den neuen Begriffen...
Mit freundl.Grüßen
Effel
Hallo effel,
Ich komme aus der Zeit als man noch die Programme in Machinensprache schrieb
Meine ersten Programme entstanden auf einem TI-59 und einem 6502-Minicomputer mit 8 Stellen 7-Segmentanzeige und ca 20 Tasten (16 davon 0-F). Dann kamen BASIC, PASCAL, 8086 Assemler, COBOL, PL/1, IBM/370 Assembler, dBase, C, C++ (nur die Basics), Smalltalk, C#, PHP und JavaScript. Soll nur heißen: Wenn man vom Assembler her kommt, sind OOP und funktionale Basics nicht außer Reichweite. Aber ganz schön weit weg. Bei rein funktionalen Sprachen wie Elixier, womit Christian Kruse das Forum gemacht hat, gehen bei mir allerdings die Klappen schlagartig zu.
Heap
Ist ein Speicherbereich der JavaScript-Runtime, wo alles liegt, was nicht auf den Stack passt. Und, ja, das ist so gut wie alles. Arrays, Objekte, vorcompilierter JavaScript-Code, und interne Objekte wie Funktions- oder Modul-Scopes. Der Heap wird dynamisch verwaltet und kann von JS im Hintergrund reorganisiert werden (Garbage Collection).
Adressen
Zeigen auf die auf dem Heap gespeicherten Dinge, ABER: wenn der Garbage Collector ein Objekt an eine andere Adresse verschiebt, müssen alle Stellen, die die Adresse dieses Objekts enthalten, korrigiert werden. Dazu muss er entweder exakt nachhalten, wo solche Stellen sind, oder es darf nur eine einzige Stelle geben: eine Tabelle aller vorhandenen Objekte.
Lösung 1 ist speicheraufwändiger, hat aber nur Zeitaufwand beim Speichern von Adressen. Lösung 2 ist einfacher, aber jeder Lesezugriff muss über die Objekttabelle gehen und dauert deshalb länger. Ich weiß nicht, wie es die JS Engines machen.
Überlauf
Stack und Heap werden von JS verwaltet. Reicht ihr Platz nicht, kann JS weiteren Platz vom Betriebssystem anfordern. Das ist auf modernen Prozessoren ziemlich einfach, weil die Prozesse keinen direkten Speicher bekommen, sondern einen virtuellen Adressraum, der per Hardware im Prozessor auf den realen Speicher abgebildet wird. Ich kann also für jeden Prozess einfach mal 2GB oder mehr virtuellen Speicher anlegen, den Stack oben unter die Decke hängen und den Heap von unten wachsen lassen. Realer Speicher wird nur soviel belegt, wie auch genutzt wird.
Was Du meinst, sind Zugriffe über die Grenze von Speicherbereichen hinaus, z.B. Zugriff auf Index 1000 eines Arrays mit 100 Elementen. Das geht in JS nicht, weil diese Zugriffe von der Engine überwacht werden.
const und Zeichenketten
Nein. Mit const definierst Du, dass eine Variable nur einmal befüllt und dann nicht mehr verändert werden kann. Das ist ziemlich oft sinnvoll, und der Just-in-Time Compiler (JIT) von JavaScript kann const-Variablen besser optimieren. Allerdings würde ich vermuten, dass moderne JITs den const-Zustand einer Variablen auch selbstständig erkennen können. const/let ist vor allem für den Leser des Codes von Bedeutung.
Zeichenketten hingegen sind in JavaScript und vielen anderen Sprachen ohnehin immutable. D.h. hier
let a = (new Date()).toString();
a = "Otto";
console.log(a); // Otto
a[3] = "i"; // Keine Fehlermeldung, aber:
console.log(a); // Nicht Otto, sondern immer noch Otto
wird der Heapspeicherbereich, wo die Stringdarstellung des Datums steht, nicht durch "Otto" überschrieben. Statt dessen wird die Verbindung von a auf diesen Bereich aufgehoben. Irgendwann läuft der Garbage Collector und räumt den alten String mit dem Datum ab.
Die Variable a verweist nun auf den String "Otto". Dieser String ist unveränderlich, das liegt aber nicht daran, dass er ein String-Literal ist, das gilt für jeden String.
function-Name
Der Name ist ein Name im Programmcode. Und er landet auch in der name
Eigenschaft des Objekts, das die Funktion repräsentiert. Dieser Name bleibt unverändert, auch wenn das Objekt irgendwohin übergeben wird.
function hugo1() { return "Hier ist hugo1/" + hugo1.name; }
const hugo2 = function() { return "Hier ist hugo2/" + hugo2.name; }
function otto(func) {
console.log("Ergebnis von " + func.name + ": " + func());
}
otto(hugo1);
// Ergebnis von hugo1: Hier ist hugo1/hugo1
otto(hugo2);
// Ergebnis von hugo2: Hier ist hugo2/hugo2
Es sieht so aus, als wäre function hugo1() {...}
und const hugo2 = function() {...}
so ziemlich das Gleiche. Ja, so ziemlich. Exakt gleich aber nicht. Das Wiki diskutiert das ausführlich.
im Module direct1 functionsaufruf m kann diese Referenz an die "Argument"variable add weiter gegeben werden...
Ja, so wie jeder JavaScript-Wert. Wo nun der genau Ort ist, an dem die Weitergabe erfolgt, kann man diskutieren. Vom Prinzip her legt der Aufrufer alle Parameter und die Rückkehradresse auf den Stack (so wie auch im Assembler) und der Aufgerufene weiß, dass mit dem Namen "add" der so-und-so-vielte Parameter auf dem Stack gemeint ist.
...aber kann dort nicht mehr überschrieben werden.
Naja, wenn Du an add
etwas zuweist, wird der Wert auf dem Stack schon überschrieben. Aber eben nicht die Stelle, wo der Stackwert hergekommen ist.
Ein Scope ist ein Gültigkeitsbereich, z.B. für Variable.
Zur Compile-Zeit: Ja. Zur Laufzeit wird bei einem Funktionsaufruf zu jedem Scope ein Aufrufkontext erzeugt, der die Variablen dieses Scopes aufnimmt. Dieser Aufrufkontext kannst Du Dir wie ein Objekt auf dem Heap vorstellen, dessen Eigenschaften die lokalen Variablen sind. Jede Funktion, die in diesem Aufrufkontext definiert wird, bekommt einen internen Verweis auf diesen Kontext. Deswegen schrieb ich vorhin auch, dass der Aufrufer "vom Prinzip her" die Parameter auf den Stack legt. Tatsächlich sind die Funktionsparameter Teil des Aufrufkontextes. Das müssen sie sein, damit Closures funktionieren.
function getAdder(summand) {
return function(x) { return x + summand; }
}
const add7 = getAdder(7);
console.log(add7(3)); // gibt 10 aus
getAdder gibt eine Funktion zurück, die den Wert addiert, der an getAdder übergeben wurde. Im Beispiel wird diese Funktion in add7 gespeichert. Wenn add7 aufgerufen wird, ist getAdder schon zu Ende, d.h. der Stackframe des getAdder-Aufrufs ist nicht mehr da. Trotzdem kann die von getAdder zurückgegebene Funktion noch auf den summand-Parameter zugreifen. Warum geht das?
Eigentlich gibt es im compilierten Bereich keine Variablennamen mehr, sondern nur noch Adressen, auf die der Prozessor zugreift.
Im Wesentlichen hat Du recht. Das ist genau so wie bei Symbolen, die Du im Assembler-Sourcecode definierst. Es gibt aber Fälle, wo der Name zur Laufzeit auf anderem Wege noch verfügbar ist:
So kämpfe ich mit den neuen Begriffen...
Ja, das ist einfach viel Stoff. Und an machen Stellen muss man echt aufpassen, dass man sich das gedanklich nicht zu sehr vereinfacht und in Fallen tappt.
Rolf
Da hast Du Dir viel Arbeit gemacht und ich habe erst einmal genung
mit freundl Gruß
Effel