Closure (good parts) Was ist das?

Weiter führender Hinweis: Tennet Correspondence Principle (test of lambda abstraction)
Blocks (aber nicht für return, break, continue, this, arguments, var, function): {…} ≡ (function(){…})();
Expressions (aber nicht für this, arguments: (…) ≡ (function(){return …;})()
Closure (allgemein) Funktion in Funktion ...

Eine innere Funktion, die in einer äußeren Funktion erzeugt wird, hat einen "unsichtbaren Zugriff" auf die inneren Variablen der äußeren Funktion. Die innere Funktionen kann die Variablen der äußeren Funktionen (ähnlich wie globale Variablen) nutzen. Dies wird als Closure bezeichnet und ist die Quelle einer enormen Ausdruckskraft.

Der funktion-interne Geltungsbereich von Variablen bewirkt, daß innere Funktionen Zugriff auf die Werte von Variablen in der umschließenden, äußere Funktion (außer this, arguments) haben.

Eine Closure entspricht der Benutzung von (freie, pseudo globalen) Variablen in einer Funktion, die aus übergeordneten Gültigkeitsbereichen kommen und statisch bzw. lexikalisch in der zurück gegebenen Funktion gespeichert werden. Infolge der (mit-) gespeicherten Gültigkeitsbereiche kann die Funktion auf diese freie Variablen zugreifen.
Code Snippet: Funktionaübergabe als Parameter (Closure)
  1. var x = 10;
  2. function func() { window.alert(x); }
  3. (function (funArg) {
  4.   var x = 20; funArg(); // 10, nicht 20
  5. } (func));
Beispiele (einführend zu Closure) zum besseren Verständnis

Zur Einführung in Closure werden einfache Beispiele betrachtet. Es gibt einen (festen) Array names, der Namen enthält. Der Zugriff kann mit Hilfe der Array-Indizierung erfolgen. Z.B. zeigt alert(names[2]) den Namen 'Christian' an.

  1. var names = ['Axel','Bernd','Christian','Dora'];

Die folgende Funktion get_name(n) gibt den n-ten Namen zurück. Die Funktion kann den Array-Zugriff kontrollieren. Nachteilig ist, daß der names-Array global ist.

  1. var names = ['Axel','Bernd','Christian','Dora'];
  2.  
  3. var get_name = function (n) {
  4.     if(n >= 0 && n < names.length) {
  5.       return names[n];
  6.     } else {
  7.       return '';
  8.     }
  9. };


Die folgende Funktion get_name(n) verwendet einen inneren Array names. Nachteilig ist, daß die Ausführung des Zugriffes recht langsam ist, weil bei jedem Funktionsaufruf der Array neu angelegt wird.

  1. var get_name = function (n) {
  2.     var names = ['Axel','Bernd','Christian','Dora'];
  3.     if(n >= 0 && n < names.length) {
  4.       return names[n];
  5.     } else {
  6.       return '';
  7.     }
  8. };


Die nachfolgende Methode verwendet Closure. Beim ersten Aufruf der "Wrapper-Funktion" wird der innere Array names angelegt und der Rückgabewert der namenlosen inneren Funktion wird zu get_name(n). Der Array names existiert dann privat.

  1. var get_name = (function () {
  2.     var names = ['Axel','Bernd','Christian','Dora'];
  3.     return function (n) {
  4.         if(n >= 0 && n < names.length) {
  5.           return names[n];
  6.         } else {
  7.           return '';
  8.         }
  9.     };
  10. }());

Beispiel (private Variable) gutes Programmiermuster

obj gibt wegen function(){... return {...};}(); ein Objekt {...} zurück, das 2 Methoden enthält, die das Recht haben, auf die private Variable val zuzugreifen. obj kann etwa verwendet werden wie obj.erhoehe(1.23) und obj.get_val()

Code Snippet: Closure (private Variable)
  1. var obj = (function() {
  2.   var val = 0;
  3.   return {
  4.     erhoehe: function (inc) {
  5.       val += typeof inc === 'number' ? inc : 1;
  6.     },
  7.     get_val: function () {
  8.              return val;
  9.     }
  10.   }; // ende von return braucht ;
  11. }()); // () erzeugt Objekt mit .erhoehe(), .get_val()
  12.  
  13. obj.erhoehe(1.34);
  14. obj.erhoehe();
  15.  
  16. document.write("document.write(obj.get_val()) ergibt "+ obj.get_val() );

Teste href="javascript:alert(obj.get_val())" und auch onclick="alert(obj.get_val());return false;"

Beispiel (Parameter als private Variable) Parameter als private Variable

Die folgende Funktion soll ohne new-Präfix verwendet werden. Ein Aufruf von quo liefert ein Objekt zurück, das eine Funktion .get_status() enthält.

In var myQuo = quo('Hallo Welt'); entsprich myQuo der Referenz auf das zurückgegeben Objekt. Die .get_status()-Methode darf auf den privaten Bereich (hier der Parameter) von quo zugreifen, obwohl quo bereits zurückgekehrt ist. Die .get_status()-Methode besitztkeinen Zugriff auf eine Kopie des Parameters, sondern auf den Parameter selbst. Dies wird als Closure bezeichnet.

Code Snippet: Closure (Parameter als private Variable)
  1. var quo = function(status) {
  2.   return {
  3.     get_status: function () {
  4.       return status;
  5.     }
  6.   }; // ende von return braucht ;
  7. }; // erzeugt Objekt mit get_status()
  8.  
  9. var myQuo = quo('test1');

Teste myQuo.get_status()

Beispiel (title-text in confirm-box) zusätzlich onclick-Event einfügen

Alle a-Tags mit titel-Attribut sollen den title-Text in einer confirm-Box ausgeben. "confirm" meint bestätigen. Eine confirm-Box hat einen Anzeigetext und zwei Bottons (OK und Cancel).

a-Tags erhalten im titel-Attribut oft Erklärungen und zusätzliche Hinweise. blinde Menschen, die einen Screen-Reader verwenden, können das eingeblendete title-Tooltip (hover, mouse-over) nicht erkennen. Um den title-Hinweis lesen zu können, muß der Screenreader umgeschalten werden.

Wie kann ECMAScript hier helfen?

Die zu entwickelnde Funktion title2confirm() soll alle Elementen a[i] vom Array var a = document.getElementsByTagName('a') der HTML-Seite durchlaufen und den title-text a[i].title jedes a[i]-Elementes als confirm-text in einen onclick-Handler einfügen.

Ein onclick-Ereignis wird vor der href-Ausführung ausgeführt.
Nach dem onclick-Ereignis wird 
     href nur dann ausgeführt,
          wenn der onclick-Handler true zurück gibt.

Code Snippet für title2confirm():
  1. function title2confirm(on) {
  2.   var win = window, doc = win.document,
  3.   do_it = function (that, title) {
  4.            return function () {return win.confirm(title);};
  5.   }, i, el, htm, title, onclick,   
  6.   a = doc.getElementsByTagName('a'), len = a.length;
  7.  
  8.   for (i = 0; i < len; i += 1) { if (!a[i]) { continue; }
  9.     el = a[i];
  10.     htm = el.innerHTML;
  11.     title = el.title;
  12.     onclick = el.onclick;
  13.     if (/toggle_confirm/.test(onclick)) { continue; }
  14.     if (on === 'off' && /confirm\(/.test(onclick)) {
  15.       a[i].innerHTML = htm.replace(/^\(c\) /, '');
  16.       a[i].onclick = null;
  17.     } else if (title && !onclick) {
  18.       a[i].innerHTML = '(c) ' + htm;
  19.       a[i].onclick = do_it(el, title);
  20.     }
  21.   }
  22. }

Eine weitere Funktion toggle_confirm(that) soll es ermöglichen, per a-Tag

<a href="javascript:void(0)" onclick="visu.toggle_confirm(this);">[+] confirm</a>

die "title-in-confirm-kopierung" an und aus zu schalten ([+] bzw. [-]).

Code Snippet für title2confirm-on/off-Umschaltung
  1. function toggle_confirm(that) {
  2. var htm = that.innerHTML;
  3. if (that && /\[\+\]/.test(htm)) {
  4.   that.innerHTML = htm.replace(/\[\+\]/, '[-]');
  5.   title2confirm('on');
  6. } else {
  7.   that.innerHTML = htm.replace(/\[\-\]/, '[+]');
  8.   title2confirm('off');
  9. }
  10. }

Zum Umschalten von confirm 'on' auf confirm 'off' und umgekehrt, können zwei a-Tags, eines mit title2confirm('on') und das andere mit title2confirm('off') verwendet werden, etwa

<a href="javascript:void(0)" onclick="title2confirm('on')" > Schalte auf on  </a>,
<a href="javascript:void(0)" onclick="title2confirm('off')"> Schalte auf off </a>

Bildschirm-Platz-sparender ist die Umschaltung mit lediglich einem a-Tag. Hierzu kann title2confirm(arg) verwendet werden, indem anstelle eines Strings 'on' oder 'off' das this des aufrufenden a-Tags verwendet wird. Achtung! href liefert nicht das this vom a-Tag. Deshalb wird onclick verwendet.

<a href="javascript:void(0)" onclick="title2confirm(this);">[+] confirm</a>
"[+] confirm" schaltet auf "[-] confirm" und umgekehrt.
"[+] confirm" schaltet die confirm-Anzeige-Aktionen ein. 
     Alle a-Tags mit confirm haben dann ein pre-Zeichen "(c) ..."
     und zeigen damit an, daß erst eine confirm-Abfrage bestätigt werden muß.
"[-] confirm" schaltet die confirm-Aktionen ab. Alle pre-Zeichen "(c) ..." werden gelöscht.

teste: Schaltet confirm ein bzw. aus mit [+] toggle_confirm(this)
teste: Wirkungen von 'on' bzw. 'off':

teste a-Tag mit href alert('href0') und title="title0"
teste a-Tag mit href alert(this === window) und title-text

teste: Nicht-Auswirkungen auf fehlendes title- und vorhandenes onclick-Attribut bei 'on' bzw. 'off':

teste a-Tag mit href alert('href1') und ohne title-Attribut
teste a-Tag mit href alert('href2') und
title="title bleibt" und
onclick="alert('Was liefert this === window bei onclick? ..."

Beispiel (Closure, entityify() und deentityify()) .method verständlicher als .prototype

Die eingebaute .prototype-Methode ist umständlich zu schreiben und nicht intuitiv verständlich. Deshalb kann durchgängig die (nachfolgend angegebene) .method-Methode von Crockford verwendet werden.

Nachfolgend werden mit der .method-Methode die Funktionen deentityify und entityify definiert, die dann wie eingebaute ECMAScript-String-Methoden verwendet werden, wie z.B. var ss = "<&'>".entityify();

Code Snippet
  1. Function.prototype.method = function(name, func) {
  2.     if (!this.prototype[name]) {
  3.         this.prototype[name] = func;
  4.     }
  5. };
  6. String.method('deentityify', function () {
  7.  m var entity = { amp:'&', quot:'"', lt: '<', gt: '>' };
  8.   return function () {
  9.     return this.replace(/&([^&;]+);/g,
  10.       function (a,b) {
  11.         var r = entity[b];
  12.         return typeof r === 'string' ? r : a;
  13.       });
  14.     };
  15.   }()
  16. );
  17. String.method('entityify', function () {
  18.   return this.replace(/&/g,'&amp;')
  19.              .replace(/</g,'&lt;')
  20.              .replace(/"/g,'&quot;')
  21.              .replace(/>/g,'&gt;');
  22. });
  23.  
  24. var html = '<div class="c"> &auml; meint ä <\/div>',
  25. s1 = html.entityify(),
  26. s2 = s1.entityify(),
  27. s3 = s2.deentityify(),
  28. str = "s1 = " + s1 +
  29.        "\ns2 = " + s2 +
  30.        "\ns3 = " + s3;
  31. document.getElementById("deentityify").innerHTML = str;

Anzeige:






Beispiel (Hintergrundfarbe, einführend) Setze zeitgesteuert Grün

Die Funktion fade_simple = function(node, ms) soll den Grünanteil 'gg'der Hintergrundfarbe zeitgesteuert (Closure) setzen: gg = '00', '11', '22', '33', '44', '55', '66', '77', '88', '99', 'aa', 'bb', 'cc', 'dd', 'ee', 'ff'.

Code Snippet: Hintergrundfarbe (einführend)
  1. var fade_simple = function(node, ms) {
  2.    var i=0, gg, step; ms = ms || 100;
  3.  
  4.    step = function() {
  5.       gg = i.toString(16) + i.toString(16);
  6.       node.style.backgroundColor = "#ff" + gg + "ff";
  7.        if(i < 15) {
  8.         i += 1;
  9.         setTimeout(step,ms);
  10.       }
  11.    }; setTimeout(step, ms);
  12. };
  13.  
  14. // window.onload = function() { fade_simple(document.body); }

Hier ist die Ausführung der Funktion fade_simple(document.getElementById('FADE_SIMPLE')): Test

Beispiel (Hintergrundfarbe, Farbverlauf) Setze zeitgesteuert Hintergrundfarben

Dies ist eine Erweiterung des vorherigen Beispieles, wobei die inneren Hilfsfunktionen r_g_b_from(col) und function hex_from(dez) verwendet werden.

Code Snippet: Hintergrundfarbe (Farbverlauf)
  1. var fade = function(node, col1, col2, anz, ms)
  2. {
  3.   var i=0, o1,o2, r,g,b, dr,dg,db, h, col, step;
  4.   
  5.   function r_g_b_from(col) { // z.B. col="#ffff8a";
  6.     if(col.length !== 7){ return {r:255,g:255,b:0}; }
  7.     r = parseInt(col.substr(1,2),16);
  8.     g = parseInt(col.substr(3,2),16);
  9.     b = parseInt(col.substr(5,2),16); return {'r':r,'g':g,'b':b};
  10.   }
  11.  
  12.   function hex_from(dez) {
  13.     dez = Math.min(255,dez); h = Math.round(dez).toString(16);
  14.     if(h.length < 2) {return "0" + h;} return h;
  15.   }
  16.   // default
  17.   col1 = col1 || '#660000'; anz = anz || 80;
  18.   col2 = col2 || '#ffffff'; ms = ms || 50;
  19.   
  20.   o1 = r_g_b_from(col1); o2 = r_g_b_from(col2);
  21.   dr = (o2.r - o1.r) / anz;
  22.   dg = (o2.g - o1.g) / anz;
  23.   db = (o2.b - o1.b) / anz;
  24.   
  25.   step = function() {
  26.     col="#" + hex_from(o1.r) + hex_from(o1.g) + hex_from(o1.b);
  27.     node.style.backgroundColor = col;
  28.     o1.r += dr; o1.g += dg; o1.b += db;
  29.     if(i < anz) { i += 1; setTimeout(step,ms); }
  30.   }; setTimeout(step, ms);
  31. };
  32.  
  33. //fade(document.body);

Hier ist die Ausführung der Funktion fade(document.body): Test

Beispiel (onclick-Handler, Closure) Was bedeutet Closure?

Mit der folgenden Funktion add_handlers_to sollen DOM-Elementen eines Array's jeweils einige CSS-Attribute und ein onclick-Ereignis hinzu gefügt werden.

Wichtig ist, dass die Handlerfunktion nicht an die Variable i, sondern an den Wert von i beim Click-Ereignis gebunden wird.

Lösung: Es wird eine Funktion = function (i) {...}(i) definiert, die sofort mit i aufgerufen wird. Dadurch wird eine Eventhandler-Funktion zurück gegeben, die an den übergebenen Wert von i gebunden ist (und nicht an add_handlers_to). Diese zurück gegebene Funktion wird dann onclick zugewiesen.

Code Snippet: onclick-Handler (Closure)
  1. var add_handlers_to = function (nodes) { var i;
  2.   for(i = 0; i < nodes.length; i += 1) {
  3.     nodes[i].style.cursor  = 'pointer';
  4.     nodes[i].style.padding = '3px';
  5.     nodes[i].style.border  = '3px outset #ccc';
  6.     
  7.     nodes[i].onclick = function (i) {
  8.       return function (e) {
  9.         var d = document.documentElement, b = document.body;
  10.         if (!e) { e = window.event;} //IE
  11.         // Maus-Position
  12.         if ( e.pageX == null && e.clientX != null ) {
  13.          e.pageX = e.clientX + (d && d.scrollLeft || b && b.scrollLeft || 0)
  14.                              - (d && d.clientLeft || b && b.clientLeft || 0);
  15.          e.pageY = e.clientY + (d && d.scrollTop  || b && b.scrollTop  || 0)
  16.                              - (d && d.clientTop  || b && b.clientTop  || 0);
  17.         }
  18.         alert(e.type+' auf id='+this.id+' mit (x,y)=('+e.pageX+','+e.pageY+')');
  19.       };  // ende  return function (e)
  20.     }(i); // ende  nodes[i].onclick
  21.   }
  22. };
  23. add_handlers_to([
  24. document.getElementById('id0'),
  25. document.getElementById('id1'),
  26. document.getElementById('id2')
  27. ]);

Zum Testen bitte auf die id's klicken. id0  id1  id2