034.3 Lektion 2
Zertifikat: |
Web Development Essentials |
---|---|
Version: |
1.0 |
Thema: |
034 JavaScript-Programmierung |
Lernziel: |
034.3 JavaScript-Kontrollstrukturen und -Funktionen |
Lektion: |
2 von 2 |
Einführung
Ergänzend zu den Standardfunktionen, die JavaScript bereitstellt, können Sie eigene, benutzerdefinierte Funktionen schreiben, um eine Eingabe auf eine Ausgabe abzubilden, die den Anforderungen der Anwendung entspricht. Benutzerdefinierte Funktionen sind im Grunde eine Reihe gekapselter Anweisungen, die irgendwo als Teil eines Ausdrucks verwendet werden können.
Funktionen sind ein guter Weg, doppelten Code zu vermeiden, da sie an verschiedenen Stellen im Programm aufgerufen werden können. Außerdem erleichtert die Gruppierung von Anweisungen in Funktionen die Bindung benutzerdefinierter Aktionen an Ereignisse, was ein zentraler Aspekt der JavaScript-Programmierung ist.
Eine Funktion definieren
Je größer ein Programm, desto schwieriger wird es, seine Aufgaben ohne Funktionen zu organisieren. Jede Funktion hat ihren eigenen privaten Variablenbereich, so dass die in einer Funktion definierten Variablen nur innerhalb dieser Funktion verfügbar sind. So werden sie nicht mit Variablen aus anderen Funktionen verwechselt. Globale Variablen sind zwar immer noch innerhalb der Funktionen zugänglich, aber der bevorzugte Weg, um Eingabewerte an eine Funktion zu senden, sind Funktionsparameter. Als Beispiel werden wir den Primzahl-Prüfer aus der vorangegangenen Lektion ausbauen:
// A naive prime number tester
// The number we want to evaluate
let candidate = 231;
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Display the result in the console window
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
Um später im Code zu prüfen, ob eine Zahl eine Primzahl ist, müssten Sie den bereits geschriebenen Code wiederholen. Diese Praxis ist allerdings nicht empfehlenswert, da etwaige Korrekturen oder Verbesserungen des ursprünglichen Codes überall dort wiederholt werden müssten, wo der Code kopiert wurde. Außerdem belastet die Wiederholung des Codes den Browser und das Netzwerk und verlangsamt möglicherweise die Anzeige der Webseite. Verschieben Sie stattdessen die entsprechenden Anweisungen in eine Funktion:
// A naive prime number tester function
function test_prime(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
Die Funktionsdeklaration beginnt mit der Anweisung function
, gefolgt von dem Namen der Funktion und ihren Parametern. Der Name der Funktion folgt denselben Regeln wie die Namen von Variablen. Die Parameter der Funktion, auch Argumente genannt, werden durch Kommata getrennt und in Klammern gesetzt.
Tip
|
Die Auflistung der Argumente in der Funktionsdeklaration ist nicht zwingend erforderlich. Die Argumente, die einer Funktion übergeben werden, können aus einem Array-ähnlichen |
Im Beispiel hat die Funktion test_prime
nur ein Argument (candidate
), das dem zu testenden Primzahlkandidaten entspricht. Funktionsargumente funktionieren wie Variablen, aber ihre Werte werden von der Anweisung, die die Funktion aufruft, zugewiesen. Zum Beispiel ruft die Anweisung test_prime(231)
die Funktion test_prime
auf und weist dem Argument candidate
den Wert 231
zu, der dann innerhalb des Funktionskörpers wie eine gewöhnliche Variable verfügbar ist.
Wenn die aufrufende Anweisung einfache Variablen für die Funktionsparameter verwendet, werden deren Werte in die Funktionsargumente kopiert. Dieser Vorgang — das Kopieren der von der aufrufenden Anweisung genutzten Parameterwerte in die innerhalb der Funktion verwendeten Parameter — wird als Wertübergabe von Argumenten bezeichnet. Alle Änderungen, die die Funktion am Argument vornimmt, wirken sich nicht auf die ursprüngliche Variable aus, die in der aufrufenden Anweisung verwendet wird. Verwendet die aufrufende Anweisung jedoch komplexe Objekte als Argumente (d.h. ein Objekt mit Eigenschaften und Methoden) für die Parameter der Funktion, so werden diese als Referenz übergeben, und die Funktion kann das ursprüngliche Objekt, das in der aufrufenden Anweisung verwendet wurde, verändern.
Die Argumente, die als Wert übergeben werden, sowie die Variablen, die innerhalb der Funktion deklariert werden, sind außerhalb der Funktion nicht sichtbar. Das heißt, ihr Geltungsbereich ist auf den Funktionskörper beschränkt, in dem sie deklariert wurden. Dennoch werden Funktionen in der Regel verwendet, um eine Ausgabe zu erzeugen, die außerhalb der Funktion sichtbar ist. Um einen Wert mit der aufrufenden Funktion zu teilen, definiert eine Funktion eine return
-Anweisung.
Die Funktion test_prime
im Code-Beispiel gibt beispielsweise den Wert der Variablen is_prime
zurück. Daher kann die Funktion die Variable überall dort ersetzen, wo sie im ursprünglichen Beispiel verwendet würde:
// The number we want to evaluate
let candidate = 231;
// Display the result in the console window
if ( test_prime(candidate) )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
Die return
-Anweisung gibt, wie ihr Name schon sagt, die Kontrolle an die aufrufende Funktion zurück. Daher wird an der Stelle, an der die return
-Anweisung in der Funktion platziert ist, nichts ausgeführt, was ihr folgt. Eine Funktion kann mehrere return
-Anweisungen enthalten. Diese Praxis kann nützlich sein, wenn einige innerhalb von bedingten Anweisungsblöcken stehen, so dass die Funktion bei jedem Durchlauf eine bestimmte return
-Anweisung ausführen kann oder nicht.
Einige Funktionen geben keinen Wert zurück, so dass die Anweisung return
nicht erforderlich ist. Die internen Anweisungen der Funktion werden unabhängig von ihrem Vorhandensein ausgeführt, so dass Funktionen auch dazu dienen können, die Werte globaler Variablen oder den Inhalt von Objekten zu ändern, die als Referenz übergeben werden. Hat die Funktion keine return
-Anweisung, wird ihr Standard-Rückgabewert auf undefined
gesetzt: eine reservierte Variable, die keinen Wert hat und nicht geschrieben werden kann.
Funktionsausdrücke
In JavaScript sind Funktionen nur eine weitere Art von Objekten. Daher können Funktionen in einem Skript wie Variablen verwendet werden. Diese Eigenschaft wird explizit, wenn die Funktion mit einer alternativen Syntax deklariert wird, den Funktionsausdrücken:
let test_prime = function(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
Der einzige Unterschied zwischen diesem Beispiel und der Funktionsdeklaration im vorherigen Beispiel besteht in der ersten Zeile: let test_prime = function(candidate)
statt function test_prime(candidate)
. In einem Funktionsausdruck wird der Name test_prime
für das Objekt verwendet, das die Funktion enthält, und nicht, um die Funktion selbst zu benennen. Funktionen, die in Funktionsausdrücken definiert sind, werden auf die gleiche Weise aufgerufen wie Funktionen, die mit der Deklarationssyntax definiert wurden. Während deklarierte Funktionen jedoch vor oder nach ihrer Deklaration aufgerufen werden können, können Funktionsausdrücke nur nach ihrer Initialisierung aufgerufen werden. Wie bei Variablen führt der Aufruf einer in einem Ausdruck definierten Funktion vor ihrer Initialisierung zu einem Referenzfehler.
Funktionsrekursion
Neben der Ausführung von Anweisungen und dem Aufruf von eingebauten Funktionen können benutzerdefinierte Funktionen auch andere benutzerdefinierte Funktionen aufrufen, einschließlich sich selbst. Der Aufruf einer Funktion aus sich selbst wird als Funktionsrekursion bezeichnet. Je nach Art des Problems, das Sie zu lösen versuchen, können rekursive Funktionen einfacher sein als verschachtelte Schleifen zur Ausführung sich wiederholender Aufgaben.
Bisher wissen wir, wie man mit einer Funktion prüft, ob eine bestimmte Zahl eine Primzahl ist. Nehmen wir nun an, Sie wollen die nächste Primzahl nach einer gegebenen Zahl finden. Sie könnten eine while
-Schleife nutzen, um die Kandidatenzahl zu erhöhen, und eine verschachtelte Schleife schreiben, die nach ganzzahligen Faktoren für diesen Kandidaten sucht:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "true" keeps the loop going until a prime is found
while ( true )
{
// Auxiliary control variable
let is_prime = true;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Test the next candidate
is_prime = false;
break;
}
}
// End loop and return candidate if it is prime
if ( is_prime )
{
return candidate;
}
// If prime not found yet, try the next odd number
candidate = candidate + 2;
}
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Sie sehen, dass wir eine konstante Bedingung für die while
-Schleife (den Ausdruck true
in der Klammer) und die Hilfsvariable is_prime
verwenden müssen, um zu wissen, wann die Schleife beendet werden soll. Obwohl diese Lösung korrekt ist, sind verschachtelte Schleifen nicht so elegant wie eine Rekursion, um die gleiche Aufgabe zu erfüllen:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Call the next_prime function recursively, this time
// using the failed candidate as the argument.
return next_prime(candidate);
}
}
// "candidate" is not divisible by any integer factor other
// than 1 and itself, therefore it is a prime number.
return candidate;
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Beide Versionen von next_prime
geben die nächste Primzahl nach der als einziges Argument übergebenen Zahl zurück (from
). Die rekursive Version beginnt, wie die vorherige Version, mit der Überprüfung der Spezialfälle (d.h. Zahlen kleiner oder gleich zwei). Dann wird der Kandidat inkrementiert und mit der for
-Schleife nach ganzzahligen Faktoren gesucht (beachten Sie, dass es keine while
-Schleife mehr gibt). Zu diesem Zeitpunkt wurde die einzige gerade Primzahl bereits getestet, also werden der Kandidat und seine möglichen Faktoren um zwei erhöht. (Eine ungerade Zahl plus zwei ist die nächste ungerade Zahl.)
Es gibt nur zwei Auswege aus der for
-Schleife im Beispiel: Wenn alle möglichen Faktoren getestet werden und keiner bei der Division des Kandidaten einen Rest gleich Null hat, wird die for
-Schleife beendet und die Funktion gibt den Kandidaten als nächste Primzahl nach from
zurück. Andernfalls, wenn factor
ein ganzzahliger Faktor von candidate
ist (candidate % factor == 0
), stammt der zurückgegebene Wert von der Funktion next_prime
, die rekursiv aufgerufen wird, dieses Mal mit dem inkrementierten candidate
als from
-Parameter. Die Aufrufe von next_prime
werden übereinander gestapelt, bis ein Kandidat schließlich keine ganzzahligen Faktoren mehr aufweist. Dann wird die letzte next_prime
-Instanz, die die Primzahl enthält, diese an die vorherige next_prime
-Instanz zurückgeben, und somit sukzessive bis hinunter zur ersten next_prime
-Instanz. Obwohl jeder Aufruf der Funktion dieselben Variablennamen verwendet, sind die Aufrufe voneinander isoliert, so dass ihre Variablen im Speicher getrennt gehalten werden.
Geführte Übungen
-
Welchen Overhead verringern Sie durch die Verwendung von Funktionen?
-
Was ist der Unterschied zwischen Funktionsargumenten, die als Wert übergeben werden, und Funktionsargumenten, die als Referenz übergeben werden?
-
Welcher Wert wird als Ausgabe einer benutzerdefinierten Funktion verwendet, wenn sie keine Rückgabeanweisung hat?
Offene Übungen
-
Was ist die wahrscheinliche Ursache für einen Uncaught Reference Error, der beim Aufruf einer mit Ausdruck-Syntax deklarierten Funktion auftritt?
-
Schreiben Sie eine Funktion mit dem Namen
multiples_of
, die drei Argumente entgegennimmt:factor
,from
undto
. Verwenden Sie innerhalb der Funktion die Anweisungconsole.log()
, um alle Vielfachen vonfactor
zwischenfrom
undto
auszugeben.
Zusammenfassung
In dieser Lektion erfahren Sie, wie Sie benutzerdefinierte Funktionen in JavaScript-Code schreiben. Mit Hilfe von benutzerdefinierten Funktionen können Sie die Anwendung in “Stücke” wiederverwendbaren Codes unterteilen, was das Schreiben und Warten größerer Programme erleichtert. In dieser Lektion werden die folgenden Konzepte und Verfahren behandelt:
-
Wie man eine benutzerdefinierte Funktion definiert: Funktionsdeklarationen und Funktionsausdrücke.
-
Verwendung von Parametern als Funktionsinput.
-
Verwendung der
return
-Anweisung, um die Funktionsausgabe festzulegen. -
Funktionsrekursion.
Lösungen zu den geführten Übungen
-
Welchen Overhead verringern Sie durch die Verwendung von Funktionen?
Funktionen ermöglichen die Wiederverwendung von Code, was die Wartung des Codes erleichtert. Eine kleinere Skriptdatei spart außerdem Speicherplatz und Downloadzeit.
-
Was ist der Unterschied zwischen Funktionsargumenten, die als Wert übergeben werden, und Funktionsargumenten, die als Referenz übergeben werden?
Bei der Übergabe als Wert wird das Argument in die Funktion kopiert und die Funktion kann die ursprüngliche Variable in der aufrufenden Anweisung nicht ändern. Bei der Übergabe per Referenz kann die Funktion die ursprüngliche Variable in der aufrufenden Anweisung verändern.
-
Welcher Wert wird als Ausgabe einer benutzerdefinierten Funktion verwendet, wenn sie keine Rückgabeanweisung hat?
Der zurückgegebene Wert wird auf
undefined
gesetzt.
Lösungen zu den offenen Übungen
-
Was ist die wahrscheinliche Ursache für einen Uncaught Reference Error, der beim Aufruf einer mit Ausdruck-Syntax deklarierten Funktion auftritt?
Die Funktion wurde vor ihrer Deklaration in der Skriptdatei aufgerufen.
-
Schreiben Sie eine Funktion mit dem Namen
multiples_of
, die drei Argumente entgegennimmt:factor
,from
undto
. Verwenden Sie innerhalb der Funktion die Anweisungconsole.log()
, um alle Vielfachen vonfactor
zwischenfrom
undto
auszugeben.function multiples_of(factor, from, to) { for ( let number = from; number <= to; number++ ) { if ( number % factor == 0 ) { console.log(factor, "*", number / factor, "=", number); } } }