Hallo Auge
Wenn es darum geht herauszufinden, ob ein bestimmter Wert in einem Array enthalten ist, dann gäbe es zu diesem Zweck auch in JavaScript eine eingebaute Methode, nämlich
Array.prototype.includes
.Das war mir nicht bekannt. Ich suchte halt das Pendant zur PHP-Funktion und fand das obige, seinen Zweck erfüllende Beispiel.
Allerdings fehlt in deiner übersetzten Variante der PHP-Funktion namens in_array
ein auch in JavaScript nicht unwesentliches Detail, nämlich die Möglichkeit, durch Übergabe eines Arguments für den optionalen dritten Parameter statt auf lenient equality auf strict equality zu testen. ;-)
Aber auch ohne die Verwendung von Array.prototype.includes
, das ja tatsächlich noch nicht so lange Teil des (living) Standards ist, hätten dir einige etablierte Methoden für Arrayinstanzen zur Verfügung gestanden um das Ziel zu erreichen, wie zum Beispiel Array.prototype.some
.
const array = ['tick', 'trick', 'track'];
// arrow function
console.info(array.some(value => value == 'trick')); // true
// ordinary function expression
const result = array.some(function (value) {
return value == 'donald';
});
console.info(result); // false
Diese Arraymethode erwartet als erstes Argument eine Rückruffunktion, die für jedes Element des Arrays aufgerufen wird, solange die Funktion keinen truthy value zurückgibt.
Gibt die Funktion bei einem Aufruf einen solchen Wert zurück, wird die Ausführung der Methode beendet und es wird true
zurückgegeben. Gibt die Funktion hingegen für alle Elemente des Arrays einen falsy value zurück, dann ist der Rückgabewert der Methode entsprechend false
. Damit ließe sich der Test ob ein Wert in dem Array enthalten ist also auch durchführen.
Ebenfalls beliebt ist die Variante mit Array.prototype.indexOf
. Diese Methode prüft mit dem strict equality Vergleichsalgorithmus, ob der als erstes Argument an die Methode übergebene Wert in dem Array enthalten ist und gibt dann den Index der ersten Fundstelle zurück, oder, wenn kein passender Eintrag gefunden wurde, den Wert -1
.
const array = ['garfield', 'odie'];
if (array.indexOf('garfield')) {
console.log(true); //
}
console.log(array.indexOf('garfield') != -1); // true
Dabei muss aber natürlich berücksichtigt werden, dass der gesuchte Eintrag wie in dem Beispiel oben auch den Index 0
haben kann, bei dem es sich um einen falsy value handelt. Statt explizit auf -1
zu prüfen, kann hier aber auch der bitweise NOT Operator verwendet werden (Tilde), der wenn es sich bei dem Operanden um den Wert -1
handelt, 0
zurückgibt.
if (!~array.indexOf('garfield')) {
console.log(true); // true
}
Da du in deinem Beispielcode das Ergebnis der Prüfung auch gleich verwenden willst, wenn denn der Wert in dem Array gefunden wurde, könnte man hier auch Array.prototype.find
verwenden. Die Methode funktioniert ähnlich wie some
, jedoch wird hier bei einem positiven Rückgabewert der als Argument übergebenen Funktion von der Methode nicht true
zurückgegeben, sondern der Wert selbst.
const array = ['asterix', 'obelix'], name = 'asterix';
// arrow function
console.log(array.find(value => value == name)); // asterix
// ordinary function expression
const result = array.find(function (value) {
return value == 'idefix';
});
console.log(result || ''); //
Wird von der Rückruffunktion bei keinem Aufruf ein truthy value zurückgegeben, dann ist undefined
der Rückgabewert der Methode. Allerdings finde ich die Variante mit includes
hier nach wie vor am besten, auch wenn sie noch nicht flächendeckend implementiert ist. ;-)
Sieht grundsätzlich übersichtlicher aus, auch wenn ich das Konzept anonymer Funktionen immer noch etwas verwirrend finde.
Hmm. Wenn du etwas genauer beschrieben hättest, was genau du daran verwirrend findest, könnte ich dir bei der Entwirrung vielleicht besser helfen. So kann ich nur ein paar allgemeine Erklärungen zum Thema anbieten …
Fangen wir also damit an, dass du zunächst einmal zwei Möglichkeiten hast, wie du eine Funktion definieren kannst, nämlich entweder indem du die Funktion deklarierst (Function Declaration), oder aber indem du die Funktion als einen Ausdruck notierst, der wenn er ausgewertet wurde als Ergebnis eine Funktion zurückgibt (Function Expression).
// function declaration
function tintin ( ) {
return 'et milou';
}
Betrachten wir also erstmal Funktionsdeklarationen, die so wie in dem Beispiel oben notiert werden. Hier ist klar, dass eine Notierung ohne Bezeichner nicht besonders sinnvoll wäre, da die Funktion dann nicht referenziert werden könnte. Entsprechend produziert eine anonyme Funktionsdeklaration, von einer hier nicht relevanten Ausnahme abgesehen (Export Default Declaration), immer einen Syntaxfehler.
Jedenfalls wird bei einer deklarierten Funktion der angegebene Funktionsbezeichner, hier also tintin, automatisch zur lexikalischen Umgebung des Ausführungskontextes hinzugefügt, in welchem die Funktion deklariert wurde, sodass sie über ihren Bezeichner referenziert werden kann.
Aber das ist nicht alles, denn deklarierte Funktionen werden darüber hinaus gehoistet, sprich sie werden so behandelt, als wären sie am Anfang des jeweiligen Gültigkeitsbereichs notiert worden, noch vor allen Anweisungen die da gegebenenfalls noch folgen. Darum können sie im Code aufgerufen werden bevor sie deklariert wurden.
console.log(milou( )); // et tintin
function milou ( ) {
return 'et tintin';
}
Wie gesehen ist das aber nicht die einzige Möglichkeit eine Funktion zu definieren, sondern die Definition kann auch innerhalb eines Ausdrucks erfolgen. Die Syntax für einen Funktionsausdruck ist dabei nicht anders als die einer Funktionsdeklaration, das heißt, die Unterscheidung kann immer nur mit Blick auf die Umgebung getroffen werden, in der die Funktion definiert wurde.
// named function expression
var homer = function simpson ( ) {
return 'jay';
};
In diesem Beispiel haben wir eine Variablendeklaration, bei der die Variable mit einem benannten Funktionsausdruck initialisiert wird. Im Vergleich zu einer Funktionsdeklaration gibt es hier nun zwei wesentliche Unterschiede. So wird der angegebene Bezeichner der Funktion nicht der lexikalischen Umgebung des laufenden Ausführungskontextes hinzugefügt, sondern er kann nur innerhalb des Ausdrucks, also innerhalb des Körpers der Funktion angesprochen werden. Das heißt, die Funktion kann hier nur über die Variable referenziert werden der sie zugewiesen wurde.
var barney = function gumble ( ) {
if (typeof gumble == 'function') {
console.log('duff');
}
};
barney( ); // duff
gumble( ); // Reference Error - gumble is not defined
Darüber hinaus wird die Funktion auch nicht gehoistet, sie kann also erst referenziert werden, nachdem die Variable initialisiert wurde. Genauer gesagt: Zwar wird die mit var
deklarierte Variable hier ebenfalls bei der Initialisierung des Ausführungskontextes der lexikalischen Umgebung hinzugefügt, aber sie bleibt solange undefined
bis ihr ein Wert zugwiesen wird, hier also das Ergebnis des Funktionsausdrucks.
Da es sich bei Funktionen in JavaScript um Bürger erster Klasse handelt, kann ein Funktionsausdruck überall notiert werden wo ein Ausdruck notiert werden darf, also zum Beispiel auch bei der Übergabe von Argumenten an eine andere Funktion. Oder auch als Rückgabewert einer Funktion.
window.addEventListener('DOMContentLoaded', function init ( ) {
console.log(typeof init); // function
});
function produce ( ) {
return function product ( ) {
// do something
};
}
console.log(typeof produce( )); // function
Da nun der Bezeichner einer als Ausdruck notierten Funktion grundsätzlich nicht außerhalb der Funktion selbst sichtbar ist, kann er auch weggelassen werden. Darin liegt auch kein Nachteil, denn selbst wenn es beispielsweise für Debuggingzwecke unter Umständen sinnvoll sein kann einen Bezeichner anzugeben, ist die Referenzierbarkeit über einen eigenen Bezeichner für die Erfüllung einer Aufgabe meist keine Notwendigkeit.
// property
const object = {
lucky : function ( ) {
return 'eddie';
}
};
console.log(object.lucky( )); // eddie
const array = [1, 2, 3];
// argument
array.forEach(function (value) {
console.log(value + 2); // 3, 4, 5
});
Das heißt, oft trägt eine deklarierte Funktion lediglich zur Bevölkerung der lexikalischen Umgebung bei, obwohl sie nur an einer einzigen Stelle aufgerufen wird. Wobei zu berücksichtigen ist, dass eine deklarierte Funktion anders als ein Funktionsausdruck nicht unmittelbar aufgerufen werden darf.
// lexical environment binds handler
function handler (event) {
console.log(event.target.tagName);
}
document.body.addEventListener('click', handler);
// no binding here
document.body.addEventListener('click', function handler (event) {
console.log(event.target.tagName);
});
Wird also wie hier im ersten Beispiel bei der Argumentübergabe an addEventListener
nur eine Referenz auf eine zuvor deklarierte Funktion übergeben, dann sollte innerhalb der lexikalischen Umgebung dieses Ausführungskontextes besser keine andere Funktion oder Variable mit diesem Bezeichner deklariert werden.
Wird hingegen wie im zweiten Beispiel ein Ausdruck verwendet, dann kann es hier zu keinen Konflikten kommen. Und da die Funktion darüber hinaus ohnehin darauf angelegt ist, automatisch beim Ereigniseintritt aufgerufen zu werden und weil dafür kein Bezeichner nötig ist, könnte man die Funktion hier auch als anonymen Ausdruck notieren.
window.addEventListener('DOMContentLoaded', function ( ) {
// do something
});
Für die Lesbarkeit beziehungsweise Verständlichkeit des Codes ist das nicht zwingend ein Nachteil denke ich, eher im Gegenteil, denn auf diese Weise ist eine Zusammenhängende Anweisung nicht über mehrere Stellen innerhalb des Quelltextes verstreut.
Noch einen Schritt weiter in diese Richtung gehen übrigens Pfeilfunktionen (Arrow Functions), wie ich sie in einigen der Beispiele weiter oben bereits verwendet habe. Denn diese Funktionen sind grundsätzlich anonym und sie können entsprechend nur als Ausdruck notiert werden.
const log = value => console.log(value);
log('message'); // message
const math = {
add : (a, b) => a + b
};
log(math.add(4, 4)); // 8
Neben anderen Vorteilen, wie etwa einer lexikalischen Bindung der Kontextvariable this
, trägt gerade die kompakte Syntax ohne das Schlüsselwort function
und ohne Funktionsbezeichner dazu bei, dass Implementierungen mittels Pfeilfunktionen in der Regel deutlich eleganter sind als vergleichbare Konstrukte mit gewöhnlichen Funktionen.
const array = ['foo', 'bar', 'baz'];
// anonymous function expression
array.forEach(function (value) {
console.log(value);
});
// function declaration
function print (value) {
console.log(value);
}
array.forEach(print);
// arrow function
array.forEach(value => console.log(value));
Hinsichtlich des Debuggings ist Anonymität auch nicht unbedingt ein Problem, da Funktionsobjekte in der Regel über eine Eigenschaft mit dem Bezeichner name verfügen, für die bei unbenannten Funktionen implizit der Name der Variable oder der Eigenschaft hinterlegt wird, welcher sie zugewiesen wurden, und die von vielen Entwicklungsumgebungen examiniert wird.
Naja, wie auch immer. Ich hoffe anonyme Funktionen verwirren dich nun etwas weniger. ;-)
Viele Grüße,
Orlok