mark: javascript - Variable Scope - Recursion - Problem

Hallo,

ich habe ein Problem mit Variable Scope in Zusammenhang mit einer Rekursiven Funktion. Ich durchsuche rekursiv Unterverzeichnisse nach einer Datei und speichere bei einem Match den vollständigen Pfad in ein Array. Leider stehe ich ziemlich auf der Leitung, wenn ich das Ganze ohne globale Variable "matches" abbilden will.

Folgende zwei Beispiele dienen nur der illustration meiner Gedankengänge. Korrigiert mich, wenn ich falsch liege, aber:

  • A funktioniert nicht, weil ich durch die Rekursion, durch den erneuten Funktionsaufruf jedesmal eine neue, leere Variable matches habe. Is klar.
  • B funktioniert nicht, weil die Variable/Funktion "recursion" noch nicht definiert ist, wenn ich sie inmitten ihrer selbst abermals aufrufe. Is auch klar.

Die Frage, die bleibt ist: Wie macht man das richtig und in schön ? :)

Beispiel A
// get all IIS config filepaths recursively
function getIISConfigFilesPath(dir){
    var folderContent = fs.readdirSync(dir);
    var matches = [];
    
    folderContent.forEach(item => {
        var stats = fs.lstatSync(dir + '/' + item);
        
        if(stats.isDirectory()){
            getIISConfigFilesPath(dir + '/' + item);
        }else if(stats.isFile()){
            if(typeof item !== 'undefined' 
               && item === config.IIS_CONFIG_FILE){
                
                matches.push(dir + '/' + item);
                console.log(matches);
            }
        }
    });
    
    return matches;
};
Beispiel B
function getIISConfigFilesPath(dir){
    
    var matches = [];
    
    var recursion = function(dir, matches){
        var folderContent = fs.readdirSync(dir);

        folderContent.forEach(item => {
            // lstat doesn't follow symbolic links
            var stats = fs.lstatSync(dir + '/' + item);

            if(stats.isDirectory()){
                recursion(dir + '/' + item);
            }else if(stats.isFile()){
                if(typeof item !== 'undefined' 
                   && item === config.IIS_CONFIG_FILE){

                    matches.push(dir + '/' + item);
                    console.log(matches);
                }
            }
        });
    }(dir, matches);
    
    return matches;
};

