Fehlerhafter JavaScript-Code: Die 10 häufigsten Fehler, die JavaScript-Entwickler machen
Veröffentlicht: 2022-03-11Heute ist JavaScript der Kern praktisch aller modernen Webanwendungen. Insbesondere in den letzten Jahren hat sich eine breite Palette leistungsstarker JavaScript-basierter Bibliotheken und Frameworks für die Entwicklung von Single-Page-Anwendungen (SPA), Grafiken und Animationen und sogar serverseitige JavaScript-Plattformen verbreitet. JavaScript ist in der Welt der Web-App-Entwicklung wirklich allgegenwärtig geworden und wird daher zu einer immer wichtigeren Fähigkeit, die es zu beherrschen gilt.
Auf den ersten Blick mag JavaScript ganz einfach erscheinen. Und in der Tat ist es für jeden erfahrenen Softwareentwickler eine ziemlich einfache Aufgabe, grundlegende JavaScript-Funktionalität in eine Webseite einzubauen, selbst wenn sie JavaScript noch nicht kennen. Dabei ist die Sprache deutlich nuancierter, kraftvoller und komplexer, als man zunächst vermuten würde. Tatsächlich führen viele der Feinheiten von JavaScript zu einer Reihe allgemeiner Probleme, die verhindern, dass es funktioniert – 10 davon besprechen wir hier –, die es wichtig sind, sich dessen bewusst zu sein und sie zu vermeiden, wenn man ein meisterhafter JavaScript-Entwickler werden möchte.
Häufiger Fehler Nr. 1: Falsche Verweise this
Ich hörte einmal einen Komiker sagen:
Ich bin nicht wirklich hier, denn was ist hier, außer dort, ohne das „t“?
Dieser Witz charakterisiert in vielerlei Hinsicht die Art von Verwirrung, die Entwickler oft in Bezug auf das Schlüsselwort this
von JavaScript haben. Ich meine, ist this
wirklich das oder ist es etwas ganz anderes? Oder ist es undefiniert?
Da JavaScript-Codierungstechniken und Entwurfsmuster im Laufe der Jahre immer ausgefeilter geworden sind, gab es eine entsprechende Zunahme der Verbreitung von selbstreferenzierenden Bereichen innerhalb von Rückrufen und Closures, die eine ziemlich häufige Quelle für „diese/jene Verwirrung“ sind.
Betrachten Sie dieses Beispiel-Code-Snippet:
Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); // what is "this"? }, 0); };
Das Ausführen des obigen Codes führt zu folgendem Fehler:
Uncaught TypeError: undefined is not a function
Warum?
Es dreht sich alles um den Kontext. Der Grund, warum Sie den obigen Fehler erhalten, liegt darin, dass Sie beim Aufrufen setTimeout()
tatsächlich window.setTimeout()
aufrufen. Als Ergebnis wird die anonyme Funktion, die an setTimeout()
wird, im Kontext des window
-Objekts definiert, das keine clearBoard()
Methode hat.
Eine herkömmliche, mit alten Browsern kompatible Lösung besteht darin, Ihren Verweis this
einfach in einer Variablen zu speichern, die dann von der Schließung geerbt werden kann. z.B:
Game.prototype.restart = function () { this.clearLocalStorage(); var self = this; // save reference to 'this', while it's still this! this.timer = setTimeout(function(){ self.clearBoard(); // oh OK, I do know who 'self' is! }, 0); };
Alternativ können Sie in neueren Browsern die Methode bind()
verwenden, um die richtige Referenz zu übergeben:
Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this' }; Game.prototype.reset = function(){ this.clearBoard(); // ahhh, back in the context of the right 'this'! };
Häufiger Fehler Nr. 2: Zu glauben, es gäbe einen Geltungsbereich auf Blockebene
Wie in unserem Leitfaden zur Einstellung von JavaScript besprochen, ist eine häufige Quelle der Verwirrung unter JavaScript-Entwicklern (und daher eine häufige Fehlerquelle) die Annahme, dass JavaScript für jeden Codeblock einen neuen Bereich erstellt. Dies gilt zwar für viele andere Sprachen, nicht jedoch für JavaScript. Betrachten Sie beispielsweise den folgenden Code:
for (var i = 0; i < 10; i++) { /* ... */ } console.log(i); // what will this output?
Wenn Sie vermuten, dass der Aufruf von console.log()
entweder undefined
ausgeben oder einen Fehler ausgeben würde, haben Sie falsch geraten. Ob Sie es glauben oder nicht, es wird 10
ausgegeben. Warum?
In den meisten anderen Sprachen würde der obige Code zu einem Fehler führen, da die „Lebensdauer“ (dh der Geltungsbereich) der Variablen i
auf den for
-Block beschränkt wäre. In JavaScript ist dies jedoch nicht der Fall, und die Variable i
bleibt auch nach Abschluss der for
-Schleife im Gültigkeitsbereich und behält ihren letzten Wert nach dem Verlassen der Schleife. (Dieses Verhalten wird übrigens als variables Heben bezeichnet).
Es ist jedoch erwähnenswert, dass die Unterstützung für Geltungsbereiche auf Blockebene durch das neue Schlüsselwort let
in JavaScript Einzug hält. Das Schlüsselwort let
ist bereits in JavaScript 1.7 verfügbar und soll ab ECMAScript 6 ein offiziell unterstütztes JavaScript-Schlüsselwort werden.
Neu bei JavaScript? Informieren Sie sich über Bereiche, Prototypen und mehr.
Häufiger Fehler Nr. 3: Erstellen von Speicherlecks
Speicherlecks sind fast unvermeidliche JavaScript-Probleme, wenn Sie nicht bewusst codieren, um sie zu vermeiden. Es gibt zahlreiche Möglichkeiten, wie sie auftreten können, daher werden wir nur einige ihrer häufigeren Vorkommen hervorheben.
Speicherleck Beispiel 1: Dangling-Verweise auf nicht mehr existierende Objekte
Betrachten Sie den folgenden Code:
var theThing = null; var replaceThing = function () { var priorThing = theThing; // hold on to the prior thing var unused = function () { // 'unused' is the only place where 'priorThing' is referenced, // but 'unused' never gets invoked if (priorThing) { console.log("hi"); } }; theThing = { longStr: new Array(1000000).join('*'), // create a 1MB object someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000); // invoke `replaceThing' once every second
Wenn Sie den obigen Code ausführen und die Speichernutzung überwachen, werden Sie feststellen, dass Sie ein massives Speicherleck haben, das ein volles Megabyte pro Sekunde verliert! Da hilft auch kein manueller GC. Es sieht also so aus, als würden wir bei jedem Aufruf von longStr
replaceThing
. Aber warum?
Lassen Sie uns die Dinge genauer untersuchen:
Jedes theThing
Objekt enthält sein eigenes longStr
Objekt. Jede Sekunde, wenn wir replaceThing
, hält es an einer Referenz auf das vorherige theThing
Objekt in priorThing
. Aber wir würden immer noch nicht denken, dass dies ein Problem wäre, da das zuvor referenzierte priorThing
jedes Mal dereferenziert würde (wenn priorThing
über priorThing = theThing;
zurückgesetzt wird). Und darüber hinaus wird nur im Hauptteil von replaceThing
und in der unused
Funktion referenziert, die tatsächlich nie verwendet wird.
Also fragen wir uns wieder, warum es hier ein Speicherleck gibt!?
Um zu verstehen, was vor sich geht, müssen wir besser verstehen, wie die Dinge in JavaScript unter der Haube funktionieren. Die typische Art und Weise, wie Closures implementiert werden, besteht darin, dass jedes Funktionsobjekt einen Link zu einem Dictionary-Style-Objekt hat, das seinen lexikalischen Geltungsbereich darstellt. Wenn beide Funktionen, die in replaceThing
definiert sind, tatsächlich priorThing
verwenden, wäre es wichtig, dass beide dasselbe Objekt erhalten, selbst wenn priorThing
immer wieder zugewiesen wird, sodass beide Funktionen dieselbe lexikalische Umgebung teilen. Aber sobald eine Variable von einer Closure verwendet wird, landet sie in der lexikalischen Umgebung, die von allen Closures in diesem Gültigkeitsbereich geteilt wird. Und diese kleine Nuance führt zu diesem knorrigen Speicherleck. (Weitere Einzelheiten dazu finden Sie hier.)
Speicherleck Beispiel 2: Zirkuläre Referenzen
Betrachten Sie dieses Codefragment:
function addClickHandler(element) { element.click = function onClick(e) { alert("Clicked the " + element.nodeName) } }
Hier hat onClick
eine Closure, die einen Verweis auf element
(über element.nodeName
) beibehält. Indem Sie auch onClick
zu element.click
, wird die Zirkelreferenz erstellt; dh: element
-> onClick
-> element
-> onClick
-> element
…
Interessanterweise würde der obige zirkuläre Selbstverweis, selbst wenn element
aus dem DOM entfernt wird, verhindern, dass element
und onClick
gesammelt werden, und somit ein Speicherleck.
Speicherlecks vermeiden: Was Sie wissen müssen
Die Speicherverwaltung (und insbesondere die Garbage Collection) von JavaScript basiert weitgehend auf dem Begriff der Objekterreichbarkeit.
Die folgenden Objekte gelten als erreichbar und werden als „Roots“ bezeichnet:
- Objekte, auf die von überall in der aktuellen Aufrufliste verwiesen wird (d. h. alle lokalen Variablen und Parameter in den Funktionen, die gerade aufgerufen werden, und alle Variablen im Abschlussbereich)
- Alle globalen Variablen
Objekte werden mindestens so lange im Speicher gehalten, wie sie von einem der Wurzeln durch eine Referenz oder eine Kette von Referenzen zugänglich sind.
Es gibt einen Garbage Collector (GC) im Browser, der den von nicht erreichbaren Objekten belegten Speicher bereinigt; dh Objekte werden nur dann aus dem Speicher entfernt , wenn der GC glaubt, dass sie nicht erreichbar sind. Leider ist es ziemlich einfach, nicht mehr existierende „Zombie“-Objekte zu erhalten, die tatsächlich nicht mehr verwendet werden, aber die der GC immer noch für „erreichbar“ hält.
Häufiger Fehler Nr. 4: Verwirrung über Gleichberechtigung
Einer der Vorteile von JavaScript ist, dass es jeden Wert, auf den in einem booleschen Kontext verwiesen wird, automatisch in einen booleschen Wert umwandelt. Aber es gibt Fälle, in denen dies ebenso verwirrend wie praktisch sein kann. Einige der folgenden Beispiele sind zum Beispiel dafür bekannt, viele JavaScript-Entwickler zu beißen:
// All of these evaluate to 'true'! console.log(false == '0'); console.log(null == undefined); console.log(" \t\r\n" == 0); console.log('' == 0); // And these do too! if ({}) // ... if ([]) // ...
In Bezug auf die letzten beiden sind, obwohl sie leer sind (was zu der Annahme führen könnte, dass sie als false
ausgewertet würden), sowohl {}
als auch []
tatsächlich Objekte, und jedes Objekt wird in JavaScript auf den booleschen Wert true
gezwungen. in Übereinstimmung mit der ECMA-262-Spezifikation.
Wie diese Beispiele demonstrieren, können die Regeln des Typus Zwang manchmal schlammklar sein. Dementsprechend ist es normalerweise am besten, ===
und !==
(statt ==
und !=
) zu verwenden, es sei denn, Typumwandlung ist ausdrücklich erwünscht, um unbeabsichtigte Nebeneffekte der Typumwandlung zu vermeiden. ( ==
und !=
führen beim Vergleich zweier Dinge automatisch eine Typkonvertierung durch, während ===
und !==
denselben Vergleich ohne Typkonvertierung durchführen.)
Und ganz nebenbei – aber da wir über Typenzwang und Vergleiche sprechen – ist es erwähnenswert, dass der Vergleich von NaN
mit irgendetwas (sogar NaN
!) immer false
zurückgibt. Sie können daher nicht die Gleichheitsoperatoren ( ==
, ===
, !=
, !==
) verwenden, um festzustellen, ob ein Wert NaN
ist oder nicht. Verwenden Sie stattdessen die integrierte globale Funktion isNaN()
:
console.log(NaN == NaN); // false console.log(NaN === NaN); // false console.log(isNaN(NaN)); // true
Häufiger Fehler Nr. 5: Ineffiziente DOM-Manipulation
JavaScript macht es relativ einfach, das DOM zu manipulieren (dh Elemente hinzuzufügen, zu ändern und zu entfernen), trägt aber nicht dazu bei, dies effizient zu tun.
Ein gängiges Beispiel ist Code, der eine Reihe von DOM-Elementen nacheinander hinzufügt. Das Hinzufügen eines DOM-Elements ist ein teurer Vorgang. Code, der mehrere DOM-Elemente nacheinander hinzufügt, ist ineffizient und funktioniert wahrscheinlich nicht gut.

Eine effektive Alternative, wenn mehrere DOM-Elemente hinzugefügt werden müssen, besteht darin, stattdessen Dokumentfragmente zu verwenden, wodurch sowohl die Effizienz als auch die Leistung verbessert werden.
Zum Beispiel:
var div = document.getElementsByTagName("my_div"); var fragment = document.createDocumentFragment(); for (var e = 0; e < elems.length; e++) { // elems previously set to list of elements fragment.appendChild(elems[e]); } div.appendChild(fragment.cloneNode(true));
Zusätzlich zu der inhärent verbesserten Effizienz dieses Ansatzes ist das Erstellen angefügter DOM-Elemente teuer, während das Erstellen und Ändern von DOM-Elementen im getrennten Zustand und das anschließende Anfügen eine viel bessere Leistung ergibt.
Häufiger Fehler Nr. 6: Falsche Verwendung von Funktionsdefinitionen innerhalb von for
-Schleifen
Betrachten Sie diesen Code:
var elements = document.getElementsByTagName('input'); var n = elements.length; // assume we have 10 elements for this example for (var i = 0; i < n; i++) { elements[i].onclick = function() { console.log("This is element #" + i); }; }
Basierend auf dem obigen Code, wenn es 10 Eingabeelemente gäbe, würde das Klicken auf eines von ihnen „Dies ist Element Nr. 10“ anzeigen! Dies liegt daran, dass zu dem Zeitpunkt, an dem onclick
für eines der Elemente aufgerufen wird, die obige for-Schleife abgeschlossen ist und der Wert von i
bereits 10 ist (für alle Elemente).
So können wir die obigen Codeprobleme jedoch beheben, um das gewünschte Verhalten zu erreichen:
var elements = document.getElementsByTagName('input'); var n = elements.length; // assume we have 10 elements for this example var makeHandler = function(num) { // outer function return function() { // inner function console.log("This is element #" + num); }; }; for (var i = 0; i < n; i++) { elements[i].onclick = makeHandler(i+1); }
In dieser überarbeiteten Version des Codes wird makeHandler
jedes Mal sofort ausgeführt, wenn wir die Schleife durchlaufen, wobei jedes Mal der dann aktuelle Wert von i+1
empfangen und an eine bereichsbezogene num
-Variable gebunden wird. Die äußere Funktion gibt die innere Funktion zurück (die auch diese bereichsbezogene num
-Variable verwendet), und der onclick
des Elements wird auf diese innere Funktion gesetzt. Dadurch wird sichergestellt, dass jeder onclick
den richtigen i
-Wert empfängt und verwendet (über die Scope-Variable num
).
Häufiger Fehler Nr. 7: Versäumnis, die prototypische Vererbung richtig zu nutzen
Ein überraschend hoher Prozentsatz von JavaScript-Entwicklern versteht die Funktionen der prototypischen Vererbung nicht vollständig und kann sie daher nicht vollständig nutzen.
Hier ist ein einfaches Beispiel. Betrachten Sie diesen Code:
BaseObject = function(name) { if(typeof name !== "undefined") { this.name = name; } else { this.name = 'default' } };
Scheint ziemlich einfach zu sein. Wenn Sie einen Namen angeben, verwenden Sie ihn, andernfalls setzen Sie den Namen auf „Standard“; z.B:
var firstObj = new BaseObject(); var secondObj = new BaseObject('unique'); console.log(firstObj.name); // -> Results in 'default' console.log(secondObj.name); // -> Results in 'unique'
Aber was wäre, wenn wir dies tun würden:
delete secondObj.name;
Wir würden dann bekommen:
console.log(secondObj.name); // -> Results in 'undefined'
Aber wäre es nicht schöner, wenn dies auf "Standard" zurückgesetzt würde? Dies kann leicht durchgeführt werden, wenn wir den ursprünglichen Code ändern, um die prototypische Vererbung wie folgt zu nutzen:
BaseObject = function (name) { if(typeof name !== "undefined") { this.name = name; } }; BaseObject.prototype.name = 'default';
Mit dieser Version erbt BaseObject
die Eigenschaft name
von seinem prototype
, wo sie (standardmäßig) auf 'default'
gesetzt ist. Wenn der Konstruktor also ohne Namen aufgerufen wird, ist der Name standardmäßig default
. Wenn die Eigenschaft name
aus einer Instanz von BaseObject
entfernt wird, wird die Prototypkette durchsucht und die Eigenschaft name
wird aus dem prototype
abgerufen, dessen Wert immer noch 'default'
ist. Also bekommen wir jetzt:
var thirdObj = new BaseObject('unique'); console.log(thirdObj.name); // -> Results in 'unique' delete thirdObj.name; console.log(thirdObj.name); // -> Results in 'default'
Häufiger Fehler Nr. 8: Erstellen falscher Verweise auf Instanzmethoden
Lassen Sie uns ein einfaches Objekt definieren und wie folgt eine Instanz davon erstellen:
var MyObject = function() {} MyObject.prototype.whoAmI = function() { console.log(this === window ? "window" : "MyObj"); }; var obj = new MyObject();
Lassen Sie uns nun der Einfachheit halber einen Verweis auf die whoAmI
Methode erstellen, vermutlich, damit wir nur über whoAmI()
und nicht über das längere obj.whoAmI()
darauf zugreifen können:
var whoAmI = obj.whoAmI;
Und nur um sicherzugehen, dass alles gut aussieht, drucken wir den Wert unserer neuen whoAmI
Variablen aus:
console.log(whoAmI);
Ausgänge:
function () { console.log(this === window ? "window" : "MyObj"); }
OK Cool. Sieht gut aus.
Aber schauen Sie sich jetzt den Unterschied an, wenn wir obj.whoAmI()
aufrufen, im Vergleich zu unserer praktischen Referenz whoAmI()
:
obj.whoAmI(); // outputs "MyObj" (as expected) whoAmI(); // outputs "window" (uh-oh!)
Was schief gelaufen ist?
Der Headfake hier ist, dass bei der Zuweisung var whoAmI = obj.whoAmI;
, die neue Variable whoAmI
wurde im globalen Namensraum definiert. this
ist sein Wert window
, nicht die obj
von MyObject
!
Wenn wir also wirklich einen Verweis auf eine vorhandene Methode eines Objekts erstellen müssen, müssen wir dies innerhalb des Namensraums dieses Objekts tun, um den Wert von this
zu erhalten. Eine Möglichkeit dazu wäre beispielsweise wie folgt:
var MyObject = function() {} MyObject.prototype.whoAmI = function() { console.log(this === window ? "window" : "MyObj"); }; var obj = new MyObject(); obj.w = obj.whoAmI; // still in the obj namespace obj.whoAmI(); // outputs "MyObj" (as expected) obj.w(); // outputs "MyObj" (as expected)
Häufiger Fehler Nr. 9: Bereitstellen einer Zeichenfolge als erstes Argument für setTimeout
oder setInterval
Lassen Sie uns für den Anfang etwas klarstellen: Das Bereitstellen eines Strings als erstes Argument für setTimeout
oder setInterval
ist per se kein Fehler. Es ist vollkommen legitimer JavaScript-Code. Hier geht es eher um Leistung und Effizienz. Was selten erklärt wird, ist, dass unter der Haube, wenn Sie einen String als erstes Argument an setTimeout
oder setInterval
, dieser an den Funktionskonstruktor übergeben wird, um in eine neue Funktion konvertiert zu werden. Dieser Prozess kann langsam und ineffizient sein und ist selten notwendig.
Die Alternative zur Übergabe eines Strings als erstes Argument an diese Methoden besteht darin, stattdessen eine Funktion zu übergeben. Schauen wir uns ein Beispiel an.
Hier wäre also eine ziemlich typische Verwendung von setInterval
und setTimeout
, wobei eine Zeichenfolge als erster Parameter übergeben wird:
setInterval("logTime()", 1000); setTimeout("logMessage('" + msgValue + "')", 1000);
Die bessere Wahl wäre, eine Funktion als Anfangsargument zu übergeben; z.B:
setInterval(logTime, 1000); // passing the logTime function to setInterval setTimeout(function() { // passing an anonymous function to setTimeout logMessage(msgValue); // (msgValue is still accessible in this scope) }, 1000);
Häufiger Fehler Nr. 10: Nichtbenutzung des „strikten Modus“
Wie in unserem JavaScript Hiring Guide erklärt, ist der „strenge Modus“ (dh einschließlich 'use strict';
am Anfang Ihrer JavaScript-Quelldateien) eine Möglichkeit, freiwillig eine strengere Analyse und Fehlerbehandlung für Ihren JavaScript-Code auch zur Laufzeit zu erzwingen als es sicherer zu machen.
Zwar ist die Nichtverwendung des strikten Modus per se kein „Fehler“, aber seine Verwendung wird zunehmend gefördert und seine Unterlassung wird zunehmend als schlechte Form angesehen.
Hier sind einige der wichtigsten Vorteile des strikten Modus:
- Erleichtert das Debuggen. Codefehler, die andernfalls ignoriert worden wären oder stillschweigend fehlgeschlagen wären, werden jetzt Fehler generieren oder Ausnahmen auslösen, wodurch Sie früher auf Probleme in Ihrem Code aufmerksam gemacht und Sie schneller zu ihrer Quelle geleitet werden.
- Verhindert versehentliche Globals. Ohne den strikten Modus wird beim Zuweisen eines Werts zu einer nicht deklarierten Variablen automatisch eine globale Variable mit diesem Namen erstellt. Dies ist einer der häufigsten Fehler in JavaScript. Im Strict-Modus wird beim Versuch, dies zu tun, ein Fehler ausgegeben.
- Beseitigt
this
Zwang . Ohne den strikten Modus wird ein Verweis auf einenthis
-Wert von null oder undefiniert automatisch auf den globalen erzwungen. Dies kann zu vielen Headfakes und Pull-out-your-hair-Fehlern führen. Im strikten Modus löst das Verweisen auf aathis
Wert von null oder undefiniert einen Fehler aus. - Verhindert doppelte Eigenschaftsnamen oder Parameterwerte. Der strikte Modus löst einen Fehler aus, wenn er eine doppelt benannte Eigenschaft in einem Objekt (z. B.
var object = {foo: "bar", foo: "baz"};
) oder ein doppelt benanntes Argument für eine Funktion (z. B.function foo(val1, val2, val1){}
), wodurch Sie mit ziemlicher Sicherheit einen Fehler in Ihrem Code abfangen, für dessen Suche Sie andernfalls viel Zeit verschwendet hätten. - Macht eval() sicherer. Es gibt einige Unterschiede im Verhalten von
eval()
im Strict-Modus und im Non-Strict-Modus. Am wichtigsten ist, dass im strikten Modus Variablen und Funktionen, die innerhalb einereval()
Anweisung deklariert sind, nicht im enthaltenden Gültigkeitsbereich erstellt werden (sie werden im enthaltenden Gültigkeitsbereich im nicht strikten Modus erstellt, was ebenfalls eine häufige Quelle von Problemen sein kann). - Löst einen Fehler bei ungültiger Verwendung von
delete
aus. Derdelete
-Operator (der zum Entfernen von Eigenschaften von Objekten verwendet wird) kann nicht auf nicht konfigurierbare Eigenschaften des Objekts angewendet werden. Nicht strikter Code schlägt stillschweigend fehl, wenn versucht wird, eine nicht konfigurierbare Eigenschaft zu löschen, während der strikte Modus in einem solchen Fall einen Fehler auslöst.
Einpacken
Wie bei jeder Technologie gilt: Je besser Sie verstehen, warum und wie JavaScript funktioniert und wie nicht, desto solider wird Ihr Code und desto mehr können Sie die wahre Kraft der Sprache effektiv nutzen. Umgekehrt liegt in der Tat ein Mangel an richtigem Verständnis von JavaScript-Paradigmen und -Konzepten, wo viele JavaScript-Probleme liegen.
Sich gründlich mit den Nuancen und Feinheiten der Sprache vertraut zu machen, ist die effektivste Strategie, um Ihre Sprachkenntnisse zu verbessern und Ihre Produktivität zu steigern. Das Vermeiden vieler häufiger JavaScript-Fehler hilft, wenn Ihr JavaScript nicht funktioniert.