Skip to content

Files

Latest commit

 

History

History
468 lines (324 loc) · 15.3 KB

File metadata and controls

468 lines (324 loc) · 15.3 KB

Funktionen (Functions)

Des öfteren kommt es vor, dass wir eine (ähnliche) Aktion an mehreren Stellen im script ausführen möchten.

Wenn sich der Benutzer einloggt, ausloggt oder sonstige Aktinen ausübt, möchten wir Ihm eine Nachricht anzeigen.

Funktionen kann man auch als die "Bausteine" des Programms verstehen. Sie erlauben es uns geschriebenen Code häufig abzurufen ohne diesen erneut schreiben zu müssen.

Wir haben bereits eingebaute (built-in) Funktionen (functions) wie alert(message), prompt(message, default) und confirm(question) gesehen. Es ist uns aber auch möglich selbst Funktionen zu kreieren.

Funktionsdeklarierung (Function Declaration)

Funktionen können wir m. H. von Funktionsdeklarierungen (function declaration) erstellen.

Das sieht wie folgt aus:

function showMessage() {
  alert( 'Hello everyone!' );
}

Das keyword (Schlüsselwort) function steht an erster Stelle, darauf folgt der Name der Funktion. Dann noch eine Liste an Parameter zwischen den Klammern (mit einem "," voneinander getrennt; im obigen Beispiel leer) und letztlich der Code der Funktion, der auch "Körper der Funktion" (the function body) genannt wird, der zwischen den geschweiften Klammern (curly braces) steht.

function name(parameters) {
  ...body...
}

Unsere neue Funktion kann mit ihrem Namen abgerufen werden: showMessage().

Zum Beispiel:

function showMessage() {
  alert( 'Hallo an alle!' );
}
*!*
showMessage();
*/!*

Der Abruf (call) showMessage() führt den Code der Funktion aus. Darum sehen wir die Nachricht hier zwei Mal.

Dieses Beispeil zeigt perfekt was der hauptsächliche Zweck von Funktionen ist: sie verhindern die Duplikation von Code.

Falls wir jemals die Nachricht ändern müssten, oder die Art auf die sie angezeigt wird, genügt es den Code einmalig umzuschreiben: der innerhalb der Funktion der ihn ausführt.

Lokale (local) Variablen

Eine Variable, die innerhalb einer Funktion deklariert wird, ist ausschließlich innerhalb dieser sichtbar.

Ein Beispiel:

function showMessage() {
*!*
  let message = "Hallo, ich bin JavaScript!"; // Lokale (*local*) Variable
*/!*
  alert( message );
}
showMessage(); // Hallo, ich bin JavaScript!
alert( message ); // <-- Fehler! Die Variable ist nicht aufrufbar weil sie lokal ist

Außenstehende (outer) Variablen

Eine Funktion kann auch auf außenstehende Variablen zugreifen, wie folgendes Beispiel zeigt:

let *!*userName*/!* = 'John';
function showMessage() {
  let message = 'Hallo, ' + *!*userName*/!*;
  alert(message);
}
showMessage(); // Hallo, John

Die Funktion hat vollständigen Zugriff auf außenstehende Variablen. Und kann diese auch modifizieren.

Hier zu sehen:

let *!*userName*/!* = 'John';

function showMessage() {
  *!*userName*/!* = "Bob"; // (1) Ändert die außenstehende (*outer*) Variable

  let message = 'Hallo, ' + *!*userName*/!*;
  alert(message);
}

alert( userName ); // *!*John*/!* Vor dem Funktionsabruf

showMessage();

alert( userName ); // *!*Bob*/!*, Der Wert wurde von der Funktion modifiziert

Die außenstehende Variable wird nur genutzt, wenn es keine lokale gibt.

Falls innerhalb der Funktion eine gleichnamige Variable deklariert wird, wird die außenstehende von dieser überschattet (shadowed). Im folgenden Beispiel greift die Funktion auf die lokale Variable userName zu. Die außenstehende wird schlicht ignoriert:

let userName = 'John';

function showMessage() {
*!*
  let userName = "Bob"; // deklariert eine lokale Variable
*/!*

  let message = 'Hallo, ' + userName; // *!*Bob*/!*
  alert(message);
}

// die Funktion wird ihre eigene Variable erstellen und nutzen
showMessage();

alert( userName ); // *!*John*/!*, bleibt unverändert, die Funktion greift nicht auf die außenstehende Variable zu
Variablen, die außerhalb jeglicher Funktionen deklariert werden, wie das äußere `userName` im oben stehenden Code, nennt man *global*.

Auf globale Variablen kann von jeder Funktion aus zugegriffen werden (sofern diese nicht von Lokalen "überschattet" werden).