akzeptierte Antworten

  1. Tach!

    ich habe ein Problem mit Variable Scope in Zusammenhang mit einer Rekursiven Funktion. Ich durchsuche rekursiv Unterverzeichnisse nach einer Datei und speichere bei einem Match den vollständigen Pfad in ein Array. Leider stehe ich ziemlich auf der Leitung, wenn ich das Ganze ohne globale Variable "matches" abbilden will.

    Eine Funktion sollte selbständig arbeiten, nur Parameter entgegennehmen und ein Ergebnis liefern. Wenn sie außerhalb ihres Scopes etwas speichert ist sie nebenläufig. Das will man gern vermeiden.

    Für die Rekursion kann man so vorgehen, dass am tiefsten Punkt die dortigen Ergebnisse zusammengetragen werden und als Funktionsergebnis zurückgeliefert werden. Als Parameter nimmt die Funktion in deinem Fall den Pfad entgegen. Eine Ebene weiter oben nimmt sie die Ergebnisse von den Unten-drunter-Aufrufen entgegen, fügt diese zusammen und fügt ihre eigenen Ergebnisse hinzu, und das ist das Rückgabeergebnis. Auch hier wieder den Pfad als Parameter übergeben. Die Sammelstelle ist dabei immer nur ein lokales Array (oder was auch immer). Und so setzt sich das bis nach oben fort. Natürlich ist der eigentliche Programmablauf genau umgekehrt als eben geschildert.

    • A funktioniert nicht, weil ich durch die Rekursion, durch den erneuten Funktionsaufruf jedesmal eine neue, leere Variable matches habe. Is klar.

    A funktioniert nicht, weil matches eine immer wieder neu angelegte lokale Variable ist und das Funktionsergebnis beim Auftauchen zwar zurückgegeben wird, aber dann ignoriert wird. Damit liefert nur der äußere Aufruf ein "flaches" Ergebnis.

    • B funktioniert nicht, weil die Variable/Funktion "recursion" noch nicht definiert ist, wenn ich sie inmitten ihrer selbst abermals aufrufe. Is auch klar.

    recursion ist keine Funktion sondern nimmt ein Funktionsergebnis auf. Die dortige Funktion wird nämlich gleich aufgerufen.

    Die Frage, die bleibt ist: Wie macht man das richtig und in schön ? :)

    Wie A, aber mit Beachtung der Rückgaben.

    dedlfix.

    1. Herzlichen Dank, dedlfix !

      Ich habe das jetzt korrigiert. Dabei habe ich beachtet, dass alle Parameter der Funktion übergeben werden. Bei meinem ersten Post war "needle" noch global und ich übergebe nun die variable "matches" an jede weitere Rekursion.

      Hmm ... vielleicht wäre es performanter ich würde matches = matches [] weglassen und einfach einfordern, dass drei Parameter übergeben werden, von denen einer eben ein leeres Array ist?

      Hier der funktionierende Code:

      // get all IIS config file (applicationhost.config) paths recursively
      function getIISConfigFilesPath(dir, needle, matches){
          var folderContent = fs.readdirSync(dir);
          matches = matches || [];
          
          
          folderContent.forEach(item => {
              var stats = fs.lstatSync(dir + '/' + item);
              
              if(stats.isDirectory()){
                  getIISConfigFilesPath(dir + '/' + item, needle, matches);
                  
              }else if(stats.isFile()){
                  if(typeof item !== 'undefined' 
                     && item === needle){
                      
                      matches.push(dir + '/' + item);
                  }
              }
          });
          
          return matches;
      };
      
       
      console.log(getIISConfigFilesPath('c:/DEIN/PFAD', 'ZU_SUCHENDE_DATEI.TXT'));
      
      1. Tach!

        Ich habe das jetzt korrigiert. Dabei habe ich beachtet, dass alle Parameter der Funktion übergeben werden. Bei meinem ersten Post war "needle" noch global und ich übergebe nun die variable "matches" an jede weitere Rekursion.

        Das würde ich nicht machen. matches ist das Ergebnis, das "unten rausfallen" sollten. So wie du es jetzt hast, arbeiten die inneren Ausrufe anders als der äußere. Das muss ja nicht uneinheitlich sein.

        Hmm ... vielleicht wäre es performanter ich würde matches = matches || [] weglassen und einfach einfordern, dass drei Parameter übergeben werden, von denen einer eben ein leeres Array ist?

        Mikrooptimierung. Wenn du viele Durchläufe brauchst, um überhaupt ein auswertbares Messergebnis zu bekommen, in der Praxis aber immer nur einer stattfindet, dann ist eine Optimierung in der Regel sinnlos.

        dedlfix.

      2. Hmm ... vielleicht wäre es performanter ich würde matches = matches [] weglassen und einfach einfordern, dass drei Parameter übergeben werden, von denen einer eben ein leeres Array ist?

        Es gibt sehr gute Profiling-Tools für JavaScript mit denen sich zielgerichtete Performance-Analysen betreiben lassen – mit spekulativen Optimierungen kommt man in der Regel nicht weit. Die große Herausforderung liegt darin zu überbrücken, dass sich die Modelle der JS-Semantik, die moderne Jit-Compiler haben, wesentlich von unseren intuitiven Vorstellungen unterscheiden.

        Und trotzdem spekuliere ich mal, dass dein Flaschenhals die vielen synchronen IO-Operationen mit der Festplatte sind und die asynchronen Pendants der fs-Funktionen dir einen massiven Boost geben könnten.

        Einen Schönheitsfehler hat matches = matches || [] als Pattern für Default-Parameter dennoch: Es funktioniert generell nicht mit Parametern, die falsy Werte annehmen können. Stattdessen hat JavaScript heute seine eigene Syntax dafür:

        function foo (bar = []) { /* ... */ }