Tracks
/
JavaScript
JavaScript
/
Übungen
/
Koordinatentransformation
Koordinatentransformation

Koordinatentransformation

Lernübung

Einführung

Closures sind ein Programmiermuster in JavaScript, mit dem du Variablen aus einem äußeren lexikalischen Gültigkeitsbereich in einem verschachtelten Block verwenden kannst. JavaScript unterstützt Closures ganz selbstverständlich, und sie werden oft benutzt, ohne dass man weiß, was sie eigentlich sind.

// Top-level declarations are global-scope
const dozen = 12;

{
  // Braces create a new block-scope
  // Referencing the outer variable is a closure.
  const twoDozen = dozen * 2;
}

// Because of the block-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined

Neben geschweiften Klammern {} erzeugen auch Funktionen (und Klassen) neue Gültigkeitsbereiche und können Werte einschließen:

const dozen = 12;

// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
  // This is declared inside the function scope, and uses the top-level scope.
  // This works, and encloses the value 12.
  const twoDozen = dozen * 2;

  // This only uses the locally declared variable and the passed argument to the parameter `n`
  return (twoDozen / 2) * n;
}

// Because of the function-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined

Wie die twoDozen-Beispiele zeigen, lassen sich Werte in einem verschachtelten Gültigkeitsbereich (Funktion, Block usw.) einschließen, aber nicht aus diesem Kontext herausziehen. In den meisten Fällen ist es im modernen JavaScript beabsichtigt, dass ein Wert nicht in einen äußeren Gültigkeitsbereich entweicht.

Closures, um Zustand zu speichern und Werte weiterzugeben

Mit einer veränderlichen Variablendeklaration (wie let oder var) lässt sich ein Zustand bewahren:

let counter = 0;

// This function closure increments the counter's state in the outer lexical context.
// This way the counter can be shared between many calling contexts.
export function increment() {
  counter += 1;
  return counter;
}

increment();
// => 1

counter;
// => 1

Werte einschließen, ohne den Zustand nach außen dringen zu lassen

Kombinierst du die beiden Ideen, also einen Wert einzuschließen, um Zustand zu bewahren, und dafür zu sorgen, dass eingeschlossene Werte nicht nach außen entweichen, dann kannst du private Werte erzeugen.

Am häufigsten erstellst du eine Funktion, die eine Funktion zurückgibt, welche einen Zustand einschließt.

export function makeCounter() {
  let counter = 0;

  // This returns a new function that encloses the local variable counter
  return function increment() {
    counter += 1;
    return counter;
  };
}

// Counter did not leak
counter;
// => Uncaught ReferenceError: counter is not defined

// This creates a new counter.
// This assigns the increment function to the variable myFirstCounter.
const myFirstCounter = makeCounter();

typeof myFirstCounter;
// => function

myFirstCounter.name;
// => increment

myFirstCounter();
// => 1
myFirstCounter();
// => 2

// This creates new counter (with new, separate local state / enclosed counter variable)
const mySecondCounter = makeCounter();

mySecondCounter();
// => 1

// It does not affect the first counter.

myFirstCounter();
// => 3
Note

Viele Programmierer finden Closures ein schwieriges Konzept, und eine Funktion aus einer Funktion zurückzugeben ist nicht üblich oder in manchen Programmiersprachen gar nicht möglich. Wenn du mehr Lesestoff möchtest, ist der Leitfaden zu Closures auf MDN sehr ausführlich.

Anleitung

Deine Designfirma hat bisher vor allem mit CSS-Transformationen gearbeitet, um Webseiten zu erstellen. Nach einiger Diskussion entscheidet ihr euch, ab jetzt JavaScript zu verwenden, um einige Berechnungen dynamisch durchzuführen. Einige deiner Teammitglieder haben weniger Erfahrung mit JavaScript, also entscheidest du dich, eine Closure zu verwenden, um wiederverwendbare Transformationen für {x, y}-Koordinatenpaare zu erstellen.

1. Verschiebe die Koordinaten

Implementiere die Funktion translate2d, die eine Funktion zurückgibt, die mithilfe einer Closure eine wiederholbare 2D-Verschiebung eines Koordinatenpaars durchführt.

In der Geometrie bezeichnet die Verschiebung das Bewegen von Punkten, Vektoren oder Formen um dieselbe Strecke in eine Richtung. Sie lässt sich als Addition einer Konstante zu jedem Punkt auffassen.

const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]

2. Skaliere die Koordinaten

Implementiere die Funktion scale2d, die eine Funktion zurückgibt, die mithilfe einer Closure eine wiederholbare 2D-Skalierung eines Koordinatenpaars durchführt.

In der Geometrie bezeichnet die gleichmäßige Skalierung das Vergrößern oder Verkleinern von Vektoren oder Formen in die gleiche Richtung. Sie lässt sich als Multiplikation jedes Punktes mit einer Konstante (Skalierungsfaktor) auffassen.

Gehe in dieser Übung davon aus, dass nur positive Skalierungswerte vorkommen.

const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]

3. Setze Transformationsfunktionen zusammen

Kombiniere zwei Transformationsfunktionen zu einer wiederholbaren Transformation. Das nennt man oft Funktionskomposition. Dabei wird das Ergebnis der ersten Funktion 'f(x)' als Eingabewert für die zweite Funktion 'g(x)' verwendet.

const moveCoordinatesRight2Px = translate2d(2, 0);
const doubleCoordinates = scale2d(2, 2);
const composedTransformations = composeTransform(
  moveCoordinatesRight2Px,
  doubleCoordinates,
);
const result = composedTransformations(0, 1);
// result => [4, 2]

4. Speichere die Ergebnisse von Funktionen

Implementiere die Funktion memoizeTransform. Sie nimmt eine Funktion entgegen, die memoisiert werden soll, und gibt eine neue Funktion zurück, die sich die Eingabewerte der übergebenen Funktion merkt. So kann der letzte Rückgabewert „gemerkt“ und nur einmal berechnet werden, wenn die Funktion erneut mit denselben Argumenten aufgerufen wird.

Memoisierung wird manchmal in der dynamischen Programmierung verwendet. Sie ermöglicht, teure Operationen nur einmal auszuführen, da ihre Ergebnisse gemerkt werden. Beachte, dass in dieser Übung nur das letzte Ergebnis gemerkt wird, anders als bei manchen Lösungen der dynamischen Programmierung, die alle Ergebnisse memoisieren.

const tripleScale = scale2d(3, 3);
const memoizedScale = memoizeTransform(tripleScale);

memoizedScale(4, 3); // => [12, 9], this is computed since it hasn't been computed before for the arguments
memoizedScale(4, 3); // => [12, 9], this is remembered, since it was computed already
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
JavaScript Exercism

Bereit, mit Koordinatentransformation zu starten?

Melde dich bei Exercism an, um JavaScript mit 37 Konzepte159 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.