Es hat sich als gute und moderne Praxis entwickelt, so wenig globale Variablen wie möglich zu verwenden. Moderner Code hat wenige bis keine dieser globalen Variablen. Die meisten Variablen verbleiben innerhalb ihrer Funktionen. Manchmal sind Globale Variabeln aber doch praktisch, wenn man z.B. Daten des Projektstands speichern möchte.

Parameter

Wir können Funktionen jegliche Art von Daten zukommen lassen (auch Funktionsargumente [function arguments] genannt).

Im folgenden Beispiel hat die Funktion zwei Parameter: from und text.

function showMessage(*!*from, text*/!*) { // Argumente: from, text
  alert(from + ': ' + text);
}

*!*
showMessage('Ann', 'Hallo!'); // Ann: Hallo! (*)
showMessage('Ann', "Wie geht's?"); // Ann: Wie geht's? (**)
*/!*

Wenn die Funktion in den Zeilen (*) und (**)abgerufen wird, werden die gegebenen Werte in die lokalen Variablen from und text kopiert, woraufhin die Funktion diese nutzt.

Hier ist ein weiteres Beispiel: wir haben eine Variable from und spielen diese der Funktion zu. Beachte, dass die Funktion from ändert, jedoch diese Änderung außen nicht sichtbar wird, weil die Funktion stets eine Kopie des Werts annimmt:

function showMessage(from, text) {

*!*
  from = '*' + from + '*'; // hübscht "from" auf
*/!*

  alert( from + ': ' + text );
}

let from = "Ann";

showMessage(from, "Hallo"); // *Ann*: Hallo

// der Wert von "from" bleibt der selbe, die Funktion hat nur eine lokale Kopie modifiziert
alert( from ); // Ann

Default-Werte

Wenn ein Parameter nicht gegeben ist, wird dessen Wert zu undefined.

Bspw. kann die zuvor genannte Funktion showMessage(from, text) mit Hilfe eines einzelen Arguments abgerufen werden:

showMessage("Ann");

Das ist kein Fehler. Ein solcher Abruf würde "Ann: undefined" ausgeben, da es kein text gibt und daher davon ausgegangen wird das text === undefined ist.

Falls wir in diesem Fall ein "default" für text festlegen wollen, können wir diesen hinter = definieren:

function showMessage(from, *!*text = "kein Text übergeben"*/!*) {
  alert( from + ": " + text );
}

showMessage("Ann"); // Ann: kein Text übergeben

Wenn der text Parameter jetzt nicht gegeben wird, erhält er den Wert "kein Text übergeben"

Hier ist "kein Text übergeben" nur ein String, kann aber auch ein viel komplexerer Ausdruck (expression) sein, der nur evaluiert und zugewiesen wird, wenn der entsprechende Parameter fehlt. Das macht folgendes möglich:

function showMessage(from, text = anotherFunction()) {
  // anotherFunction() wird nur ausgeführt wenn kein Text gegeben ist
  // dessen Resultat wird der Wert von *text* sein
}
In JavaScript wird ein Default-Parameter immer dann evaluiert, wenn die Funktion ohne den respektiven Parameter abgerufen wird.

Im oberen Beispiel wird `anotherFunction()` jedes Mal abgerufen, wenn `showMessage()` ohne den `text` Parameter abgerufen wird.
In älteren JavaScript-Versionen waren Default-Parameter nicht unterstützt, weshalb sich alternative Wege entwickelt haben. Diese sind meist in alten *scripts* aufzufinden.

Beispielhaft ist eine strikte Prüfung auf `undefined`:

```js
function showMessage(from, text) {
*!*
  if (text === undefined) {
    text = 'kein Text übergeben';
  }
*/!*

  alert( from + ": " + text );
}
```

...Oder der `||` Operator:

```js
function showMessage(from, text) {
  // falls text nicht gegeben ist erhält es den "default" Wert
  text = text || 'kein Text übergeben';
  ...
}
```


Returning a value

A function can return a value back into the calling code as the result.

The simplest example would be a function that sums two values:

function sum(a, b) {
  *!*return*/!* a + b;
}

let result = sum(1, 2);
alert( result ); // 3

The directive return can be in any place of the function. When the execution reaches it, the function stops, and the value is returned to the calling code (assigned to result above).

There may be many occurrences of return in a single function. For instance:

function checkAge(age) {
  if (age >= 18) {
*!*
    return true;
*/!*
  } else {
*!*
    return confirm('Do you have permission from your parents?');
*/!*
  }
}

let age = prompt('How old are you?', 18);

if ( checkAge(age) ) {
  alert( 'Access granted' );
} else {
  alert( 'Access denied' );
}

It is possible to use return without a value. That causes the function to exit immediately.

For example:

