Lieber dedlfix,
vielen Dank für Deine vielen wertvollen Denkanstöße!
Ich würde es gern sehen, wenn die Instantiierungen anonymer Funktionen durch die Variante 2, also die IIFE-Schreibweise ausgetauscht würden.
sagte nicht Crockford, er möge es nicht, wenn die Argumente-Klammer außerhalb der umspannenden Klammern läge? So á la "da hängen die Eier 'raus"?
(function (param) { ... } (value));
// anstatt
(function (param) { ... }) (value);
Aber das ist meines Erachtens ziemlich egal. Gerade für Anfänger ist es das. Inwiefern ich damit Anfängern den Einstieg in die Instanziierung von Objekten mit dem Schlüsselwort new
verbaue... da bin ich als Pädagoge eher gelassen, denn meine Erfahrung zeigt, dass man bereits Bekanntes durchaus in neue Kontexte einzuordnen lernt.
Ich hab da mal eben einen Erläuterungsartikel dazu geschrieben, siehe obigen Link.
Und schon wieder hat das Wiki inhaltlich gewonnen. Danke dafür!
Das MDN sagt, dass querySelectorAll() eine non-live NodeList liefert und keine lebendige. Dieses Detail ist aber für den Artikel nicht weiter relevant, weil die Anzahl der Spiele auf der Seite sich nicht im laufenden Betrieb ändern wird.
Ungeachtet dessen muss ich das dann korrigieren. Leider bleibt es eine NodeList und wird kein Array, sodass die schönen Iterationsmethoden wie forEach
leider nicht zur Verfügung stehen.
Es ist sicher auch wenig sinnvoll, mehr als ein Spiel auf die Seite zu bringen, solange man eh nur zwei Personen abwechselnd agieren lässt.
Um aber die Unabhängigkeit der Objekte bzw. den Scope von Funktionen und Variablen zu zeigen, wäre es durchaus sinnvoll - immerhin ist es ein Tutorial, bei dem Anfänger grundsätzlich etwas lernen sollen, nicht nur zielgerichtet für das eine Ergebnis.
Jedenfalls könnte man dem Spiel auch eine ID statt einer Klasse vergeben. Es sei denn, du möchtest nur einen Grund haben, warum das eigentliche Spiel in einer Funktion (TicTacToe) ausgelagert ist. Aber das könnte man auch mit guter Strukturierung und Bilden von Zuständigkeitseinheiten begründen.
Wenn mehr als ein Spiel auf der Seite möglich sein soll, dann braucht es eine Klasse anstelle einer ID. Und das Fass "ID versus Klasse" wollte ich nicht aufmachen. Das soll gerne in einem anderen Tutorial besprochen werden. Warum soll man z.B. nicht verschiedene Spielstände zum Weiterspielen anbieten können? Das geht nur mit einer Klasse anstelle einer ID!
Statt for mit der Zählvariable i fände ich ja forEach() ausdrucksstärker, vor allem, weil man dann nicht
games[i]
sonderngame
zur Repräsentation eines einzelnen Spiels hat. Dummerweise liefert querySelectorAll() kein Array sondern eine NodeList, und die hat keine Array-Methoden. Was aber geht, wäre for...of, was aber auf Microsoft-Seite erst im Edge-Browser verfügbar ist. Jedenfalls kommt man damit an die Ausdrucksstärke von forEach heran.
Das mit Iterationsmethoden und Callback-Funktionen empfinde ich als ein kleines bisschen fortgeschrittener, als es ein reines Anfänger-Tutorial im Kern verkraftet. Bei der "Was fehlt noch?"-Rubrik könnte man solche Gedanken anbringen. Oder man schreibt gleich ein ganz anders konstruiertes Beispiel und macht ein Anfänger-II-Tutorial daraus. Magst Du (<I>) ...?
Es gibt einen entscheidenden Unterschied zwischen
var foo = function() {...};
und
function foo() {...};
Ja. Den habe ich bewusst vorenthalten. Mir war im Kontext des Tutorials zunächst wichtig anzudeuten (nicht: genau darzulegen!), dass sich ein Funktionsbezeichner wie eine gewöhnliche Variable verhält. Wann die Funktion definiert wird, und wann ihr Code ausführbar zur Verfügung steht, ist selbstverständlich bei den Schreibweisen höchst unterschiedlich.
Variablen-/Parameternamen bitte ausschreiben. Bei i als allgemein übliche Laufvariable ist das noch kein Problem (besser: forEach/for...of verwenden, wenn möglich), aber el und e sind gerade für Anfänger, die nicht wissen, was die Funktionen für Parameter erwarten, erklärungsbedürftig. Also lieber element und event nehmen. Das gilt im Prinzip auch für Code, der nicht nur Anfängern gewidmet ist. Für absichtlich unleserlichen Code gibt es Uglifier ;)
Guter Einwand! Das werde ich verbessern. Auch an den Beispieldateien.
In check() ist es besser, statt
!finished
nur auffinished
zu prüfen und in dem Fall die Funktion zu verlassen. Das reduziert eine Einrückungsebene, besonders weil es keinen Else-Zweig oder generellen Code nach diesem if kommt.
Hmm. Ja. Im Prinzip schon. Aber dann müsste ich return
einführen. Das habe ich zwar implizit schon getan, indem ich Rückgabewerte von Methoden in Variablen ablege, wie ich aber mitten aus einer Funktion aussteige, egal ob mit oder ohne Rückgabewert, wollte ich nicht ohne konkreten Anlass einführen.
"Es hat einen Sinn, die Variablendeklaration an den Anfang der Funktion zu setzen und nicht erst im Anweisungsblock des if-Statements. Das ist gute Programmierertradition, da damit der Code für andere übersichtlicher wird." - Njein, kann man so sehen, kann man aber auch anders sehen.
Stimmt, kann man. Aber wenn man die Sache mit dem Scope üben will, dann ist es einleuchtend, wenn man die Deklaration an oberster Stelle des Scopes vornimmt. Findest Du nicht?
Besser sind kleinere lokale übersichtliche Scopes, statt dass man im Großen versucht den globalen Scope zu meiden, im kleinen aber Variablen im gesamten Scope rumliegen hat. Im Falle von
i
ist es sogar unübersichtlicher, weili
nur ganz lokal benötigt wird und nicht in der gesamten Funktion.
Bedeutet das, dass Du gerne noch mehr Funktionen verschachteln möchtest? Hier in diesem konkreten Anfänger-Tutorial? Oder war das eher eine allgemeine Marschrichtung für erfahrenere JavaScriptler?
Dafür hat man sich auch in der kommenden JavaScript-Version das Schlüsselwort let ausgedacht, das den Scope von Variablen nochmal deutlich einschränken kann. Ansonsten muss man sagen: Kommt drauf an. Manchmal benötigt man eine Variable wirklich im gesamten Scope und manchmal eben nicht.
Aha! Dann wäre das offensichtlich etwas für forgeschrittenere Tutorials. Aber gut, dass Du das angesprochen hast, denn diese neuesten ECMA-Script-Standards habe ich noch nicht so auf dem Schirm. Insbesondere let
kenne ich aus BASIC auf dem C64...
Zur Ermittlung ob full oder nicht, kann man nach dem ersten false die Schleife abbrechen, "fuller" wird es nämlich mit den anderen Werten auch nicht.
Tja, dann müsste ich break
einführen. Wollte aber sparsam darin sein, was ich alles auf einen echten Anfänger los lasse.
Noch besser wäre es, every() zu nehmen, was im vorliegenden Ansatz wieder daran krankt, dass eine HTMLCollection kein Array ist.
Eben. Warum nur um Himmels Willen hat man das so gemacht?? Diese ganzen NodeLists haben diese bequemen Iterationsmethoden nicht. Was haben die sich dabei nur gedacht? Ist es das live, dass eine Iterationsmethode scheitern ließe? Das will mir nicht einleuchten.
"Wenn wir prüfen wollen, ob der String leer ist, genügt es nicht mit dem ==-Operator zu prüfen, da JavaScript intern Wertetypen umrechnen kann, um verschiedene Typen miteinander zu vergleichen." - Oh doch, das genügt in dem Fall vollkommen, weil beim Vergleich von zwei Strings (hier className mit einem Stringliteral) kein anderer Wertetyp beteiligt ist. Anders sähe der Fall aus, wenn ein Integer oder Boolean beteiligt wäre - ist aber nicht.
Es könnte eine Klasse "0" oder "000000" benutzt werden - extrem unwahrscheinlich, aber irgendwie hielt ich es für nötig, die Typsicherheit bei Vergleichen von beiden Seiten aus vorzuführen. Es sollte einmal ein typsicherer Vergleich nötig sein und einmal ein lose typisierter. Hättest Du bessere Beispiele gewusst, um das Problem zu veranschaulichen? Ich habe hier eher pädagogisch denn maximal effizient programmierend entschieden.
className ist definiert als string, und selbst wenn man da 0 (Integer-Literal) reinschreibt, kommt "0" (String) beim Lesen raus.
Genau, und "0" ist falsy, ebenso wie "00000".
Ist das nicht etwas inkonsequent? Einerseits die automatische Typkonvertierung mithilfe von
===
vermeiden (abgesehen von obigem Sinn-Argument für den dortigen Fall) und andererseits darauf zu bauen?
Es war eine pädagogische Entscheidung, um die Problematik der Typ(un)sicherheit in JavaScript schon einem Anfänger zu vermitteln.
Abgesehen von dieser Stichelei, pfeif ich persönlich auf solche Konsistenzen aus niederen Beweggründen.
Hehe, und ich mache sie mir pädagogisch zunutze.
Ich brauche die Erfahrungswerte, die ich bekomme, wenn ich in solche potentiellen Fehlerquellen reintappe.
Eben.
Gut, dass wir darüber diskutiert haben.
Die Verwendung eines
//<![CDATA[...//]]>
-Blocks ist laut Standard nicht notwendig. Vielleicht gab/gibt es einige Situationen, für die die Browser das brauchen, aber da ist mir auch noch keine über den Weg gelaufen. In HTML5 ist das Regelwerk von script gehörig aufgebohrt worden (und ich hab keine Lust das alles zu lesen), aber dass nun darin Markup interpretiert wird, kann ich mir nicht vorstellen. Der CDATA-Rahmen kann also weg. Undtype="text/javascript"
ist der Standard-Wert und kann damit auch weggelassen werden.
Das ist alles schön und gut, wenn kein String in der Form "</script>" enthalten ist. Bastele ich einen CDATA-Block darum, habe ich kein Problem. Ohne diesen Block könnte mir das um die Ohren fliegen (und ist es in der Vergangenheit auch). Daher mache ich das immer dran. Es stört nicht und fördert ungefragt und unbenötigt die XHTML-Kompatibilität.
Wenn man es "richtig" macht, stochert man lieber nicht in der Ausgabe herum, um Werte für die Geschäftslogik zu bekommen. Wenn man an der Ausgabe Änderungen vornehmen möchte (zum Beispiel statt Tabelle was anderes), muss man durch die Geschäftslogik durchlaufen und dort alle Element- und/oder Klassennamen ändern. Besser ist, man hat eine separate Datenhaltung.
Darüber hatte ich mir im Vorfeld einige Gedanken gemacht. Ich hatte ein Array mit den 3x3 Feldern, wobei jedes Array-Element, welches für ein Feld stand, ein Objekt war, welches unter anderem das betroffene Elementobjekt enthielt. Etwas in dieser Richtung:
var board = [
[
{
element: <HTMLTableCellElement>,
value: "x"
},
{
element: <HTMLTableCellElement>,
value: ""
},
{
element: <HTMLTableCellElement>,
value: "o"
}
], // weitere Arrays für Zeilen 2 & 3
];
Ich habe dann festgestellt, dass das Prüfen und Ermitteln von Gewinner oder Unentschieden für einen Anfänger unnötig kompliziert wird. Deshalb habe ich mich für eine Anfänger-Lösung mit der className
-Eigenschaft entschieden.
Liebe Grüße,
Felix Riesterer.