function showMovie(age) {
  if ( !checkAge(age) ) {
*!*
    return;
*/!*
  }

  alert( "Showing you the movie" ); // (*)
  // ...
}

In the code above, if checkAge(age) returns false, then showMovie won't proceed to the alert.

````smart header="A function with an empty return or without it returns `undefined`" If a function does not return a value, it is the same as if it returns `undefined`:

function doNothing() { /* empty */ }

alert( doNothing() === undefined ); // true

An empty return is also the same as return undefined:

function doNothing() {
  return;
}

alert( doNothing() === undefined ); // true

````warn header="Never add a newline between `return` and the value"
For a long expression in `return`, it might be tempting to put it on a separate line, like this:

```js
return
 (some + long + expression + or + whatever * f(a) + f(b))
```
That doesn't work, because JavaScript assumes a semicolon after `return`. That'll work the same as:

```js
return*!*;*/!*
 (some + long + expression + or + whatever * f(a) + f(b))
```

So, it effectively becomes an empty return.

If we want the returned expression to wrap across multiple lines, we should start it at the same line as `return`. Or at least put the opening parentheses there as follows:

```js
return (
  some + long + expression
  + or +
  whatever * f(a) + f(b)
  )
```
And it will work just as we expect it to.

Naming a function [#function-naming]

Functions are actions. So their name is usually a verb. It should be brief, as accurate as possible and describe what the function does, so that someone reading the code gets an indication of what the function does.

It is a widespread practice to start a function with a verbal prefix which vaguely describes the action. There must be an agreement within the team on the meaning of the prefixes.

For instance, functions that start with "show" usually show something.

Function starting with...

  • "get…" -- return a value,
  • "calc…" -- calculate something,
  • "create…" -- create something,
  • "check…" -- check something and return a boolean, etc.

Examples of such names:

showMessage(..)     // shows a message
getAge(..)          // returns the age (gets it somehow)
calcSum(..)         // calculates a sum and returns the result
createForm(..)      // creates a form (and usually returns it)
checkPermission(..) // checks a permission, returns true/false

With prefixes in place, a glance at a function name gives an understanding what kind of work it does and what kind of value it returns.

A function should do exactly what is suggested by its name, no more.

Two independent actions usually deserve two functions, even if they are usually called together (in that case we can make a 3rd function that calls those two).

A few examples of breaking this rule:

- `getAge` -- would be bad if it shows an `alert` with the age (should only get).
- `createForm` -- would be bad if it modifies the document, adding a form to it (should only create it and return).
- `checkPermission` -- would be bad if it displays the `access granted/denied` message (should only perform the check and return the result).

These examples assume common meanings of prefixes. You and your team are free to agree on other meanings, but usually they're not much different. In any case, you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge.
Functions that are used *very often* sometimes have ultrashort names.

For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`.

These are exceptions. Generally functions names should be concise and descriptive.

Functions == Comments

Functions should be short and do exactly one thing. If that thing is big, maybe it's worth it to split the function into a few smaller functions. Sometimes following this rule may not be that easy, but it's definitely a good thing.

A separate function is not only easier to test and debug -- its very existence is a great comment!

For instance, compare the two functions showPrimes(n) below. Each one outputs prime numbers up to n.

The first variant uses a label:

function showPrimes(n) {
  nextPrime: for (let i = 2; i < n; i++) {

    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }

    alert( i ); // a prime
  }
}

The second variant uses an additional function isPrime(n) to test for primality:

function showPrimes(n) {

  for (let i = 2; i < n; i++) {
    *!*if (!isPrime(i)) continue;*/!*

    alert(i);  // a prime
  }
}

function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if ( n % i == 0) return false;
  }
  return true;
}

The second variant is easier to understand, isn't it? Instead of the code piece we see a name of the action (isPrime). Sometimes people refer to such code as self-describing.

So, functions can be created even if we don't intend to reuse them. They structure the code and make it readable.

Summary

A function declaration looks like this:

function name(parameters, delimited, by, comma) {
  /* code */
}
  • Values passed to a function as parameters are copied to its local variables.
  • A function may access outer variables. But it works only from inside out. The code outside of the function doesn't see its local variables.
  • A function can return a value. If it doesn't, then its result is undefined.

To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables.

It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side-effect.

Function naming:

  • A name should clearly describe what the function does. When we see a function call in the code, a good name instantly gives us an understanding what it does and returns.
  • A function is an action, so function names are usually verbal.
  • There exist many well-known function prefixes like create…, show…, get…, check… and so on. Use them to hint what a function does.

Functions are the main building blocks of scripts. Now we've covered the basics, so we actually can start creating and using them. But that's only the beginning of the path. We are going to return to them many times, going more deeply into their advanced features.