Erste Schritte
Hello World & Kommentare
JavaScript läuft in Browsern und Node.js. console.log() ist die primäre Debug-Ausgabe. JSDoc-Kommentare (/** */) bieten Typinformationen und Dokumentation für IDEs. Verwenden Sie 'use strict' oder ES-Module für sichereres Parsen. Kommentare werden zur Laufzeit ignoriert.
// Single-line comment
/* Multi-line
comment */
// Console output
console.log("Hello, World!");
console.warn("Warning message");
console.error("Error message");
console.table([{a: 1}, {a: 2}]);
// JSDoc comments (for documentation)
/**
* Adds two numbers
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} The sum
*/
function add(a, b) { return a + b; }Strict Mode & Module
'use strict' aktiviert strengeres Parsen und fängt stille Fehler ab. ES-Module (import/export) sind der moderne Standard; CommonJS (require/module.exports) ist Node.js-traditionell. Verwenden Sie immer Module, um den globalen Scope nicht zu verschmutzen. Browser unterstützen <script type='module'>.
"use strict"; // enables strict mode (catches common mistakes)
x = 10; // ReferenceError: x is not defined (strict mode)
// ES Modules (modern)
// import { greet } from './utils.js';
// export function greet(name) { return `Hello, ${name}`; }
// CommonJS (Node.js traditional)
// const fs = require('fs');
// module.exports = { greet: (name) => `Hello, ${name}` };
// Running JS
// Browser: include in <script> or via dev server
// Node.js: node script.js
// Deno: deno run script.tsVariablen: let, const, var
Bevorzugen Sie immer const; verwenden Sie let nur, wenn Neuzuweisung nötig ist; vermeiden Sie var vollständig. const verhindert Neuzuweisung, aber nicht Mutation – Objekt-/Array-Inhalte können sich noch ändern. let/const sind block-scoped; var ist function-scoped und gehoisted (verursacht Bugs). TDZ verhindert die Verwendung von Variablen vor der Deklaration.
// const - cannot be reassigned (use by default)
const PI = 3.14159;
const user = { name: "Alice" };
user.name = "Bob"; // OK - object contents can change
// user = {}; // TypeError - can't reassign const
// let - block-scoped, can be reassigned
let count = 0;
count = 1;
if (true) {
let local = 5; // only accessible in this block
}
// var - function-scoped, hoisted (AVOID in modern code)
var old = "legacy"; // hoisted, can cause bugs
// Temporal Dead Zone (TDZ)
// console.log(x); // ReferenceError
// let x = 5;Datentypen & typeof
JavaScript hat 7 primitive Typen (string, number, bigint, boolean, undefined, null, symbol) und Referenztypen (Objekte, Arrays, Funktionen). Primitive sind unveränderlich und werden per Wert kopiert; Objekte sind veränderbar und werden per Referenz übergeben. typeof null gibt 'object' aufgrund eines historischen Bugs zurück – verwenden Sie === null, um auf null zu prüfen.
// Primitive types (immutable, passed by value)
const str = "hello"; // string
const num = 42; // number (no separate int/float)
const big = 9007199254740993n; // bigint
const bool = true; // boolean
const undef = undefined; // undefined (no value)
const empty = null; // null (intentional empty)
const sym = Symbol("id"); // symbol (unique identifier)
// Reference types (mutable, passed by reference)
const arr = [1, 2, 3]; // object (array)
const obj = { a: 1 }; // object
const fn = function() {}; // object (function)
// typeof operator
console.log(typeof "hi"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical bug!)
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function(){}); // "function"Typkonvertierung & Coercion
JavaScripts Typ-Coercion ist notorisch verwirrend. Verwenden Sie immer === (strikte Gleichheit) statt == (locker), um unerwartete Coercion zu vermeiden. Der +-Operator verkettet, wenn einer der Operanden ein String ist; andere Operatoren konvertieren zu Number. Falsy-Werte: false, 0, '', null, undefined, NaN. Verwenden Sie Number.isNaN(), um auf NaN zu prüfen (NaN !== NaN).
// Explicit conversion
String(42); // "42"
(42).toString(); // "42"
Number("42"); // 42
Number("3.14"); // 3.14
Number(""); // 0
Number("abc"); // NaN
parseInt("42px"); // 42
parseFloat("3.14abc"); // 3.14
Boolean(0); // false
Boolean(""); // false
Boolean("x"); // true
// Implicit coercion (often confusing)
console.log("5" + 3); // "53" (string concatenation)
console.log("5" - 3); // 2 (numeric subtraction)
console.log("5" * "2"); // 10
console.log(1 + "2" + 3); // "123"
console.log(true + 1); // 2
// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("0") console.log("truthy"); // runs! non-empty string
// Strict vs loose equality
console.log(1 == "1"); // true (loose, coerces)
console.log(1 === "1"); // false (strict, no coercion)
console.log(null == undefined); // true
console.log(null === undefined); // falseStrings
String-Methoden
Strings sind unveränderlich – Methoden geben neue Strings zurück. slice() unterstützt negative Indizes (vom Ende); substring() nicht. replace() ersetzt nur die erste Übereinstimmung; verwenden Sie replaceAll() (ES2021) für alle. at() (ES2022) erlaubt negative Indizes. split() + join() ist die idiomatische Methode, Zeichen in einem String zu ersetzen.
const s = "Hello, World";
// Length & access
console.log(s.length); // 12
console.log(s[0]); // "H"
console.log(s.charAt(0)); // "H"
console.log(s.at(-1)); // "d" (ES2022, negative index)
// Case
console.log(s.toUpperCase()); // "HELLO, WORLD"
console.log(s.toLowerCase()); // "hello, world"
// Search
console.log(s.indexOf("World")); // 7 (-1 if not found)
console.log(s.includes("World")); // true
console.log(s.startsWith("Hello")); // true
console.log(s.endsWith("World")); // true
// Extract
console.log(s.slice(0, 5)); // "Hello"
console.log(s.slice(-5)); // "World"
console.log(s.substring(0, 5)); // "Hello" (no negative)
console.log(s.split(", ")); // ["Hello", "World"]
// Modify
console.log(s.replace("o", "0")); // "Hell0, World"
console.log(s.replaceAll("o", "0")); // "Hell0, W0rld"
console.log(s.trim()); // remove whitespace
console.log(s.padStart(15, "*")); // "***Hello, World"
console.log("a,b,c".split(",").join("-")); // "a-b-c"Template-Literale
Template-Literale (Backticks) erlauben String-Interpolation mit ${}, mehrzeilige Strings und getaggte Templates. Sie sind viel lesbarer als String-Verkettung. Getaggte Templates lassen Sie Template-Literale mit einer Funktion verarbeiten – verwendet von styled-components, graphql-tag usw.
const name = "Alice";
const age = 30;
// Template literals (backticks)
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);
// Multi-line strings
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// Expressions inside ${}
console.log(`Next year: ${age + 1}`);
console.log(`Upper: ${name.toUpperCase()}`);
console.log(`${name.length} chars`);
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `<b>${values[i]}</b>` : ''), '');
}
const result = highlight`Name: ${name}, Age: ${age}`;
// "Name: <b>Alice</b>, Age: <b>30</b>"String-Suche & Regex
JavaScript-Strings unterstützen Regex via match(), matchAll(), replace(), search() und split(). Benannte Erfassungsgruppen (?<name>...) (ES2018) machen Regex lesbarer. matchAll() gibt einen Iterator zurück (effizienter als match() für globale Regex). Verwenden Sie .test(), um zu prüfen, ob ein Muster übereinstimmt, ohne zu extrahieren.
const text = "The quick brown fox jumps over the lazy dog";
// Search methods
console.log(text.search(/brown/)); // 10 (index of match)
console.log(text.match(/\w+/g)); // ["The","quick","brown",...]
console.log(text.matchAll(/\w+/g)); // iterator of matches
// Replace with regex
console.log(text.replace(/o/g, "0")); // replace all 'o'
console.log(text.replace(/(\w+)/g, "\$1!")); // capture group
// Capture groups
const date = "2024-01-15";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day); // 2024 01 15
// Named capture groups (ES2018)
const m = date.match(/(?<year>\d{4})-(?<month>\d{2})/);
console.log(m.groups.year); // 2024
console.log(m.groups.month); // 01
// Test if pattern matches
const emailRe = /^[^@]+@[^@]+\.[^@]+$/;
console.log(emailRe.test("[email protected]")); // trueString-Iteration & Spread
Strings sind mit for...of iterierbar. Der Spread-Operator [...] teilt einen String in Zeichen – unerlässlich für korrekte Emoji-Behandlung (Surrogate Pairs). String-Vergleich ist lexicografisch nach UTF-16-Codeeinheit, verwenden Sie also localeCompare() für locale-bewusstes Sortieren. Emoji und einige Zeichen sind 2 Codeeinheiten lang.
const s = "Hello";
// Iterate characters
for (const char of s) {
console.log(char); // H, e, l, l, o
}
// Spread into array
const chars = [...s]; // ["H", "e", "l", "l", "o"]
console.log(chars);
// Spread with map/filter
const upper = [...s].map(c => c.toUpperCase()).join("");
console.log(upper); // HELLO
// String comparison
console.log("a" < "b"); // true (lexicographic)
console.log("apple" < "banana"); // true
console.log("Z" < "a"); // true (uppercase < lowercase in ASCII)
// Locale-aware comparison
console.log("ö".localeCompare("o", "de")); // locale-specific
// Repeat
console.log("ab".repeat(3)); // "ababab"
// Code points (handles emoji correctly)
const emoji = "😀";
console.log(emoji.length); // 2 (surrogate pair!)
console.log([...emoji].length); // 1 (correct)
console.log("😀".codePointAt(0)); // 128512Zahlen & Mathematik
Zahlen & Operatoren
JavaScript hat einen einzigen Zahlentyp (64-bit Float) – keine separate int/float. BigInt (n-Suffix) behandelt Integer über 2^53 hinaus. Fließkomma-Arithmetik hat Genauigkeitsprobleme (0.1 + 0.2 !== 0.3) – verwenden Sie Number.EPSILON für Vergleiche. ** ist der Potenzierungsoperator (ES2016).
// JavaScript has one number type (IEEE 754 double)
const int = 42;
const float = 3.14;
const exp = 1e6; // 1000000
const hex = 0xff; // 255
const bin = 0b1010; // 10
const oct = 0o755; // 493
const big = 9007199254740993n; // BigInt (arbitrary precision)
// Arithmetic
console.log(10 / 3); // 3.333...
console.log(10 % 3); // 1 (remainder)
console.log(2 ** 10); // 1024 (exponent)
console.log(Math.floor(10 / 3)); // 3
console.log(Math.trunc(-3.7)); // -3 (toward zero)
// Increment/decrement
let x = 5;
console.log(x++); // 5 (post-increment, returns old)
console.log(++x); // 7 (pre-increment, returns new)
// Floating point issues
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false!
// Fix: use Number.EPSILON or round
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // trueMath-Objekt
Das Math-Objekt bietet Konstanten und Funktionen. Alle trigonometrischen Funktionen verwenden Bogenmaß. Math.random() gibt [0, 1) zurück – multiplizieren und abrunden für Integer-Bereiche. Für kryptografische Zufälligkeit verwenden Sie crypto.getRandomValues(). Math.max/min akzeptieren keine Arrays direkt – spreaden Sie sie mit ....
// Constants
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
console.log(Math.SQRT2); // 1.4142135623730951
// Rounding
console.log(Math.round(3.7)); // 4 (nearest)
console.log(Math.floor(3.7)); // 3 (down)
console.log(Math.ceil(3.2)); // 4 (up)
console.log(Math.trunc(-3.7)); // -3 (toward zero)
console.log(Math.sign(-5)); // -1 (sign: -1, 0, or 1)
// Power & roots
console.log(Math.pow(2, 10)); // 1024
console.log(Math.sqrt(144)); // 12
console.log(Math.cbrt(27)); // 3
console.log(Math.abs(-5)); // 5
// Min/Max
console.log(Math.max(1, 5, 3)); // 5
console.log(Math.min(1, 5, 3)); // 1
console.log(Math.max(...[1, 5, 3])); // 5 (spread array)
// Trigonometry (radians)
console.log(Math.sin(Math.PI / 2)); // 1
console.log(Math.cos(0)); // 1
console.log(Math.PI / 180 * 90); // radians from degrees
// Random
console.log(Math.random()); // 0 to <1
console.log(Math.floor(Math.random() * 100)); // 0-99 integerNumber-Methoden & Parsen
Geben Sie immer die Basis (Radix) für parseInt() an – ältere Browser interpretieren führende Nullen als oktal. Number.isNaN() ist zuverlässig; globales isNaN() konvertiert (isNaN('abc') ist true). toFixed() gibt einen String zurück, keine Number. Zahlen über MAX_SAFE_INTEGER verlieren Genauigkeit – verwenden Sie BigInt.
// Number methods
const num = 1234.5678;
console.log(num.toFixed(2)); // "1234.57" (string)
console.log(num.toPrecision(3)); // "1.23e+3"
console.log(num.toString(2)); // binary string
console.log((255).toString(16)); // "ff" (hex)
// Number object methods
console.log(Number.isInteger(42)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN)); // true (reliable)
console.log(Number.isNaN("NaN")); // false (global isNaN would be true)
console.log(Number.parseInt("42px")); // 42
console.log(Number.parseFloat("3.14")); // 3.14
// Parsing strings
console.log(parseInt("42", 10)); // 42 (always specify radix!)
console.log(parseInt("0xff", 16)); // 255
console.log(parseFloat("3.14abc")); // 3.14
// Safe integers
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.isSafeInteger(2 ** 53)); // false
// NaN checks
const result = Number("abc"); // NaN
console.log(Number.isNaN(result)); // true
console.log(result === NaN); // false! NaN !== NaNDatenstrukturen
Arrays
Arrays sind dynamisch, geordnet und können gemischte Typen halten. push/pop sind O(1); shift/unshift/splice sind O(n). find()/findIndex() nehmen eine Prädikatsfunktion. forEach() gibt nichts zurück; verwenden Sie map() zum Transformieren. Arrays sind Objekte – typeof [] ist 'object'. Verwenden Sie Array.isArray() zum Prüfen.
// Creating arrays
const nums = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null];
const empty = new Array(5); // [empty x 5]
// Access & modify
console.log(nums[0]); // 1
console.log(nums.length); // 5
nums[0] = 0; // modify
console.log(nums.at(-1)); // 5 (negative index, ES2022)
// Add/remove
nums.push(6); // add to end, returns new length
nums.pop(); // remove from end, returns element
nums.unshift(0); // add to start
nums.shift(); // remove from start
nums.splice(1, 2); // remove 2 elements at index 1
nums.splice(1, 0, "a"); // insert at index 1
// Search
console.log(nums.indexOf(3)); // index or -1
console.log(nums.includes(3)); // true/false
console.log(nums.find(n => n > 3)); // first match
console.log(nums.findIndex(n => n > 3)); // index of first match
// Iterate
nums.forEach((val, idx) => console.log(idx, val));
for (const [idx, val] of nums.entries()) {
console.log(idx, val);
}Array-Methoden (map, filter, reduce)
map/filter/reduce sind die heilige Dreifaltigkeit der funktionalen Array-Programmierung. map transformiert, filter wählt aus, reduce aggregiert. Sie sind verkettbar und mutieren nicht das Original (außer reverse/sort). sort() konvertiert standardmäßig zu Strings – stellen Sie immer einen Komparator für Zahlen bereit. flat() flattet verschachtelte Arrays.
const nums = [1, 2, 3, 4, 5];
// map - transform each element (returns new array)
const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10]
const withIndex = nums.map((n, i) => `${i}:${n}`);
// filter - keep elements that pass test (returns new array)
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
// reduce - accumulate to single value
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120
const max = nums.reduce((a, b) => Math.max(a, b));
// Chaining
const result = nums
.filter(n => n % 2 === 0) // [2, 4]
.map(n => n * 10) // [20, 40]
.reduce((sum, n) => sum + n, 0); // 60
// Other useful methods
console.log(nums.slice(1, 3)); // [2, 3] (copy portion)
console.log(nums.concat([6, 7])); // [1,2,3,4,5,6,7]
console.log([...nums, 6, 7]); // spread (modern)
console.log(nums.reverse()); // reverse in-place
console.log(nums.sort((a, b) => a - b)); // numeric sort
console.log([3, 1, 2].sort()); // [1, 2, 3] (string sort!)
console.log(nums.flat()); // flatten one level
console.log([1, [2, [3]]].flat(Infinity)); // [1, 2, 3]Objekte
Objekte sind Schlüssel-Wert-Sammlungen (Schlüssel sind Strings oder Symbole). Punktnotation für statische Schlüssel, Klammernotation für dynamische/spezielle Schlüssel. Berechnete Eigenschaftsnamen {[expr]: val} sind ES6. Object.keys/values/entries extrahieren Arrays; Object.fromEntries kehrt entries um. for...in iteriert Schlüssel (einschließlich geerbter).
// Object literal
const user = {
name: "Alice",
age: 30,
"is-admin": false, // keys with special chars need quotes
greet() { // method shorthand
return `Hello, I'm ${this.name}`;
}
};
// Access
console.log(user.name); // dot notation
console.log(user["is-admin"]); // bracket notation (for dynamic/special keys)
// Computed property names (ES6)
const key = "dynamic";
const obj = { [key]: "value", [`id_${1}`]: 100 };
// Add/modify/delete
user.email = "[email protected]"; // add
user.age = 31; // modify
delete user["is-admin"]; // delete
// Check property existence
console.log("name" in user); // true
console.log(user.hasOwnProperty("name")); // true
// Iterate
for (const key in user) {
console.log(key, user[key]);
}
// Object methods
console.log(Object.keys(user)); // ["name", "age", ...]
console.log(Object.values(user)); // ["Alice", 31, ...]
console.log(Object.entries(user)); // [["name","Alice"], ...]
console.log(Object.fromEntries([["a", 1]])); // {a: 1}Destructuring & Spread
Destructuring extrahiert Werte aus Objekten/Arrays prägnant. Unterstützt Umbenennung (key: newName), Standards (= value) und Rest (...rest). Spread (...) expandiert Iterables/Objekte – großartig zum Zusammenführen und flachen Kopieren. Object-Spread überschreibt doppelte Schlüssel (letzter gewinnt). Destructuring in Funktionsparametern ist mächtig für optionale Konfiguration.
// Object destructuring
const user = { name: "Alice", age: 30, email: "[email protected]" };
const { name, age } = user; // extract by key
const { name: fullName, email = "N/A" } = user; // rename + default
const { ...rest } = user; // rest pattern
// name -> undefined, fullName -> "Alice", rest -> {age, email}
// Array destructuring
const [a, b, c] = [1, 2, 3];
const [first, , third] = [1, 2, 3]; // skip elements
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]
// Swap variables
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1
// Nested destructuring
const { data: { users } } = response;
const [[a, b], [c, d]] = [[1, 2], [3, 4]];
// Function parameters
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}`;
}
greet({ name: "Alice" }); // "Hello, Alice"
// Spread operator
const arr1 = [1, 2], arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
const obj1 = { a: 1 }, obj2 = { b: 2 };
const combined = { ...obj1, ...obj2 }; // {a:1, b:2}
const clone = { ...user }; // shallow copyMap, Set, WeakMap
Map erlaubt jeden Schlüsseltyp (Objekte, nicht nur Strings) und erhält die Einfügereihenfolge – anders als plain Objects. Set speichert eindeutige Werte – perfekt zum Deduplizieren. WeakMap/WeakSet-Schlüssel sind schwach referenziert (können garbage collected werden), was Speicherlecks verhindert. Verwenden Sie Map, wenn Sie Nicht-String-Schlüssel oder häufiges Hinzufügen/Löschen benötigen.
// Map - key-value pairs, any key type, maintains insertion order
const map = new Map();
map.set("name", "Alice");
map.set(42, "number key");
map.set({ obj: true }, "object key");
console.log(map.get("name")); // "Alice"
console.log(map.has("name")); // true
console.log(map.size); // 3
map.delete(42);
map.clear();
// Iterate Map
for (const [key, value] of map) {
console.log(key, value);
}
// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3 (duplicates removed)
set.add(4);
set.has(2); // true
set.delete(1);
// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2,3}
const difference = new Set([...a].filter(x => !b.has(x))); // {1}
// WeakMap/WeakSet - keys must be objects, GC-friendly
const weakMap = new WeakMap();
weakMap.set({}, "value"); // key can be garbage collectedKontrollfluss
If / Else & Ternär
Verwenden Sie if/else für komplexe Logik; ternär für einfache Wertauswahl. && und || short-circuit (nützlich für Defaults/Bedingungen). ?? (nullish coalescing) prüft nur null/undefined, anders als ||, das alle falsy-Werte prüft. ?. (optional chaining) greift sicher auf verschachtelte Eigenschaften zu ohne Fehler.
const score = 85;
// if / else if / else
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else {
console.log("C");
}
// Ternary operator (expression)
const grade = score >= 60 ? "pass" : "fail";
// Nested ternary (avoid - hard to read)
const status = score >= 90 ? "excellent"
: score >= 80 ? "good"
: score >= 60 ? "pass"
: "fail";
// Short-circuit evaluation
const name = user?.name || "Anonymous"; // default value
const value = condition && doSomething(); // execute if true
// Nullish coalescing (??) - only null/undefined, not falsy
const count = 0 ?? 10; // 0 (not 10, because 0 is not null/undefined)
const name2 = null ?? "default"; // "default"
// Optional chaining (?.)
const city = user?.address?.city; // undefined if any link is null
const length = user?.name?.length; // undefined if user or name is nullSwitch-Anweisung
switch vergleicht mit strikter Gleichheit (===), also ist der Typ wichtig. Vergessen Sie nicht break – ohne fällt die Ausführung zum nächsten case durch. Gruppieren Sie cases durch Stapeln (case 6: case 7:). Switch ist sauberer als lange if/else-Ketten für diskrete Werte. Moderner Code bevorzugt manchmal Object-Lookup-Tabellen.
const day = 3;
// Traditional switch (use break!)
switch (day) {
case 1:
console.log("Monday");
break; // without break, falls through!
case 2:
console.log("Tuesday");
break;
case 6:
case 7: // multiple cases share code
console.log("Weekend");
break;
default:
console.log("Weekday");
}
// Switch with return (no break needed)
function getColor(type) {
switch (type) {
case "success": return "green";
case "error": return "red";
case "warning": return "yellow";
default: return "gray";
}
}
// Strict equality (===)
switch (1) {
case "1": console.log("string"); // NOT matched
case 1: console.log("number"); // matched
}Schleifen
Verwenden Sie for...of für Arrays/Strings (Werte), for...in für Objekte (Schlüssel) – niemals for...in auf Arrays (iteriert Indizes als Strings und inkludiert Prototype). while prüft vor der Ausführung; do...while läuft mindestens einmal. break beendet, continue springt. Verwenden Sie .entries(), um Index+Wert mit for...of zu erhalten.
// for loop
for (let i = 0; i < 5; i++) {
console.log(i);
}
// for...of (iterable values - arrays, strings, maps)
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// for...in (object keys - AVOID for arrays!)
const user = { name: "Alice", age: 30 };
for (const key in user) {
console.log(key, user[key]);
}
// while
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
// do...while (runs at least once)
let i = 0;
do {
console.log(i);
i++;
} while (i < 3);
// break & continue
for (let i = 0; i < 10; i++) {
if (i === 5) break; // exit loop
if (i % 2 === 0) continue; // skip iteration
console.log(i);
}
// Iterate with index
for (const [index, value] of fruits.entries()) {
console.log(index, value);
}Iteratoren & Generatoren
Iteratoren implementieren next(), das {value, done} zurückgibt. Generatoren (function*) vereinfachen die Iterator-Erstellung mit yield – sie pausieren die Ausführung und setzen bei next() fort. Generatoren sind faul (berechnen bei Bedarf) und können unendlich sein. Verwenden Sie sie für benutzerdefinierte Iterables, Sequenzen und Async-Flows.
// Iterable protocol (Symbol.iterator)
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const num of range) console.log(num); // 1, 2, 3, 4, 5
console.log([...range]); // [1, 2, 3, 4, 5]
// Generator function (function*)
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next().value); // 3
// Generator with yield*
function* nested() {
yield 1;
yield* [2, 3, 4]; // delegate to another iterable
yield 5;
}Funktionen
Funktionsdeklarationen & Ausdrücke
Funktionsdeklarationen werden gehoisted (können vor der Definition aufgerufen werden); Ausdrücke nicht. Arrow Functions sind prägnant und haben kein eigenes 'this' (sie erben vom einschließenden Scope). IIFEs erstellen private Scopes (mit Modulen weniger nötig). Funktionen sind First-Class – übergeben Sie sie als Argumente, geben Sie sie zurück, speichern Sie in Variablen.
// Function declaration (hoisted - can be called before definition)
greet("Alice"); // works (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression (not hoisted)
const greet2 = function(name) {
return `Hi, ${name}!`;
};
// Named function expression (for recursion/stack traces)
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
// Arrow function (ES6) - concise, no own this
const add = (a, b) => a + b;
const square = x => x * x;
const greet3 = () => "Hello!";
const log = x => { console.log(x); }; // block body needs return
// IIFE (Immediately Invoked Function Expression)
const result = (function() {
const private = "secret";
return private.toUpperCase();
})();
// Functions are first-class (can be passed/returned)
function apply(fn, value) {
return fn(value);
}
console.log(apply(square, 5)); // 25Arrow Functions & this
Arrow Functions haben kein eigenes 'this', 'arguments', 'super' oder 'new.target' – sie erben vom einschließenden Scope. Das macht sie perfekt für Callbacks (besonders in Klassenmethoden). Aber sie können nicht als Konstruktoren oder Methoden verwendet werden, die ihr eigenes 'this' benötigen. Verwenden Sie reguläre Funktionen für Objektmethoden.
// Arrow function variations
const add = (a, b) => a + b; // implicit return
const greet = name => `Hi ${name}`; // single param, no parens
const log = () => console.log("hi"); // no params
const obj = (x, y) => ({ x, y }); // return object needs parens
const multi = (a, b) => { // block body
const sum = a + b;
return sum * 2;
};
// Arrow functions don't have their own 'this'
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 'this' refers to Timer instance
console.log(this.seconds);
}, 1000);
}
// Regular function has its own 'this'
function Counter() {
this.count = 0;
// Regular function: 'this' is undefined (strict) or global
document.addEventListener("click", function() {
// this.count++; // ERROR: this is not Counter
});
// Arrow function: 'this' is Counter
document.addEventListener("click", () => {
this.count++; // works!
});
}
// Arrow functions can't be constructors
// const obj = new arrowFunc(); // TypeErrorClosures
Closures sind Funktionen, die sich an die Variablen aus ihrem definierenden Scope 'erinnern', selbst nachdem dieser Scope beendet ist. Sie ermöglichen Datenprivatsphäre (Module-Pattern), Memoization, Currying und partielle Anwendung. Jede Funktion in JavaScript ist eine Closure. Die innere Funktion behält eine Referenz auf äußere Variablen, keine Kopie.
// A closure is a function that remembers its outer variables
function makeCounter() {
let count = 0; // private variable
return function() {
return ++count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is private - can't access directly
// Module pattern (pre-ES6)
const calculator = (function() {
const result = 0; // private
return {
add(x) { return result + x; },
multiply(x) { return result * x; }
};
})();
// Practical: memoization
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!(key in cache)) {
cache[key] = fn.apply(this, args);
}
return cache[key];
};
}
const slowFib = memoize(n =>
n < 2 ? n : slowFib(n - 1) + slowFib(n - 2)
);
// Currying with closures
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // 10Arguments & Rest/Spread
Standardparameter bieten Fallback-Werte. Rest-Parameter (...name) sammeln zusätzliche Argumente in ein echtes Array – bevorzugen Sie gegenüber dem veralteten 'arguments'-Objekt. Spread (...) expandiert ein Array in einzelne Argumente. Destructuring in Parametern ermöglicht benannte, optionale Konfigurationsobjekte – ein häufiges API-Pattern.
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
return `${greeting}, ${name}!`;
}
greet(); // "Hello, Guest!"
greet("Alice"); // "Hello, Alice!"
greet("Bob", "Hi"); // "Hi, Bob!"
// Rest parameters (...args collects into array)
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
function log(tag, ...args) {
console.log(tag, args);
}
// Spread (opposite of rest)
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // 3 (spread array as args)
console.log(sum(...nums)); // 6
// arguments object (old way, avoid in modern code)
function old() {
console.log(arguments); // array-like, not a real array
const args = Array.from(arguments); // convert
}
// Destructuring parameters
function process({ name, age = 0 } = {}) {
console.log(name, age);
}
process({ name: "Alice" }); // Alice 0
process(); // undefined 0 (default empty object)OOP & Klassen
Klassen & Konstruktor
ES6-Klassen sind syntaktischer Zucker über Prototypen. Private Felder (#name) sind ES2022 und wirklich privat (anders als die _name-Konvention). Getter/Setter erlauben berechnete Properties. Statische Mitglieder gehören zur Klasse, nicht zu Instanzen. Klassenfelder (name = value) initialisieren Instanz-Properties ohne Konstruktor.
class Person {
// Fields (class fields proposal - ES2022)
species = "human"; // instance field
// Private fields (ES2022)
#ssn = "secret"; // truly private
// Static field/method
static count = 0;
// Constructor
constructor(name, age) {
this.name = name;
this.age = age;
Person.count++;
}
// Instance method
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name}, ${this.age}`;
}
// Setter
set age(value) {
if (value < 0) throw new Error("Invalid age");
this._age = value;
}
get age() {
return this._age;
}
// Static method
static create(name) {
return new Person(name, 0);
}
}
const alice = new Person("Alice", 30);
console.log(alice.greet()); // Hello, I'm Alice
console.log(alice.info); // Alice, 30
console.log(Person.count); // 1
// alice.#ssn; // SyntaxError - private!Vererbung & Polymorphismus
extends erstellt Vererbung; super() ruft den Eltern-Konstruktor auf (erforderlich vor der Verwendung von 'this'). Überschreiben Sie Methoden durch Neudefinieren. JavaScript ist einfache Vererbung, aber Mixins (Klassen-Fabriken) bieten Komposition. instanceof prüft die Prototype-Kette. Polymorphismus funktioniert durch Methoden-Überschreibung.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
// Static method can be inherited
static create(type, name) {
return new type(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // must call super() first
this.breed = breed;
}
speak() {
return `${this.name} barks`; // override
}
fetch() {
return `${this.name} fetches`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} meows`;
}
}
// Polymorphism
const animals = [new Dog("Rex", "Lab"), new Cat("Whiskers")];
animals.forEach(a => console.log(a.speak()));
// Rex barks
// Whiskers meows
// instanceof checks
const dog = new Dog("Buddy", "Poodle");
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor.name); // "Dog"
// Mixins (multiple inheritance alternative)
const Walker = (Base) => class extends Base {
walk() { return `${this.name} walks`; }
};
class Robot extends Walker(Animal) {}Prototypen
JavaScript verwendet prototypale Vererbung – Objekte erben von anderen Objekten über eine Prototype-Kette. __proto__ ist veraltet; verwenden Sie Object.getPrototypeOf/setPrototypeOf. Klassen sind syntaktischer Zucker über dieses System. Das Modifizieren eingebauter Prototypen (Array.prototype) ist gefährlich – es kann Code brechen. Bevorzugen Sie Komposition über tiefe Vererbung.
// Every object has a prototype (chain of inheritance)
const obj = {};
console.log(obj.__proto__); // Object.prototype
console.log(Object.getPrototypeOf(obj)); // preferred
// Constructor function (pre-class syntax)
function OldPerson(name) {
this.name = name;
}
OldPerson.prototype.greet = function() {
return `Hi, ${this.name}`;
};
const p = new OldPerson("Alice");
console.log(p.greet()); // Hi, Alice
// Prototype chain
// p -> OldPerson.prototype -> Object.prototype -> null
// Adding to prototype (affects all instances)
Array.prototype.last = function() {
return this[this.length - 1];
};
console.log([1, 2, 3].last()); // 3
// Object.create (prototypal inheritance)
const animal = { type: "unknown" };
const dog = Object.create(animal);
dog.type = "dog";
console.log(dog.type); // "dog" (own property)
// Check own vs inherited
console.log(dog.hasOwnProperty("type")); // true
console.log("type" in dog); // true (includes inherited)
// Get prototype chain
let proto = Object.getPrototypeOf(dog);
while (proto) {
console.log(proto);
proto = Object.getPrototypeOf(proto);
}Fehlerbehandlung
Try / Catch / Finally
try/catch/finally behandelt Ausnahmen. catch bindet das Fehlerobjekt (das .message und .stack hat). Verwenden Sie instanceof, um spezifische Fehlertypen unterschiedlich zu behandeln. Werfen Sie unbekannte Fehler immer erneut, nachdem Sie erwartete behandelt haben. Erstellen Sie benutzerdefinierte Fehler durch Erweitern von Error für anwendungsspezifische Fehlerbehandlung.
try {
const result = JSON.parse('{"invalid json"');
} catch (error) {
console.error("Parse error:", error.message);
console.error("Stack:", error.stack);
} finally {
console.log("Always runs");
}
// Catch specific error types
try {
const data = JSON.parse(input);
if (!data.name) throw new TypeError("name is required");
} catch (error) {
if (error instanceof SyntaxError) {
console.log("JSON syntax error");
} else if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else {
throw error; // re-throw unknown errors
}
}
// Error properties
const err = new Error("Something went wrong");
err.code = "CUSTOM_ERROR";
err.statusCode = 500;
throw err;
// Built-in error types
// Error, TypeError, RangeError, ReferenceError
// SyntaxError, URIError, EvalErrorBenutzerdefinierte Fehler
Erweitern Sie Error, um benutzerdefinierte Fehlertypen mit zusätzlichem Kontext (Felder, Codes) zu erstellen. Setzen Sie immer this.name auf den Klassennamen. Verwenden Sie instanceof, um spezifische Fehlertypen abzufangen. Fehler-Verkettung (ES2022 { cause }) erhält den ursprünglichen Fehler zum Debuggen. Eine gute Fehler-Hierarchie macht Fehlerbehandlung präzise.
// Custom error class
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = "DatabaseError";
this.query = query;
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError("email", "Email is required");
}
if (!user.email.includes("@")) {
throw new ValidationError("email", "Invalid email format");
}
}
// Handling custom errors
try {
validateUser({ name: "Alice" });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed: ${error.field} - ${error.message}`);
} else if (error instanceof DatabaseError) {
console.log(`DB error in query: ${error.query}`);
} else {
console.log("Unexpected error:", error);
}
}
// Error chaining (ES2022)
try {
throw new Error("Original cause");
} catch (cause) {
throw new Error("Failed to process", { cause });
}Async & Promises
Callbacks
Callbacks sind Funktionen, die übergeben werden, um später aufgerufen zu werden. Die Error-First-Konvention (err, data) ist Standard in Node.js. Verschachtelte Callbacks erstellen 'Callback Hell' – tief verschachtelter, schwer lesbarer Code. Promises und async/await lösen dies. setTimeout/setInterval sind häufige callback-basierte APIs.
// Callback pattern (old way)
function fetchData(url, callback) {
setTimeout(() => {
callback(null, { data: "result" });
}, 1000);
}
// Callback with error-first convention (Node.js style)
fetchData("/api", (error, data) => {
if (error) {
console.error(error);
return;
}
console.log(data);
});
// Callback hell (pyramid of doom)
fetchUser(userId, (err, user) => {
if (err) return handleError(err);
fetchPosts(user.id, (err, posts) => {
if (err) return handleError(err);
fetchComments(posts[0].id, (err, comments) => {
if (err) return handleError(err);
// deeply nested...
});
});
});
// Event listeners are callbacks
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
});
// setTimeout / setInterval
setTimeout(() => console.log("After 1s"), 1000);
const id = setInterval(() => console.log("tick"), 1000);
clearInterval(id); // stopPromises
Promises repräsentieren zukünftige Werte mit drei Zuständen: pending, fulfilled, rejected. .then() behandelt Erfolg, .catch() behandelt Fehler, .finally() läuft immer. Promise.all() wartet auf alle (fail-fast); allSettled() wartet auf alle (schlägt nie fehl); race() gibt erste settled zurück; any() gibt erste erfolgreiche zurück.
// Creating a promise
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Data received");
} else {
reject(new Error("Failed"));
}
});
// Consuming a promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Done"));
// Chaining
fetch("/api/users")
.then(response => response.json())
.then(users => users.filter(u => u.active))
.then(active => console.log(active))
.catch(err => console.error("Error:", err));
// Promise.all - wait for all (fails if any fails)
Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]).then(([users, posts]) => {
console.log(users, posts);
});
// Promise.allSettled - wait for all (never fails)
Promise.allSettled([p1, p2]).then(results => {
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.log(r.reason);
});
});
// Promise.race - first to settle wins
Promise.race([p1, p2]).then(first => console.log(first));
// Promise.any - first to succeed
Promise.any([p1, p2]).then(first => console.log(first));Async / Await
async/await ist syntaktischer Zucker über Promises – macht Async-Code synchron aussehend. 'await' pausiert die Funktion, bis das Promise settled. Wickeln Sie await immer in try/catch für Fehlerbehandlung. Verwenden Sie Promise.all() für parallele Operationen (schneller als sequentielles await in einer Schleife). Top-level await funktioniert in ES-Modulen.
// async function always returns a Promise
async function fetchData() {
// await pauses until the promise resolves
const response = await fetch("/api/users");
const data = await response.json();
return data;
}
// Error handling with try/catch
async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return await res.json();
} catch (error) {
console.error("Failed:", error);
return null;
}
}
// Sequential vs parallel
async function sequential() {
const a = await fetch("/api/a"); // waits for a
const b = await fetch("/api/b"); // then waits for b
return [a, b];
}
async function parallel() {
const [a, b] = await Promise.all([ // both at once
fetch("/api/a"),
fetch("/api/b")
]);
return [a, b];
}
// Top-level await (ES2022, in modules)
// const data = await fetch("/api").then(r => r.json());
// Iterating with async
async function processUrls(urls) {
for (const url of urls) {
const data = await fetch(url);
console.log(data);
}
}
// Promise.all with map (parallel)
async function fetchAll(urls) {
return Promise.all(urls.map(url => fetch(url)));
}DOM-Manipulation
Elemente auswählen & modifizieren
querySelector/querySelectorAll (CSS-Selektoren) sind die moderne Methode, Elemente auszuwählen. textContent ist sicherer als innerHTML (verhindert XSS). classList bietet add/remove/toggle/contains für Klassen. dataset greift auf data-*-Attribute zu. Sanitisieren Sie immer Benutzereingaben, bevor Sie innerHTML setzen.
// Selecting elements
const el = document.querySelector("#app"); // first match
const all = document.querySelectorAll(".item"); // all matches (NodeList)
const byId = document.getElementById("app");
const byClass = document.getElementsByClassName("item"); // HTMLCollection
// Modifying content
el.textContent = "Hello"; // text only (safe from XSS)
el.innerHTML = "<b>Bold</b>"; // HTML (XSS risk with user input!)
el.innerText = "Visible text"; // respects CSS visibility
// Attributes
el.setAttribute("data-id", "123");
const id = el.getAttribute("data-id");
el.removeAttribute("disabled");
el.dataset.id; // access data-* attributes
el.id = "new-id";
el.className = "active highlighted";
el.classList.add("active");
el.classList.remove("old");
el.classList.toggle("hidden");
el.classList.contains("active");
// Styles
el.style.color = "red";
el.style.backgroundColor = "blue"; // camelCase
el.style.cssText = "color: red; font-size: 16px;";
// Creating elements
const div = document.createElement("div");
div.textContent = "New element";
div.classList.add("box");
document.body.appendChild(div);
document.body.prepend(div); // add to beginning
document.body.insertBefore(div, referenceEl);Events
addEventListener wird gegenüber on<event>-Properties bevorzugt (erlaubt mehrere Listener). Event-Delegation (Lauschen auf einem Eltern-Element) ist effizient für dynamisch hinzugefügte Elemente. e.target ist das, worauf geklickt wurde; e.currentTarget ist das Element mit dem Listener. preventDefault() stoppt Standardverhalten; stopPropagation() stoppt Bubbling.
// Add event listener
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
console.log("Current target:", event.currentTarget);
});
// Common events
// click, dblclick, mousedown, mouseup, mousemove
// keydown, keyup, keypress
// submit, change, input, focus, blur
// load, DOMContentLoaded, resize, scroll
// Event object properties
input.addEventListener("keydown", (e) => {
console.log(e.key); // "Enter", "a", etc.
console.log(e.code); // "KeyA", "Enter"
console.log(e.ctrlKey); // true if Ctrl held
e.preventDefault(); // stop default behavior
e.stopPropagation(); // stop bubbling
});
// Event delegation (efficient for many elements)
document.addEventListener("click", (e) => {
if (e.target.matches(".delete-btn")) {
const id = e.target.dataset.id;
deleteItem(id);
}
});
// Custom events
const customEvent = new CustomEvent("userLogin", {
detail: { userId: 123 }
});
element.dispatchEvent(customEvent);
element.addEventListener("userLogin", (e) => {
console.log("User logged in:", e.detail.userId);
});
// Remove listener (must be same function reference)
const handler = () => console.log("click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);Module & JSON
ES-Module
ES-Module (import/export) sind der moderne Standard, unterstützt in Browsern und Node.js. Default-Export (einer pro Modul) vs. benannte Exports (mehrere). Dynamischer import() ermöglicht Lazy Loading. Module sind immer im Strict Mode und haben ihren eigenen Scope. Verwenden Sie type='module' in HTML-Script-Tags.
// math.js - exporting
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }
// Default export (one per module)
export default function greet(name) {
return `Hello, ${name}`;
}
// main.js - importing
import greet from "./math.js"; // default
import { add, PI } from "./math.js"; // named
import * as math from "./math.js"; // namespace
import { add as plus } from "./math.js"; // rename
import greet, { add } from "./math.js"; // default + named
// Dynamic import (returns Promise)
const module = await import("./math.js");
console.log(module.add(2, 3));
// Conditional/dynamic loading
if (featureEnabled) {
const { default: Feature } = await import("./feature.js");
new Feature();
}
// Re-export
export { add } from "./math.js";
export * from "./utils.js";
// In HTML
// <script type="module" src="app.js"></script>
// Modules are deferred and in strict mode by defaultJSON
JSON.stringify() konvertiert JS-Werte zu JSON-Strings; JSON.parse() kehrt um. Verwenden Sie Replacer/Reviver zum Filtern oder Transformieren während der Konvertierung. JSON unterstützt keine Funktionen, undefined, Dates (werden zu Strings) oder Zirkelbezüge. fetch().json() parst JSON-Antworten automatisch. Wickeln Sie JSON.parse immer in try/catch für nicht vertrauenswürdige Eingaben.
// JSON is the standard data interchange format
// JSON supports: string, number, boolean, null, array, object
// JavaScript object to JSON string
const user = { name: "Alice", age: 30, active: true };
const jsonStr = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'
// Pretty print
const pretty = JSON.stringify(user, null, 2);
// {
// "name": "Alice",
// "age": 30,
// "active": true
// }
// JSON string to JavaScript object
const parsed = JSON.parse('{"name":"Bob","age":25}');
console.log(parsed.name); // "Bob"
// Replacer function (filter/transform during stringify)
const filtered = JSON.stringify(user, (key, value) => {
if (key === "age") return undefined; // exclude
return value;
});
// Reviver function (transform during parse)
const data = JSON.parse(jsonStr, (key, value) => {
if (key === "date") return new Date(value);
return value;
});
// Fetch JSON from API
async function getUsers() {
const res = await fetch("/api/users");
return res.json(); // parses JSON automatically
}
// JSON limitations
// - No functions, undefined, or dates
// - No circular references (throws error)
// - Keys must be strings (with quotes)Datum & Uhrzeit
JavaScript Date ist notorisch unhandlich – Monate sind 0-indexiert (Januar = 0), Tage sind 1-indexiert. Date-Objekte sind veränderbar. toISOString() gibt UTC; toLocaleString() gibt lokale Zeit. Für ernsthafte Datumsarbeit verwenden Sie eine Bibliothek wie date-fns oder dayjs. Intl.DateTimeFormat bietet locale-bewusste Formatierung.
// Create dates
const now = new Date();
const specific = new Date("2024-01-15T10:30:00");
const fromMs = new Date(1705315200000);
const fromParts = new Date(2024, 0, 15, 10, 30); // month is 0-indexed!
// Get components
console.log(now.getFullYear()); // 2024
console.log(now.getMonth()); // 0-11 (January = 0!)
console.log(now.getDate()); // 1-31
console.log(now.getDay()); // 0-6 (Sunday = 0)
console.log(now.getHours()); // 0-23
console.log(now.getTime()); // milliseconds since epoch
// Set components
now.setFullYear(2025);
now.setMonth(11); // December
// Formatting
console.log(now.toISOString()); // "2024-01-15T10:30:00.000Z"
console.log(now.toLocaleDateString()); // "1/15/2024" (locale)
console.log(now.toLocaleString()); // "1/15/2024, 10:30:00 AM"
// Timestamps
const start = Date.now(); // milliseconds since epoch
// ... do work ...
console.log(`Took ${Date.now() - start}ms`);
// Intl for formatting (modern)
const formatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric", month: "long", day: "numeric"
});
console.log(formatter.format(now)); // "2024年1月15日"ES6+-Features
let, const & Block-Scoping
Bevorzugen Sie standardmäßig const, let bei Neuzuweisung, und vermeiden Sie var vollständig. const verhindert Neuzuweisung, aber Objekte/Arrays sind noch veränderbar. let und const sind block-scoped und leben in der Temporal Dead Zone vor der Deklaration (anders als var, das als undefined gehoisted wird). Dies verhindert viele subtile Bugs.
// var is function-scoped (hoisted, leaks out of blocks)
// let and const are block-scoped (stay inside {})
{
var x = 1; // accessible outside the block
let y = 2; // block-scoped
const z = 3; // block-scoped, cannot reassign
}
console.log(x); // 1
// console.log(y); // ReferenceError
// const prevents reassignment, NOT mutation
const arr = [1, 2, 3];
arr.push(4); // OK — mutating the array
// arr = [5]; // TypeError — reassigning const
// Temporal Dead Zone: let/const can't be used before declaration
// console.log(a); // ReferenceError (not undefined like var)
let a = 10;Arrow Functions & this
Arrow Functions sind prägnant und erben 'this' vom einschließenden Scope – perfekt für Callbacks und Methoden, die das äußere 'this' benötigen. Aber sie können nicht als Konstruktoren verwendet werden und haben kein 'arguments'-Objekt. Verwenden Sie keine Arrow Functions für Objektmethoden, wenn 'this' auf das Objekt verweisen soll (verwenden Sie stattdessen reguläre Methoden).
// Arrow functions: concise syntax, lexically bound 'this'
const add = (a, b) => a + b;
const square = x => x * x; // single param, no parens
const greet = name => `Hello, ${name}`; // template literal
const noop = () => {}; // no params, empty body
// Returning an object literal needs parens
const makeUser = (name, age) => ({ name, age });
// Arrow functions DON'T have their own 'this' — they inherit it
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 'this' is the Counter instance (lexical)
console.log(this.count);
}, 1000);
}
// Regular function would lose 'this' (it'd be window/undefined)Destructuring-Zuweisung
Destructuring extrahiert Werte aus Objekten/Arrays in Variablen in einer Zeile – sauberer als manueller Eigenschaftszugriff. Objekt-Destructuring verwendet { key }, Array-Destructuring verwendet [index]. Unterstützt Umbenennung (key: alias), Standards (key = default), Rest (...rest) und verschachtelte Patterns. Stark verwendet in React-Props und Funktionsparametern.
// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;
console.log(name, age); // Alice 30
// Rename and default values
const { name: fullName, country = "USA" } = user;
console.log(fullName, country); // Alice USA
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1 2 [3,4,5]
// Swap variables (no temp needed!)
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// Destructuring in function parameters
function greet({ name, greeting = "Hello" }) {
console.log(`${greeting}, ${name}`);
}
greet({ name: "Bob" }); // Hello, Bob
// Nested destructuring
const { data: { results } } = response;Spread & Rest-Operatoren
Der ...-Operator ist 'Spread' beim Expandieren (in Arrays/Objekten/Aufrufen) und 'Rest' beim Sammeln (in Parametern/Destructuring). Spread erstellt flache Kopien und merged Objekte (spätere Schlüssel überschreiben frühere). Rest-Parameter ersetzen das alte 'arguments'-Objekt und sind echte Arrays. Beide sind unerlässliche moderne JS-Idiome.
// Spread (...) expands iterables into individual elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5] (copy + append)
const merged = [...arr1, ...arr2];
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 } (shallow copy)
const updated = { ...obj1, b: 99 }; // override b: { a: 1, b: 99 }
// Rest (...) collects multiple elements into an array
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
// Spread in function calls
Math.max(...[1, 5, 3]); // 5 (same as Math.max(1, 5, 3))Template-Literale & getaggte Templates
Template-Literale (Backticks) unterstützen mehrzeilige Strings und ${}-Interpolation – viel sauberer als String-Verkettung. Getaggte Templates lassen eine Funktion die literalen Teile und interpolierten Werte verarbeiten und ermöglichen benutzerdefinierte Formatierung, Sanitisierung (z. B. HTML-Escaping) oder i18n. Beliebt in styled-components und graphql-tag.
// Template literals: backticks, multi-line, interpolation
const name = "Alice";
const msg = `Hello ${name},
this spans
multiple lines`;
// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
console.log(`Total: ${(price * (1 + tax)).toFixed(2)}`); // Total: 21.59
// Nested template literals
const items = ["apple", "banana"];
const html = `
<ul>
${items.map(i => `<li>${i}</li>`).join("")}
</ul>
`;
// Tagged templates: function processes the literal
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `**${values[i]}**` : ""), "");
}
const result = highlight`Name: ${name}, Age: ${30}`;
// "Name: **Alice**, Age: **30**"Optional Chaining & Nullish Coalescing
Optional Chaining (?.) short-circuitet zu undefined, wenn irgendein Teil der Kette null/undefined ist – eliminiert umständliche manuelle Prüfungen. Nullish Coalescing (??) bietet Defaults NUR für null/undefined, anders als ||, das auch 0, '' und false überschreibt. Zusammen behandeln sie die häufigsten 'fehlende Daten'-Patterns sicher. Verfügbar seit ES2020.
// Optional chaining (?.): safely access nested properties
const user = { profile: { name: "Alice" } };
console.log(user?.profile?.name); // "Alice"
console.log(user?.settings?.theme); // undefined (no error!)
console.log(user?.profile?.address?.city); // undefined
// Without ?., you'd write:
// user && user.profile && user.profile.name
// Optional method calls
const result = obj?.method?.();
const firstChar = str?.[0];
// Nullish coalescing (??): default only for null/undefined
const theme = user.settings?.theme ?? "light"; // "light"
const count = 0 ?? 10; // 0 (NOT 10 — ?? keeps falsy but valid values)
const name = "" ?? "Anonymous"; // "" (empty string is kept!)
// vs || which treats all falsy values (0, "", false) as missing
const count2 = 0 || 10; // 10 (probably not what you want!)Events & Event-Behandlung
addEventListener-Grundlagen
addEventListener ist die moderne Methode, Events zu binden – sie erlaubt mehrere Handler, unterstützt Event-Delegation und bietet Optionen wie once, passive und capture. Behalten Sie immer eine Referenz auf den Handler, wenn Sie ihn später entfernen müssen (anonyme Funktionen können nicht entfernt werden). Das Event-Objekt trägt target, currentTarget, preventDefault() und stopPropagation().
// Modern event binding (preferred over onclick=)
const button = document.querySelector("#myBtn");
button.addEventListener("click", function(event) {
console.log("Clicked!", event.target);
event.preventDefault(); // stop default action (e.g., form submit)
});
// Multiple listeners can be attached
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);
// Remove a specific listener (must be same reference)
button.removeEventListener("click", handler1);
// Common event types
// "click", "dblclick", "mousedown", "mouseup", "mousemove"
// "keydown", "keyup", "keypress"
// "submit", "change", "input", "focus", "blur"
// "load", "DOMContentLoaded", "resize", "scroll"
// Once option: auto-remove after first trigger
button.addEventListener("click", handler, { once: true });Event-Delegation
Event-Delegation hängt einen Listener an ein Eltern-Element, das Events von allen Kindern über Event-Bubbling behandelt. Verwenden Sie event.target.matches(selector) zum Filtern. Dies ist viel effizienter als an jedes Kind zu binden und behandelt automatisch dynamisch hinzugefügte Elemente. Der Kompromiss: Das Eltern-Element muss ein gemeinsamer Vorfahre sein, der immer existiert.
// Delegate events to a parent instead of each child
// Efficient for dynamically added elements
const list = document.querySelector("#item-list");
list.addEventListener("click", (event) => {
// event.target is the actual element clicked
if (event.target.matches("li.item")) {
console.log("Clicked:", event.target.textContent);
event.target.classList.toggle("selected");
}
});
// Now dynamically added items automatically work
list.insertAdjacentHTML("beforeend", "<li class='item'>New Item</li>");
// Benefits:
// 1. One listener instead of many (memory efficient)
// 2. Works for elements added after binding
// 3. No need to re-bind when DOM changesEvent-Propagation (Bubbling & Capturing)
Events propagieren in drei Phasen: Capturing (top-down), Target und Bubbling (bottom-up, Standard). Die meisten Handler laufen in der Bubbling-Phase. stopPropagation() verhindert, dass das Event Eltern-Elemente erreicht; stopImmediatePropagation() stoppt auch andere Handler auf demselben Element. Verwenden Sie Capturing (drittes Argument true) für Handler, die vor Kind-Handlern laufen müssen.
<div id="outer">
<div id="inner">
<button id="btn">Click</button>
</div>
</div>
// Events flow in three phases:
// 1. Capturing: top -> target (window -> document -> ... -> target)
// 2. Target: at the target element
// 3. Bubbling: target -> top (default phase most handlers run in)
// Bubbling (default): child fires first, then parents
btn.addEventListener("click", () => console.log("button"));
inner.addEventListener("click", () => console.log("inner"));
outer.addEventListener("click", () => console.log("outer"));
// Click button logs: button -> inner -> outer
// stopPropagation: prevent bubbling to parents
btn.addEventListener("click", (e) => {
e.stopPropagation();
console.log("only button");
});
// Capturing phase (third arg = true)
outer.addEventListener("click", handler, true); // runs during capture
// stopImmediatePropagation: stop other handlers on SAME element tooBenutzerdefinierte Events
CustomEvent lässt Sie anwendungsspezifische Events mit Nutzdaten in der 'detail'-Eigenschaft erstellen. Kombiniert mit dispatchEvent ermöglicht dies ein Pub/Sub-Pattern zur Entkopplung von Komponenten – Module kommunizieren ohne direkte Referenzen. Verwenden Sie eine Namenskonvention wie 'namespace:action', um Kollisionen zu vermeiden. Dies ist die Grundlage vieler Custom-Element-Frameworks.
// Create and dispatch custom events
const event = new CustomEvent("userLoggedIn", {
detail: { userId: 42, name: "Alice" },
bubbles: true, // allow bubbling
});
document.dispatchEvent(event);
// Listen for the custom event
document.addEventListener("userLoggedIn", (e) => {
console.log("User logged in:", e.detail.userId, e.detail.name);
});
// Practical: decoupled communication between components
class Cart {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
window.dispatchEvent(new CustomEvent("cart:updated", {
detail: { count: this.items.length }
}));
}
}
// Any module can listen without knowing about Cart internals
window.addEventListener("cart:updated", (e) => {
updateBadge(e.detail.count);
});Tastatur- & Formular-Events
Tastatur-Events geben e.key (logische Taste wie 'a', 'Enter') und e.code (physische Taste wie 'KeyA'). Verwenden Sie e.key für die meiste Logik. Formular-Submit benötigt immer preventDefault(), um Seiten-Reload zu stoppen. FormData + Object.fromEntries sammelt Formulardaten einfach. 'input' feuert kontinuierlich; 'change' feuert, wenn das Feld den Fokus verliert – wählen Sie basierend darauf, wann Sie Validierung wollen.
// Keyboard events
document.addEventListener("keydown", (e) => {
console.log(e.key, e.code); // e.key="a", e.code="KeyA"
if (e.key === "Escape") closeModal();
if (e.ctrlKey && e.key === "s") { e.preventDefault(); save(); }
});
// Form events
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
e.preventDefault(); // stop page reload
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data); // { username: "...", email: "..." }
});
// Input validation on change/blur
const emailInput = document.querySelector("#email");
emailInput.addEventListener("blur", () => {
if (!emailInput.value.includes("@")) {
emailInput.setCustomValidity("Enter a valid email");
} else {
emailInput.setCustomValidity("");
}
});
// Change vs input:
// 'input' fires on every keystroke; 'change' fires on blurFetch API & AJAX
Basis-fetch (GET)
fetch() ist der moderne Ersatz für XMLHttpRequest – Promise-basiert und sauberer. Entscheidend: fetch lehnt nur bei Netzwerkfehlern ab, NICHT bei HTTP-Fehlerstatus (404, 500). Prüfen Sie immer response.ok (Status 200-299) vor dem Parsen. Verwenden Sie async/await für lesbaren sequenziellen Code. response.json() ist async, weil es den Body-Stream liest.
// fetch() returns a Promise; .json() returns another Promise
fetch("https://api.example.com/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json(); // or .text(), .blob(), .arrayBuffer()
})
.then(data => console.log(data))
.catch(error => console.error("Fetch failed:", error));
// fetch does NOT reject on HTTP errors (404, 500) — only on network
// failures. You MUST check response.ok manually.
// Same with async/await (preferred)
async function getUsers() {
try {
const res = await fetch("https://api.example.com/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error(err);
}
}POST, PUT, DELETE mit fetch
Das fetch-Optionsobjekt konfiguriert Methode, Header und Body. Für JSON setzen Sie Content-Type: application/json und JSON.stringify den Body. Für Datei-Uploads verwenden Sie FormData (setzen Sie Content-Type nicht manuell – der Browser fügt die Multipart-Grenze hinzu). PUT ersetzt eine Ressource vollständig; PATCH aktualisiert sie teilweise.
// POST: create a resource
async function createUser(data) {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return res.json();
}
createUser({ name: "Alice", email: "[email protected]" });
// PUT: update a resource (full replace)
await fetch("/api/users/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice Updated" }),
});
// DELETE
await fetch("/api/users/1", { method: "DELETE" });
// Sending form data (file uploads)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: formData });
// Don't set Content-Type for FormData — browser sets it with boundaryRequest-Header & Auth
Header tragen Metadaten und Auth-Token. Bearer-Token (JWT) gehen in den Authorization-Header. Einige Header sind 'verboten' (browser-kontrolliert) wie Host und Cookie. CORS wird vom Browser erzwungen, nicht vom Server – Sie können es aus Client-JS nicht umgehen; der Server muss Access-Control-Allow-Origin senden. Preflight-OPTIONS-Anfragen erfolgen für nicht-einfache Anfragen.
// Custom headers (e.g., Bearer token auth)
async function fetchWithAuth(url, options = {}) {
const token = localStorage.getItem("authToken");
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
...options.headers, // allow overrides
},
});
return res;
}
// Common headers:
// Content-Type: application/json | application/x-www-form-urlencoded
// Authorization: Bearer <token> | Basic <base64>
// Accept: application/json
// X-Requested-With: XMLHttpRequest (anti-CSRF)
// CORS: browser blocks cross-origin requests unless the server
// returns Access-Control-Allow-Origin. fetch can't bypass CORS.AbortController (Anfragen abbrechen)
AbortController bricht fetch-Anfragen ab – unerlässlich für Search-as-you-type, Navigation weg oder Timeouts. Übergeben Sie signal an fetch; controller.abort() aufrufen löst einen AbortError aus. Ohne dies können veraltete Anfragen die UI in falscher Reihenfolge aktualisieren. AbortController funktioniert auch mit anderen Async-APIs und ist der Standard-Abbruchmechanismus in modernem JS.
// AbortController lets you cancel in-flight fetch requests
const controller = new AbortController();
async function fetchWithTimeout(url, ms = 5000) {
const timeout = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} catch (err) {
if (err.name === "AbortError") {
console.log("Request was aborted (timeout or cancel)");
} else {
throw err;
}
} finally {
clearTimeout(timeout);
}
}
// Cancel manually (e.g., user navigated away)
// controller.abort();
// Practical: cancel on new search input
searchInput.addEventListener("input", (e) => {
controller.abort(); // cancel previous request
fetchResults(e.target.value);
});Streaming-Antworten
response.body ist ein ReadableStream – Sie können Daten in Chunks verarbeiten, während sie ankommen, statt die gesamte Antwort im Speicher zu puffern. Dies ist unerlässlich für große Dateien, Streaming-Logs oder Echtzeitdaten. Verwenden Sie TextDecoder für Text-Streams. Die reader.read()-Schleife läuft bis done true ist. Streaming vermeidet Speicher-Spitzen bei großen Payloads.
// Read a large response in chunks (streaming)
async function streamJson(url) {
const res = await fetch(url);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process complete lines
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line
for (const line of lines) {
if (line.trim()) console.log(JSON.parse(line));
}
}
}
// Useful for: large files, NDJSON logs, real-time data feeds
// Processes data as it arrives instead of waiting for the full responseWeb Storage (LocalStorage & SessionStorage)
localStorage & sessionStorage-Grundlagen
localStorage persistiert unbegrenzt; sessionStorage wird geleert, wenn der Tab geschlossen wird. Beide speichern nur Strings – verwenden Sie JSON.stringify/parse für Objekte. Storage ist synchron und blockiert den Main Thread, vermeiden Sie also das Speichern großer Daten. In allen modernen Browsern verfügbar, kann aber im privaten Surfen deaktiviert sein. Kapazität ist ~5-10MB pro Origin.
// Both store strings only — JSON.stringify objects
// localStorage: persists until cleared (survives browser restart)
// sessionStorage: cleared when the tab closes
// localStorage API
localStorage.setItem("username", "Alice");
const name = localStorage.getItem("username"); // "Alice"
localStorage.removeItem("username");
localStorage.clear(); // remove ALL items
// sessionStorage API (same interface, tab-scoped)
sessionStorage.setItem("tempToken", "abc123");
// Storing objects (must serialize)
const prefs = { theme: "dark", lang: "en" };
localStorage.setItem("prefs", JSON.stringify(prefs));
const loaded = JSON.parse(localStorage.getItem("prefs"));
console.log(loaded.theme); // "dark"
// Check if storage exists (private mode may disable it)
if (typeof Storage !== "undefined") {
// localStorage/sessionStorage available
}Storage-Helper mit TTL & JSON
Web Storage hat keinen eingebauten Ablauf – dieser Wrapper fügt TTL (Time-to-Live) hinzu, indem er einen Ablaufzeitstempel zusammen mit dem Wert speichert. Dies ist das Standard-Pattern zum Cachen von API-Antworten oder Sitzungsdaten, die ablaufen sollen. Wickeln Sie Storage-Zugriff in Produktion immer in try/catch, da JSON.parse bei korrupten Daten werfen kann und Quota überschritten werden kann.
// A robust storage wrapper with expiration (TTL)
const store = {
set(key, value, ttlMs) {
const item = {
value: value,
expiry: ttlMs ? Date.now() + ttlMs : null,
};
localStorage.setItem(key, JSON.stringify(item));
},
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (item.expiry && Date.now() > item.expiry) {
localStorage.removeItem(key);
return null; // expired
}
return item.value;
},
remove(key) { localStorage.removeItem(key); },
};
// Usage: cache data for 5 minutes
store.set("userData", { name: "Alice" }, 5 * 60 * 1000);
console.log(store.get("userData")); // { name: "Alice" }
// After 5 min: returns null and cleans upStorage-Events (Tab-übergreifende Synchronisation)
Das storage-Event ist ein eingebauter Tab-übergreifender Kommunikationskanal – wenn ein Tab localStorage modifiziert, empfangen alle anderen Tabs derselben Origin das Event (aber nicht der ursprüngliche Tab). Dies ermöglicht das Synchronisieren von Zustand wie Login/Logout, Warenkorb-Updates oder Theme-Änderungen über Tabs ohne WebSockets. Das Event inkludiert key, oldValue, newValue und url.
// The 'storage' event fires in OTHER tabs when storage changes
// (not in the tab that made the change)
window.addEventListener("storage", (event) => {
console.log("Key changed:", event.key);
console.log("Old value:", event.oldValue);
console.log("New value:", event.newValue);
console.log("URL:", event.url);
if (event.key === "cart") {
updateCartDisplay(JSON.parse(event.newValue));
}
});
// Practical: sync logout across tabs
// Tab A: localStorage.setItem("logout", Date.now());
// Tab B: receives storage event -> redirects to login
// Practical: broadcast a message to all tabs
function broadcast(type, data) {
localStorage.setItem("broadcast", JSON.stringify({ type, data, t: Date.now() }));
}
window.addEventListener("storage", (e) => {
if (e.key === "broadcast") {
const msg = JSON.parse(e.newValue);
handleMessage(msg);
}
});IndexedDB (Große strukturierte Speicherung)
IndexedDB ist eine leistungsstarke NoSQL-Datenbank im Browser – asynchron, transaktional und in der Lage, deutlich mehr Daten zu speichern als localStorage (Hunderte MB). Es unterstützt Indizes, Cursor und Transaktionen. Die rohe API ist callback-basiert und ausführlich; das 'idb' npm-Paket bietet einen sauberen Promise-basierten Wrapper. Verwende IndexedDB für Offline-First-Apps, große Caches oder komplexe clientseitige Daten.
// IndexedDB: async, transactional NoSQL store for large data
// Capacity: hundreds of MB to GB (far more than localStorage's 5MB)
// Open a database
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create an object store (like a table) with a key path
if (!db.objectStoreNames.contains("users")) {
db.createObjectStore("users", { keyPath: "id" });
}
};
request.onsuccess = (e) => {
const db = e.target.result;
// Add data in a transaction
const tx = db.transaction("users", "readwrite");
const store = tx.objectStore("users");
store.add({ id: 1, name: "Alice", age: 30 });
store.add({ id: 2, name: "Bob", age: 25 });
// Query
store.get(1).onsuccess = (e) => {
console.log(e.target.result); // { id: 1, name: "Alice", age: 30 }
};
};
// For easier use, consider the 'idb' library (Promise-based wrapper)Cookies vs. Speicher-Vergleich
Cookies werden mit jeder HTTP-Anfrage gesendet (verbrauchen Bandbreite) und sind der Standard für serverseitige Authentifizierung (Session-IDs, CSRF-Tokens). localStorage/sessionStorage sind nur clientseitig und speichern deutlich mehr Daten. IndexedDB ist für große strukturierte Daten. Wähle basierend auf: Braucht der Server es? (Cookie) Wie viel Daten? (Storage vs. IndexedDB) Wie lange? (Session vs. Local). Setze Secure, HttpOnly und SameSite bei sicherheitsrelevanten Cookies.
// COOKIES: sent with every HTTP request, ~4KB limit
document.cookie = "session=abc123; max-age=3600; path=/; Secure; SameSite=Strict";
console.log(document.cookie); // "session=abc123; theme=dark"
// Set cookie with attributes
document.cookie = "token=xyz; max-age=86400; Secure; HttpOnly; SameSite=Lax";
// Note: HttpOnly can't be set via JS (server-only for security)
// LOCAL STORAGE: ~5-10MB, NOT sent to server, synchronous
localStorage.setItem("theme", "dark");
// SESSION STORAGE: ~5MB, cleared on tab close
sessionStorage.setItem("draft", "work in progress");
// WHEN TO USE WHAT:
// Cookies -> auth tokens that the server needs to read, server-side sessions
// localStorage -> user preferences, cached data that persists
// sessionStorage -> temporary per-tab state (form drafts, wizard steps)
// IndexedDB -> large datasets, offline data, complex queriesTimer (setTimeout, setInterval)
setTimeout & setInterval
setTimeout führt einen Callback einmalig nach einer Verzögerung aus; setInterval führt ihn wiederholt aus. Beide geben eine ID zurück, die zum Abbrechen über clearTimeout/clearInterval verwendet wird. Timer sind nicht präzise – sie sind Mindestverzögerungen, abhängig vom Event-Loop, Tab-Sichtbarkeit (in Hintergrund-Tabs gedrosselt) und der Verfügbarkeit des Main-Threads. Verzögerungen unter 4ms können bei verschachtelten Timern auf 4ms begrenzt werden.
// setTimeout: run once after a delay (milliseconds)
const timeoutId = setTimeout(() => {
console.log("Runs after 2 seconds");
}, 2000);
// Cancel before it fires
clearTimeout(timeoutId);
// setInterval: run repeatedly every interval
const intervalId = setInterval(() => {
console.log("Runs every 1 second");
}, 1000);
// Stop the interval
clearInterval(intervalId);
// Pass arguments to the callback
setTimeout((greeting, name) => {
console.log(`${greeting}, ${name}`);
}, 1000, "Hello", "Alice");
// NOTE: timers don't guarantee exact timing — they run after the
// minimum delay, but only when the call stack is empty (event loop)Rekursives setTimeout (Besser als setInterval)
Rekursives setTimeout wird gegenüber setInterval für wiederkehrende asynchrone Aufgaben bevorzugt, da es garantiert, dass der vorherige Aufruf beendet ist, bevor der nächste startet – keine überlappenden Ausführungen. Es erlaubt auch dynamische Intervalle (z.B. längeres Backoff bei Fehlern). setInterval kann Aufrufe stapeln, wenn der Handler länger als das Intervall braucht, was zu Performance-Problemen führt.
// setInterval problems: doesn't wait for the previous call to finish
// If a call takes longer than the interval, they can stack up.
// Better: recursive setTimeout — guarantees the gap between completions
function poll() {
fetch("/api/status")
.then(r => r.json())
.then(data => {
console.log(data);
setTimeout(poll, 1000); // schedule next AFTER this finishes
})
.catch(err => {
console.error(err);
setTimeout(poll, 5000); // retry with backoff on error
});
}
poll(); // start the loop
// This pattern: always waits for the previous call to complete
// before scheduling the next, avoiding overlap/stacking.requestAnimationFrame (Flüssige Animationen)
requestAnimationFrame (rAF) ist der korrekte Weg für visuelle Animationen – es synchronisiert mit dem Repaint-Zyklus des Browsers (~60fps), vermeidet unnötige Frames, wenn der Tab versteckt ist, und erzeugt flüssigere Animationen als setInterval. Verwende den Zeitstempel-Parameter für Delta-Time-Berechnungen, um die Animationsgeschwindigkeit bei unterschiedlichen Bildwiederholraten konsistent zu halten. Breche immer mit cancelAnimationFrame ab, wenn fertig.
// requestAnimationFrame: syncs with the display refresh (~60fps)
// More efficient than setInterval for visual animations
function animate() {
element.style.left = parseInt(element.style.left || 0) + 2 + "px";
if (parseInt(element.style.left) < 300) {
requestAnimationFrame(animate); // schedule next frame
}
}
requestAnimationFrame(animate);
// Cancel if needed
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);
// Timestamp for delta-time calculations
let lastTime = 0;
function loop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
update(delta); // frame-rate independent movement
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// rAF pauses in background tabs (saves CPU/battery)Debounce & Throttle
Debounce wartet auf eine Pause bei den Aufrufen, bevor es ausgeführt wird (z.B. Suche erst 300ms, nachdem der Benutzer aufgehört hat zu tippen). Throttle begrenzt die Ausführung auf einmal pro Intervall (z.B. Scroll-Position höchstens alle 200ms aktualisieren). Beide verhindern Performance-Probleme durch hochfrequente Events. Debounce = 'schnelle Aufrufe zu einem zusammenfassen'; Throttle = 'die Aufruf-Rate begrenzen'.
// Debounce: delay execution until calls stop for N ms
// Use for: search input, window resize, button spam
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer); // reset the countdown
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce((query) => {
fetchResults(query);
}, 300);
input.addEventListener("input", (e) => search(e.target.value));
// Throttle: execute at most once per N ms
// Use for: scroll, mousemove, drag handlers
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const onScroll = throttle(() => {
console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", onScroll);Promisifiziertes setTimeout (delay)
Das Wrappen von setTimeout in ein Promise erzeugt eine saubere 'delay'-Funktion für async/await – deutlich lesbarer als Callback-Ketten. Dieses Pattern ermöglicht Retry-Logik mit exponentiellem Backoff (1s, 2s, 4s zwischen Versuchen warten), sequenzielle Animationen und Rate-Limiting. Der delay-Helfer ist eines der nützlichsten kleinen Utilities in modernem async JS.
// A promise-based delay for use with async/await
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function countdown() {
console.log("3...");
await delay(1000);
console.log("2...");
await delay(1000);
console.log("1...");
await delay(1000);
console.log("Go!");
}
countdown();
// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return await res.json();
throw new Error(`HTTP ${res.status}`);
} catch (err) {
if (i === retries - 1) throw err;
const wait = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1} in ${wait}ms`);
await delay(wait);
}
}
}Map, Set & WeakMap
Map (Schlüssel-Wert mit beliebigem Schlüsseltyp)
Map ist eine echte Schlüssel-Wert-Sammlung, bei der Schlüssel beliebigen Typs sein können (Objekte, Funktionen, Zahlen) – im Gegensatz zu Objects, die Schlüssel zu Strings umwandeln. Map bewahrt die Einfügereihenfolge, hat eine .size-Eigenschaft und ist direkt iterierbar. Verwende Map, wenn du nicht-string Schlüssel, häufige Hinzufügungen/Löschungen brauchst oder wenn die Sammlung kein Record/DTO ist. Verwende Object für Daten mit fester Form.
// Map: like an Object but keys can be ANY type (not just strings)
const map = new Map();
// Keys can be objects, functions, primitives
const objKey = { id: 1 };
map.set("name", "Alice");
map.set(objKey, "data for this object");
map.set(42, "numeric key");
map.set(true, "boolean key");
console.log(map.get("name")); // "Alice"
console.log(map.get(objKey)); // "data for this object"
console.log(map.size); // 4
console.log(map.has(42)); // true
// Iteration preserves insertion order
for (const [key, value] of map) {
console.log(key, value);
}
// Map vs Object:
// - Map keys can be any type; Object keys are strings/symbols
// - Map has .size; Object needs Object.keys().length
// - Map is iterable by default; Object needs Object.entries()
// - Map performs better for frequent add/removeSet (Eindeutige Werte)
Set speichert eindeutige Werte – perfekt für Deduplizierung und Mitgliedschaftstests. Das Konvertieren eines Arrays in ein Set und zurück ([...new Set(arr)]) ist die idiomatische Art, Duplikate zu entfernen. Set.has() ist O(1) gegenüber Array.includes() mit O(n), also verwende Set für große Sammlungen, die du häufig prüfst. Set hat kein map/filter – zuerst zu Array spreaden.
// Set: collection of unique values (no duplicates)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) { 1, 2, 3 }
console.log(set.size); // 3
set.add(4);
set.add(4); // ignored (already exists)
console.log(set.has(3)); // true
set.delete(2);
// Iterate (preserves insertion order)
for (const item of set) {
console.log(item);
}
// Common use cases:
// 1. Remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// 2. Check for membership (faster than Array.includes for large sets)
const validIds = new Set([101, 102, 103]);
if (validIds.has(userId)) { /* ... */ }
// 3. Set operations (union, intersection)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersect = new Set([...a].filter(x => b.has(x))); // {2,3}WeakMap & WeakSet (Speicher-sichere Referenzen)
WeakMap und WeakSet halten schwache Referenzen auf Objekte – wenn keine anderen Referenzen existieren, wird der Eintrag automatisch garbage-collected. Das verhindert Speicherlecks beim Zuordnen von Daten zu DOM-Elementen oder anderen Objekten. Schlüssel müssen Objekte sein. WeakMap/WeakSet sind nicht iterierbar und haben keine .size, da Einträge jederzeit während GC verschwinden können. Verwende sie für Caching/Metadaten, die an Objekt-Lebenszyklen gebunden sind.
// WeakMap: keys must be objects; references are "weak"
// (doesn't prevent garbage collection of the key object)
const weakMap = new WeakMap();
let user = { name: "Alice" };
weakMap.set(user, "metadata");
console.log(weakMap.get(user)); // "metadata"
user = null; // remove the only strong reference
// Now the object can be GC'd, and WeakMap entry disappears automatically
// Practical: attach data to DOM elements without memory leaks
const elementData = new WeakMap();
function cacheData(element, data) {
elementData.set(element, data);
}
// When the DOM element is removed, its data is auto-cleaned
// WeakSet: collection of objects, weakly held
const processed = new WeakSet();
function processOnce(obj) {
if (processed.has(obj)) return; // already done
processed.add(obj);
doWork(obj);
}
// WeakMap/WeakSet are NOT iterable (no .size, no for...of)Map-Iteration & Konvertierung
Maps sind in Einfügereihenfolge über for...of (standardmäßig entries), .keys(), .values() oder .forEach() iterierbar. Konvertiere zwischen Maps und Objects mit Object.entries() und Object.fromEntries(). Diese bidirektionale Konvertierung ist praktisch, wenn APIs einfache Objekte erwarten, du aber intern Maps Features nutzen willst. Beachte: Object-Schlüssel werden bei der Konvertierung zum Typ String.
const map = new Map([
["name", "Alice"],
["age", 30],
["city", "NYC"],
]);
// Iterate entries (default)
for (const [key, value] of map) {
console.log(`${key} = ${value}`);
}
// Iterate keys only
for (const key of map.keys()) {
console.log(key);
}
// Iterate values only
for (const value of map.values()) {
console.log(value);
}
// forEach (callback style)
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Convert to Array
const entries = [...map.entries()]; // [["name","Alice"], ["age",30], ...]
const keys = [...map.keys()]; // ["name", "age", "city"]
const values = [...map.values()]; // ["Alice", 30, "NYC"]
// Convert Object to Map and back
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
const objFromMap = Object.fromEntries(map);Wahl zwischen Map, Set, Object & Array
Wähle die richtige Sammlung: Arrays für geordnete Listen mit Duplikaten und Index-Zugriff; Objects für Datensätze mit fester Form und JSON; Maps für dynamische Schlüssel-Wert-Sammlungen mit beliebigem Schlüsseltyp; Sets für Eindeutigkeit und schnelles Nachschlagen. Die Verwendung der falschen Struktur führt zu ausführlichem Code und Performance-Problemen – z.B. Array.includes() in einer Schleife vs. Set.has().
// Decision guide:
// Use ARRAY when:
// - You need ordered data with duplicates
// - You need index-based access (arr[5])
// - You'll map/filter/reduce frequently
const todoList = ["buy milk", "walk dog"];
// Use OBJECT when:
// - You have a fixed known shape (like a user record)
// - You need JSON serialization (JSON.stringify)
// - Keys are strings and known at write time
const user = { name: "Alice", age: 30 };
// Use MAP when:
// - Keys are not strings (objects, functions, numbers)
// - You frequently add/remove key-value pairs
// - You need to iterate in insertion order
// - The collection size changes dynamically
const handlers = new Map();
handlers.set(buttonElement, () => onClick());
// Use SET when:
// - You need unique values (no duplicates)
// - You need fast membership testing (.has is O(1))
// - Order doesn't matter much
const seen = new Set();
if (!seen.has(url)) { seen.add(url); visit(url); }Generatoren & Iteratoren
Iteratoren & Symbol.iterator
Ein Iterator hat eine next()-Methode, die {value, done} zurückgibt. Ein Iterable implementiert Symbol.iterator, das einen Iterator zurückgibt. Eingebaute Iterables (Arrays, Strings, Maps, Sets) funktionieren mit for...of, Spread (...), Destructuring und Array.from(). Das Implementieren von Symbol.iterator lässt deine benutzerdefinierten Objekte nahtlos mit all diesen Sprach-Features arbeiten.
// An iterator is an object with a next() method returning {value, done}
function makeIterator(arr) {
let i = 0;
return {
next() {
return i < arr.length
? { value: arr[i++], done: false }
: { value: undefined, done: true };
},
};
}
const it = makeIterator(["a", "b", "c"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
// An iterable implements Symbol.iterator (returns an iterator)
const range = {
from: 1, to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
},
};
},
};
// Now range works with for...of, spread, etc.
console.log([...range]); // [1, 2, 3]Generator-Funktionen (function*)
Generator-Funktionen (function*) verwenden yield, um die Ausführung zu pausieren und fortzusetzen – sie produzieren Werte verzögert, einen nach dem anderen. Das ermöglicht unendliche Sequenzen, Lazy Evaluation und speichereffiziente Pipelines. Generatoren sind sowohl Iteratoren als auch Iterables. Jeder next()-Aufruf läuft bis zum nächsten yield (oder return). Sie sind die Grundlage von async-Generatoren und JS-Coroutine-Patterns.
// Generators: pause execution with 'yield', resume with next()
function* idGenerator() {
let id = 1;
while (true) {
yield id++; // pause here, return value, resume on next()
}
}
const gen = idGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
// Infinite sequence — only computes values on demand
// Generators are iterables (work with for...of)
function* take(n, iterable) {
for (const item of iterable) {
if (n-- <= 0) return;
yield item;
}
}
function* naturalNumbers() {
let n = 1;
while (true) yield n++;
}
const firstFive = [...take(5, naturalNumbers())];
console.log(firstFive); // [1, 2, 3, 4, 5]yield* (Delegation an einen anderen Generator)
yield* delegiert alle yields an ein anderes Iterable oder einen Generator – flacht verschachtelte Strukturen ab und komponiert Generatoren sauber. Es ist das JS-Äquivalent von Pythons 'yield from'. Die Werte des delegierten Generators werden einer nach dem anderen so ausgegeben, als wären sie Teil des äußeren Generators. Dies ist die Standardmethode, um Generatoren rekursiv zu komponieren.
// yield* delegates to another iterable/generator
function* inner() {
yield "a";
yield "b";
}
function* outer() {
yield 1;
yield* inner(); // delegates: yields "a", then "b"
yield* [10, 20]; // also works with arrays
yield 2;
}
console.log([...outer()]); // [1, "a", "b", 10, 20, 2]
// Practical: flatten nested structures
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4]], 5];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5]Generator send() via next(value)
Generatoren unterstützen bidirektionale Kommunikation: next(value) übergibt einen Wert (wird das Ergebnis des letzten yield-Ausdrucks), und throw(error) injiziert eine Ausnahme an der yield-Stelle. Das ermöglicht Coroutinen, Zustandsautomaten und die generatorbasierten async-Patterns, die frühe async/await-Implementierungen antrieben. Der erste next()-Aufruf kann keinen Wert übergeben (nichts vorhanden, das ihn empfangen könnte).
// next(value) passes a value INTO the generator (becomes yield's result)
function* conversation() {
const name = yield "What's your name?"; // receives via next()
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const talk = conversation();
console.log(talk.next().value); // "What's your name?"
console.log(talk.next("Alice").value); // "Hello Alice, how old are you?"
console.log(talk.next(30).value); // "Alice is 30 years old"
// throw(error) injects an exception at the yield point
function* safeGen() {
try {
yield "step 1";
} catch (e) {
console.log("Caught:", e.message);
yield "recovered";
}
}
const g = safeGen();
g.next(); // "step 1"
g.throw(new Error("oops")); // "Caught: oops" -> "recovered"Async-Generatoren
Async-Generatoren (async function*) kombinieren Generatoren mit async/await – jedes yield kann einen Wert nach einem await produzieren. Konsumiere sie mit for await...of. Ideal für paginierte APIs, Streaming-Daten oder jedes Szenario, in dem du Werte asynchron produzierst. Sie sind die moderne Art, Streaming-Daten in JS ohne Callbacks oder manuelles Promise-Chaining zu verarbeiten.
// async function* : yields promises, can use await
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.items.length === 0) break; // no more pages
yield data.items;
page++;
}
}
// Consume with for await...of
(async () => {
for await (const items of fetchPages("/api/products")) {
console.log(`Got ${items.length} items`);
renderItems(items);
}
console.log("All pages loaded");
})();
// Practical: stream data as it arrives
async function* streamLines(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) yield line;
}
}Proxy & Reflect
Proxy-Grundlagen (Operationen abfangen)
Proxy lässt dich fundamentale Operationen auf einem Objekt abfangen und anpassen – get, set, has, deleteProperty, ownKeys und mehr. Das Handler-Objekt definiert 'Traps' (wie Getter/Setter, aber für alle Eigenschaften). Anwendungsfälle: Validierung, Logging, Standardwerte, Zugriffskontrolle, reaktive Systeme (Vue 3 nutzt Proxy für Reaktivität). Gib bei set true zurück, um Erfolg im Strict Mode anzuzeigen.
// Proxy wraps an object and intercepts fundamental operations
const target = { name: "Alice", age: 30 };
const handler = {
get(obj, prop) {
console.log(`Reading ${prop}`);
return prop in obj ? obj[prop] : "N/A"; // default value
},
set(obj, prop, value) {
console.log(`Setting ${prop} = ${value}`);
if (prop === "age" && value < 0) return false; // validation
obj[prop] = value;
return true; // indicate success
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // logs "Reading name", returns "Alice"
proxy.age = 25; // logs "Setting age = 25"
// proxy.age = -5; // logs "Setting age = -5", rejected
console.log(proxy.unknown); // logs "Reading unknown", returns "N/A"Häufige Proxy-Anwendungsfälle
Proxies ermöglichen mächtiges Metaprogramming: negative Indizes, verzögerte Eigenschaftsberechnung, Read-Only-Ansichten, Validierung, Logging und reaktive Datenbindung. Vue 3s Reaktivitätssystem nutzt Proxy, um Eigenschaftszugriffe zu verfolgen und automatische Re-Renders auszulösen. Der Kompromiss ist ein kleiner Performance-Overhead, also verwende Proxies, wo die Abstraktion es wert ist, nicht für jedes Objekt.
// 1. Negative array indices (like Python)
function negativeArray(arr) {
return new Proxy(arr, {
get(target, prop) {
const idx = Number(prop);
if (Number.isInteger(idx)) {
return target[idx < 0 ? target.length + idx : idx];
}
return target[prop];
},
});
}
const arr = negativeArray(["a", "b", "c"]);
console.log(arr[-1]); // "c"
// 2. Auto-populating / lazy properties
const lazy = new Proxy({}, {
get(target, prop) {
if (!(prop in target)) {
target[prop] = expensiveCompute(prop); // compute on first access
}
return target[prop];
},
});
// 3. Read-only object
const readOnly = new Proxy(data, {
set() { throw new Error("This object is read-only"); },
deleteProperty() { throw new Error("Cannot delete"); },
});Reflect-API
Reflect stellt dieselben Operationen bereit, die Proxy-Traps abfangen – verwende es innerhalb von Traps, um sauber an das Standardverhalten weiterzuleiten. Reflect-Methoden geben Booleans (Erfolg/Misserfolg) zurück, anstatt zu werfen, was sie sicherer für bedingte Logik macht. Reflect.ownKeys gibt alle Schlüssel zurück (Strings UND Symbole), im Gegensatz zu Object.keys, das nur aufzählbare String-Schlüssel zurückgibt. Zusammen bilden Proxy und Reflect JS' Metaprogramming-Toolkit.
// Reflect provides default behavior for proxy traps and
// functional equivalents of object operations
const obj = { x: 1, y: 2 };
// Reflect.get / Reflect.set (instead of obj[prop])
console.log(Reflect.get(obj, "x")); // 1
Reflect.set(obj, "z", 3); // obj.z = 3
console.log(Reflect.has(obj, "x")); // true (like "x" in obj)
Reflect.deleteProperty(obj, "y"); // delete obj.y
console.log(Reflect.ownKeys(obj)); // ["x", "z"]
// In a Proxy, use Reflect to forward to the default behavior
const proxy = new Proxy({}, {
get(target, prop, receiver) {
console.log(`get ${prop}`);
return Reflect.get(target, prop, receiver); // default behavior
},
set(target, prop, value, receiver) {
console.log(`set ${prop}`);
return Reflect.set(target, prop, value, receiver);
},
});
// Reflect.construct: call a constructor with an array of args
const instance = Reflect.construct(Array, [1, 2, 3]);Reaktives Objekt mit Proxy (Vue-Stil)
Das ist die Kernidee hinter Vue 3s Reaktivität – ein Proxy fängt get ab (um zu verfolgen, welche Effects von einer Eigenschaft abhängen) und set (um diese Effects auszulösen, wenn sich die Eigenschaft ändert). Das ermöglicht deklarative UI-Updates: Du mutierst den Zustand und das Framework re-rendert automatisch. Proxies machten dies in ES6 möglich; frühere Frameworks (Vue 2) verwendeten Object.defineProperty mit Einschränkungen.
// A minimal reactive system using Proxy
function reactive(target) {
const subscribers = new Set();
const handler = {
get(obj, prop) {
track(prop); // record who reads this property
const value = Reflect.get(obj, prop);
return typeof value === "object" && value !== null
? reactive(value) // deeply reactive
: value;
},
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
trigger(prop); // notify subscribers
return result;
},
};
let currentEffect = null;
function track(prop) {
if (currentEffect) subscribers.add(currentEffect);
}
function trigger(prop) {
subscribers.forEach(fn => fn());
}
return new Proxy(target, handler);
}
const state = reactive({ count: 0 });
// When state.count changes, effects re-run automaticallyWeb Workers
Einen Web Worker erstellen
Web Workers führen JavaScript in einem separaten Hintergrund-Thread aus und ermöglichen echten Parallelismus ohne Blockierung der UI. Der Main-Thread und der Worker kommunizieren über postMessage (Daten werden kopiert/structured-cloned, nicht geteilt). Worker können nicht auf das DOM oder window-Objekt zugreifen – sie sind isoliert. Verwende Worker für CPU-intensive Aufgaben wie Bildverarbeitung, Parsen großer Dateien oder komplexe Berechnungen.
// main.js — workers run JS in a background thread
const worker = new Worker("worker.js");
// Send data to the worker
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });
// Receive results from the worker
worker.onmessage = (event) => {
console.log("Result from worker:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// Terminate when done (frees resources)
// worker.terminate();
// --- worker.js (separate file) ---
// self.onmessage = (event) => {
// const { command, data } = event.data;
// const result = heavyComputation(data);
// self.postMessage(result);
// };Inline-Worker (Blob-URL)
Inline-Worker erstellen einen Worker aus einem Code-String über eine Blob-URL – keine separate Datei nötig. Praktisch für Demos, kleine Utilities oder wenn dein Build-System separate Worker-Dateien nicht einfach verarbeiten kann. Vergiss nicht, die Object-URL zu widerrufen, um Speicherlecks zu vermeiden. Der Worker-Code ist ein String, also verlierst du Editor-Syntaxhervorhebung und Typprüfung – verwende es in Produktion mit Vorsicht.
// Create a worker from a string (no separate file needed)
const workerCode = `
self.onmessage = function(e) {
const result = e.data.map(x => x * x);
self.postMessage(result);
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage([1, 2, 3, 4]);
worker.onmessage = (e) => console.log(e.data); // [1, 4, 9, 16]
// Clean up the blob URL when done
// URL.revokeObjectURL(blob URL);
// Useful for: single-file demos, bundlers that inline workers,
// or when you can't serve a separate .js fileTransferable Objects (Zero-Copy)
Normalerweise kopiert postMessage Daten (Structured Clone), was bei großen Buffern langsam ist. Die Transfer-Liste (zweites Argument) ÜBERTRÄGT das Eigentum von ArrayBuffer/MessagePort/ImageBitmap an den Worker ohne Kopieren – nahezu augenblicklich unabhängig von der Größe. Der ursprüngliche Buffer wird detached (unbrauchbar). Verwende dies für große Datensätze, Bildverarbeitung oder Audio, um den Kopier-Overhead zu vermeiden.
// Transferable objects move data to a worker WITHOUT copying
// (the original becomes unusable) — much faster for large ArrayBuffers
// Create a large buffer
const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB
const view = new Float64Array(buffer);
view[0] = 3.14;
// Transfer the buffer (second arg = transfer list)
worker.postMessage({ data: buffer }, [buffer]);
// After transfer, 'buffer' is detached (length becomes 0)
console.log(buffer.byteLength); // 0 — ownership moved to worker
// Worker receives it normally:
// self.onmessage = (e) => {
// const buf = e.data.data; // full 10MB, no copy
// };
// Transferable types: ArrayBuffer, MessagePort, ImageBitmap
// Use for: large datasets, image data, audio buffersSharedArrayBuffer & Atomics
SharedArrayBuffer ermöglicht echtes Shared Memory zwischen Threads (kein Kopieren), und Atomics bietet threadsichere Operationen (add, load, store, compareExchange, wait/notify) darauf. Das ermöglicht hochperformante parallele Algorithmen in JS. Aufgrund von Spectre-Sicherheitsbedenken erfordert SharedArrayBuffer Cross-Origin-Isolation-HTTP-Header – ohne sie ist es in modernen Browsern deaktiviert. Verwende es für WASM-Interoperabilität und schwere parallele Berechnungen.
// SharedArrayBuffer: memory shared between main thread and workers
// (both can read/write simultaneously) — true shared memory
// const sharedBuffer = new SharedArrayBuffer(1024);
// const view = new Int32Array(sharedBuffer);
// worker.postMessage({ buffer: sharedBuffer });
// Atomics: thread-safe operations on SharedArrayBuffer
// const sharedArray = new Int32Array(sharedBuffer);
// Atomics.add(sharedArray, 0, 1); // atomic increment
// Atomics.load(sharedArray, 0); // atomic read
// Atomics.store(sharedArray, 0, 42); // atomic write
// Atomics.compareExchange(sharedArray, 0, 42, 99); // CAS
// Atomics.wait / notify: block and wake threads
// Worker:
// Atomics.wait(sharedArray, 0, 0); // block until index 0 != 0
// Main:
// Atomics.store(sharedArray, 0, 1);
// Atomics.notify(sharedArray, 0); // wake waiting workers
// NOTE: Requires cross-origin isolation headers:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corpWorker-Pool-Pattern
Das Erstellen von Workern hat Overhead, daher wiederverwendet ein Worker-Pool eine feste Anzahl von Workern und reiht Aufgaben ein – wie ein Thread-Pool in anderen Sprachen. Dieses Pattern maximiert die CPU-Auslastung (ein Worker pro Kern) und vermeidet die Kosten, pro Aufgabe Worker zu erzeugen. Der Pool verteilt Aufgaben an freie Worker und reiht sie ein, wenn alle beschäftigt sind. Essenziell für die Verarbeitung vieler unabhängiger Arbeitspakete.
// A worker pool reuses workers to avoid creation overhead
class WorkerPool {
constructor(workerUrl, size = 4) {
this.workers = [];
this.queue = [];
for (let i = 0; i < size; i++) {
const worker = new Worker(workerUrl);
worker.busy = false;
this.workers.push(worker);
}
}
run(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
const worker = this.workers.find(w => !w.busy);
if (worker) {
this.execute(worker, task);
} else {
this.queue.push(task); // wait for a free worker
}
});
}
execute(worker, task) {
worker.busy = true;
worker.onmessage = (e) => {
worker.busy = false;
task.resolve(e.data);
this.next();
};
worker.postMessage(task.data);
}
next() {
const task = this.queue.shift();
const worker = this.workers.find(w => !w.busy);
if (task && worker) this.execute(worker, task);
}
}DOM-Manipulation Deep
querySelector
querySelector gibt die erste Übereinstimmung zurück, querySelectorAll gibt eine statische NodeList zurück. getElementsByClassName gibt eine live HTMLCollection zurück. NodeList unterstützt forEach; HTMLCollection nicht.
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('div.item');
all.forEach(el => console.log(el.textContent));
const live = document.getElementsByClassName('item'); // live
const static = document.querySelectorAll('.item'); // staticErstellen & Einfügen
createElement erstellt ein neues Element. appendChild fügt als letztes Kind hinzu, prepend fügt als erstes hinzu. textContent ist sicherer als innerHTML, da es XSS verhindert.
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';
div.setAttribute('data-id', '42');
const parent = document.querySelector('#container');
parent.appendChild(div);
parent.prepend(div);Event-Delegation
Event-Delegation hängt einen Listener an ein Eltern-Element statt viele an Kinder. closest findet den nächsten Vorfahren, der einem Selektor entspricht. Behandelt dynamisch hinzugefügte Elemente.
document.querySelector('#list').addEventListener('click', (e) => {
const item = e.target.closest('.list-item');
if (!item) return;
console.log('Clicked:', item.dataset.id);
});classList-API
classList bietet Methoden, um CSS-Klassen sicher zu manipulieren. toggle gibt true zurück, wenn hinzugefügt, false, wenn entfernt. Sauberer als das Manipulieren des className-Strings.
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('dark-mode');
el.classList.replace('old', 'new');
if (el.classList.contains('active')) { /* ... */ }Dataset-Attribute
data-*-Attribute speichern benutzerdefinierte Daten. dataset bietet camelCase-Zugriff: data-user-id wird zu dataset.userId. Werte sind immer Strings.
// HTML: <div data-user-id="42" data-role="admin"></div>
const el = document.querySelector('[data-user-id]');
console.log(el.dataset.userId); // "42"
console.log(el.dataset.role); // "admin"
el.dataset.userId = '99';Canvas-API
Grundlegendes Zeichnen
getContext("2d") gibt den 2D-Rendering-Kontext zurück. fillRect zeichnet ein gefülltes Rechteck, strokeRect zeichnet eine Umrandung. Setze fillStyle/strokeStyle vor dem Zeichnen.
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 50);Pfade & Linien
moveTo positioniert den Cursor ohne zu zeichnen. lineTo zeichnet eine Linie. closePath verbindet zurück zum Start. arc zeichnet Kreise/Bögen.
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 35, 25, 0, Math.PI * 2);
ctx.fill();Text & Verläufe
createLinearGradient erstellt einen Verlauf. addColorStop definiert Farben an Positionen (0-1). Setze font und textAlign vor dem Zeichnen von Text.
const grad = ctx.createLinearGradient(0, 0, 200, 0);
grad.addColorStop(0, 'red');
grad.addColorStop(1, 'blue');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 100);
ctx.font = '24px Arial';
ctx.fillText('Hello Canvas', 100, 50);Animations-Loop
requestAnimationFrame synchronisiert mit der Display-Aktualisierung (~60fps). clearRect löscht vor jedem Frame. Abbrechen mit cancelAnimationFrame.
let x = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'green';
ctx.fillRect(x, 50, 30, 30);
x += 2;
if (x > canvas.width) x = 0;
requestAnimationFrame(animate);
}
animate();Bildmanipulation
drawImage rendert Bilder. getImageData gibt Pixeldaten als RGBA-Arrays zurück. Manipuliere Pixel für Filter. putImageData schreibt modifizierte Pixel zurück.
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, 200, 150);
const data = ctx.getImageData(0, 0, 200, 150);
for (let i = 0; i < data.data.length; i += 4)
data.data[i] = 255 - data.data[i];
ctx.putImageData(data, 0, 0);
};
img.src = 'photo.jpg';WebSockets
Basis-WebSocket
WebSocket bietet Vollduplex-Kommunikation über eine einzelne TCP-Verbindung. onopen feuert bei Verbindung, onmessage bei eintreffenden Daten. Behandle immer onerror.
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => { ws.send('Hello Server'); };
ws.onmessage = (e) => { console.log('Received:', e.data); };
ws.onclose = () => console.log('Disconnected');
ws.onerror = (err) => console.error('Error:', err);JSON senden & empfangen
WebSocket-Daten werden als Strings oder Binär übertragen. JSON.stringify/parse ermöglicht strukturierten Datenaustausch. Ein type-Feld ermöglicht Nachrichten-Routing.
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'message', text: 'Hello' }));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
switch (data.type) {
case 'message': console.log(data.text); break;
}
};Wiederverbindung
WebSocket-Verbindungen können abbrechen. Exponentielles Backoff (2^retries) verhindert Überlastung des Servers. Begrenze Verzögerung auf 30s. Setze Retry-Zähler bei Erfolg zurück.
class ReconnectingWS {
constructor(url) { this.url = url; this.retries = 0; this.connect(); }
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => { this.retries = 0; };
this.ws.onclose = () => {
const delay = Math.min(1000 * 2 ** this.retries++, 30000);
setTimeout(() => this.connect(), delay);
};
}
}Binäre Daten
WebSocket unterstützt binäre Daten über ArrayBuffer. Setze binaryType auf arraybuffer. DataView bietet typisierten Zugriff. Binär ist effizienter für numerische Daten.
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 42);
ws.send(buffer);
ws.binaryType = 'arraybuffer';
ws.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
const v = new DataView(e.data);
console.log(v.getInt32(0));
}
};Heartbeat
Heartbeats erkennen veraltete Verbindungen. Sende Pings und erwarte Pongs. Wenn kein Pong innerhalb des Timeouts, schließen und neu verbinden. readyState prüft den Verbindungsstatus.
setInterval(() => {
if (ws.readyState === WebSocket.OPEN)
ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
setInterval(() => {
if (Date.now() - lastPong > 60000) ws.close();
}, 10000);Service Workers
Registrierung
Service Worker laufen in einem separaten Thread und fangen Netzwerkanfragen ab. Registrierung muss über HTTPS oder localhost erfolgen. Der SW-Dateispeicherort bestimmt den Scope.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Registered:', reg.scope))
.catch(err => console.error('Failed:', err));
}Caching
Cache-First-Strategie: aus Cache ausliefern, Fallback auf Netzwerk. install pre-cacht Assets. fetch fängt Anfragen ab. Andere Strategien: Network-First, Stale-While-Revalidate.
const CACHE = 'app-v1';
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE)
.then(c => c.addAll(['/', '/index.html', '/style.css'])));
});
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request)
.then(cached => cached || fetch(e.request)));
});Background Sync
Background Sync verschiebt Aktionen, bis die Konnektivität zurückkehrt. Das sync-Event feuert, wenn das Netzwerk verfügbar ist. Speichere ausstehende Aktionen in IndexedDB.
navigator.serviceWorker.ready.then(reg =>
reg.sync.register('send-messages'));
self.addEventListener('sync', (e) => {
if (e.tag === 'send-messages')
e.waitUntil(sendPendingMessages());
});Push-Benachrichtigungen
Push-Benachrichtigungen funktionieren sogar, wenn die App geschlossen ist. subscribe registriert bei einem Push-Service mit VAPID-Keys. userVisibleOnly erfordert das Anzeigen einer Benachrichtigung.
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
});
self.addEventListener('push', (e) => {
const data = e.data.json();
e.waitUntil(self.registration.showNotification(data.title, {
body: data.body
}));
});Update & Aktivierung
skipWaiting aktiviert einen neuen SW sofort. activate bereinigt alte Caches. clients.claim übernimmt sofort die Kontrolle. Versioniere den Cache-Namen, um Updates auszulösen.
self.addEventListener('install', (e) => {
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE)
.map(k => caches.delete(k)))));
self.clients.claim();
});IndexedDB
Datenbank öffnen
IndexedDB ist eine NoSQL-Datenbank im Browser. onupgradeneeded feuert bei Versionsänderungen und wird verwendet, um Object Stores zu erstellen. keyPath definiert den Primärschlüssel.
const req = indexedDB.open('MyDatabase', 1);
req.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains('users'))
db.createObjectStore('users', { keyPath: 'id' });
};
req.onsuccess = (e) => { const db = e.target.result; };Add & Put
Transaktionen gruppieren Operationen atomar. add schlägt bei doppelten Schlüsseln fehl, put überschreibt. readwrite erlaubt Modifikationen. oncomplete feuert bei Erfolg.
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice' }); // Fails if key exists
store.put({ id: 1, name: 'Bob' }); // Overwrites
tx.oncomplete = () => console.log('Saved');Daten abfragen
get ruft per Schlüssel ab. openCursor iteriert über Datensätze. cursor.continue geht zum nächsten. Wrappe in Promises für async/await-Verwendung.
const tx = db.transaction('users', 'readonly');
const req = tx.objectStore('users').get(1);
req.onsuccess = () => console.log(req.result);
const cursorReq = tx.objectStore('users').openCursor();
cursorReq.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { console.log(cursor.value); cursor.continue(); }
};Index-Abfragen
Indizes ermöglichen Abfragen auf Nicht-Schlüssel-Felder. IDBKeyRange erstellt Bereiche. Indizes müssen in onupgradeneeded erstellt werden.
const tx = db.transaction('users', 'readonly');
const index = tx.objectStore('users').index('name');
index.get('Alice').onsuccess = (e) =>
console.log(e.target.result);
const range = IDBKeyRange.bound('A', 'M');
index.openCursor(range).onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { cursor.continue(); }
};Löschen & Leeren
delete entfernt einen einzelnen Datensatz. clear entfernt alle Datensätze. deleteDatabase entfernt die gesamte Datenbank. Alle Modifikationen erfordern readwrite-Transaktionen.
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').delete(1); // Delete by key
tx.objectStore('users').clear(); // Clear all
indexedDB.deleteDatabase('MyDatabase'); // Delete DB
tx.oncomplete = () => console.log('Done');WebRTC
Get User Media
getUserMedia fordert Kamera- und Mikrofonzugriff an. Gibt einen MediaStream zurück. srcObject weist den Stream dem Video-Element zu. Erfordert HTTPS und Benutzerberechtigung.
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
document.querySelector('video').srcObject = stream;
});Peer-Verbindung
RTCPeerConnection stellt P2P-Verbindungen her. addTrack fügt Medien hinzu. ontrack empfängt den Remote-Stream. ICE-Kandidaten sind Netzwerkpfade, die über Signaling ausgetauscht werden.
const pc = new RTCPeerConnection(config);
stream.getTracks().forEach(t => pc.addTrack(t, stream));
pc.ontrack = (e) => { remoteVideo.srcObject = e.streams[0]; };
pc.onicecandidate = (e) => {
if (e.candidate) sendToPeer(e.candidate);
};Offer & Answer
SDP-Aushandlung: Offer/Answer-Austausch beschreibt Medienformate. setLocalDescription setzt lokales SDP, setRemoteDescription setzt Remote-SDP.
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendToPeer(offer);
// Callee:
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToPeer(answer);Data Channels
Data Channels ermöglichen beliebigen Datentransfer über WebRTC mit geringer Latenz. createDataChannel erstellt einen auf der Offerer-Seite. ondatachannel empfängt auf der Answerer-Seite.
const channel = pc.createDataChannel('chat');
channel.onopen = () => console.log('Connected');
channel.onmessage = (e) => console.log('Received:', e.data);
channel.send('Hello peer!');
pc.ondatachannel = (e) => {
e.channel.onmessage = (ev) => console.log(ev.data);
};Bildschirmfreigabe
getDisplayMedia erfasst Bildschirm, Fenster oder Tab. Der Browser zeigt einen Picker. onended feuert, wenn der Benutzer die Freigabe stoppt. Behandle immer Cleanup.
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30 }, audio: true
});
video.srcObject = stream;
stream.getVideoTracks()[0].onended = () =>
console.log('Stopped');Performance-Optimierung
Debounce & Throttle
Debounce verzögert die Ausführung, bis Aufrufe aufhören (gut für Suche). Throttle begrenzt auf einmal pro Intervall (gut für Scroll). Beide verhindern übermäßige Aufrufe.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) { fn(...args); inThrottle = true;
setTimeout(() => inThrottle = false, limit); }
};
}Web Workers
Web Workers führen JavaScript in einem separaten Thread für CPU-intensive Aufgaben aus. Daten werden über postMessage übergeben. Worker können nicht auf das DOM zugreifen.
const worker = new Worker('worker.js');
worker.postMessage({ data: [1, 2, 3] });
worker.onmessage = (e) => console.log('Result:', e.data);
// worker.js
self.onmessage = (e) => {
self.postMessage(e.data.data.map(x => x ** 2));
};Lazy Loading
IntersectionObserver feuert, wenn Elemente in den Viewport eintreten. data-src hält die echte URL; src wird gesetzt, wenn sichtbar. Reduziert das anfängliche Laden bei bildlastigen Seiten.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));DOM-Updates bündeln
DocumentFragment bündelt DOM-Einfügungen in einem einzigen Reflow, deutlich schneller als einzelnes Anhängen. Minimiere Layout-Thrashing.
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment); // Single reflowSpeicherverwaltung
WeakMap erlaubt GC von Schlüsseln und verhindert Lecks. Entferne immer Event-Listener, wenn Elemente entfernt werden. Setze große Objekte auf null für GC.
const cache = new WeakMap();
cache.set(element, data);
// When element is GC'd, entry is removed
element.removeEventListener('click', handler);
bigArray = null; // Allow GCSicherheit (XSS/CSRF)
XSS-Prävention
XSS injiziert bösartige Skripte über Benutzereingaben. Verwende niemals innerHTML mit nicht vertrauenswürdigen Daten. textContent ist sicher. CSP-Header beschränken Skript-Quellen.
// BAD: vulnerable to XSS
element.innerHTML = userInput;
// GOOD: safe alternatives
element.textContent = userInput;
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}CSRF-Prävention
CSRF bringt Benutzer zu unerwünschten Aktionen. Tokens stellen sicher, dass Anfragen von deiner App stammen. SameSite=Strict-Cookies werden nicht cross-site gesendet. Verwende CSRF-Tokens für zustandsändernde Operationen.
const token = document.cookie.match(/csrf_token=([^;]+)/)?.[1];
fetch('/api/data', {
method: 'POST',
headers: { 'X-CSRF-Token': token }
});
// SameSite cookie: Set-Cookie: session=abc; SameSite=StrictContent Security Policy
CSP beschränkt, welche Ressourcen geladen werden können. default-src ist der Fallback. script-src kontrolliert JavaScript. Beginne mit Report-Only-Modus vor der Durchsetzung.
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:">Sichere Cookies
HttpOnly verhindert JavaScript-Zugriff auf Cookies und mildert XSS. Secure stellt nur HTTPS sicher. SameSite=Strict verhindert CSRF. Session-Cookies sollten immer beides verwenden.
// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
// HttpOnly: not accessible via JavaScript
// Secure: only sent over HTTPS
// SameSite: Strict | Lax | None
document.cookie // Cannot see HttpOnly cookiesEingabevalidierung
Clientseitige Validierung verbessert UX, ist aber keine Sicherheit. Validiere immer serverseitig. Verwende DOMPurify für HTML-Sanitization. Whitelist erlaubte Tags.
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function sanitize(html) {
return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong'] });
}
// Never trust client input - always validate on server tooDesign Patterns
Singleton
Singleton stellt sicher, dass nur eine Instanz existiert. Der Konstruktor gibt die existierende Instanz zurück. Nützlich für Konfiguration, Logging und Datenbankverbindungen.
class Config {
constructor() {
if (Config.instance) return Config.instance;
this.settings = {};
Config.instance = this;
}
}
const c1 = new Config();
const c2 = new Config();
console.log(c1 === c2); // trueObserver/Pub-Sub
Das Observer-Pattern lässt Objekte Events abonnieren. on registriert, emit löst aus, off meldet ab. Grundlage ereignisgesteuerter Architekturen.
class EventEmitter {
constructor() { this.events = {}; }
on(event, cb) { (this.events[event] ||= []).push(cb); }
emit(event, data) { (this.events[event] || []).forEach(cb => cb(data)); }
off(event, cb) { this.events[event] = this.events[event]?.filter(c => c !== cb); }
}Factory
Factory erstellt Objekte ohne Instantiierungslogik offenzulegen. Der Aufrufer gibt einen Typ an, die Factory entscheidet, welche Klasse instanziiert wird.
class Dog { speak() { return 'Woof'; } }
class Cat { speak() { return 'Meow'; } }
function AnimalFactory(type) {
switch (type) {
case 'dog': return new Dog();
case 'cat': return new Cat();
}
}Module-Pattern
Das Module-Pattern kapselt privaten Zustand mit Closures. IIFE erstellt einen privaten Scope. Nur zurückgegebene Methoden sind öffentlich.
const counter = (() => {
let count = 0; // Private
return {
increment: () => ++count,
getCount: () => count
};
})();
counter.increment();
console.log(counter.getCount()); // 1Strategy
Das Strategy-Pattern kapselt austauschbare Algorithmen. Der Kontext delegiert an die ausgewählte Strategie. Vermeidet große if/else-Ketten.
const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
};
function calculate(strategy, a, b) {
return strategies[strategy]?.(a, b);
}Häufige Fallstricke
this-Bindung
this wird dadurch bestimmt, wie eine Funktion aufgerufen wird. Arrow-Funktionen erben this aus dem umschließenden Scope. Methoden verlieren this beim Ablösen. Verwende bind.
const obj = {
name: 'Alice',
greet: function() { console.log(this.name); },
arrow: () => console.log(this.name)
};
obj.greet(); // Alice
obj.arrow(); // undefined
const fn = obj.greet;
fn(); // undefined (lost binding)Gleitkomma
JavaScript verwendet IEEE 754-Gleitkomma. Verwende Number.EPSILON für Vergleiche oder multipliziere mit Zehnerpotenzen, um mit Ganzzahlen zu arbeiten.
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
const eps = Number.EPSILON;
console.log(Math.abs(0.1 + 0.2 - 0.3) < eps); // true== vs ===
== führt Typumwandlung durch, was zu überraschenden Ergebnissen führt. === prüft sowohl Typ als auch Wert ohne Umwandlung. Verwende immer === und !==.
console.log(0 == false); // true
console.log('' == false); // true
console.log(null == undefined); // true
console.log(0 === false); // false
console.log('' === false); // false
// Always use ===Closures in Schleifen
var ist function-scoped, also teilen sich alle Closures dieselbe Variable. let ist block-scoped und erstellt pro Iteration eine neue Bindung. Verwende immer let/const.
// BUG: all print 3
for (var i = 0; i < 3; i++)
setTimeout(() => console.log(i), 100);
// FIX: let
for (let i = 0; i < 3; i++)
setTimeout(() => console.log(i), 100);Async-Fehlerbehandlung
Unbehandelte Promise-Rejections können Node.js zum Absturz bringen. Wrappe await immer in try/catch oder verwende .catch(). Höre auf unhandledRejection-Events.
// BUG: unhandled rejection
async function fetchData() {
const data = await fetch('/api');
return data.json();
}
// FIX: try/catch
async function fetchDataSafe() {
try { return await (await fetch('/api')).json(); }
catch (err) { console.error('Failed:', err); return null; }
}Verwandte JavaScript-Snippets
Copy-paste ready code for common tasks.
Array Map Filter Reduce
map, filter, reduce, find, some und every auf Arrays verketten.
Array-Deduplizierung
Ein Array mit Set deduplizieren.
Tiefes Klonen
Objekte tief klonen, unterstützt gängige Datentypen.
Debounce-Funktion
Nach Auslösung eines Ereignisses eine Zeitspanne warten, bevor ausgeführt wird; den Timer zurücksetzen, wenn während der Wartezeit erneut ausgelöst wird.
Throttle-Funktion
Eine Funktion darauf beschränken, in einem Zeitintervall höchstens einmal auszuführen.
Promise.all Nebenläufigkeitskontrolle
Ein Promise-Executor mit Nebenläufigkeitslimit.
async/await Fehlerbehandlung
Async-Funktionen wrappen, um Ausnahmen einheitlich abzufangen.
Fetch-Wrapper
fetch mit Timeout, Fehlerbehandlung und JSON-Parsing wrappen.
localStorage-Operationen
localStorage mit Ablaufzeit und JSON-Unterstützung wrappen.
Cookie-Operationen
Cookie-Lese-, Schreib- und Löschoperationen wrappen.
URL-Parameter-Parsing
URL-Query-String in ein Objekt parsen.
Datumsformatierung
Ein Datum in einen bestimmten String formatieren.
Geldformatierung
Eine Zahl als tausendertrennzeichen-formatierten Geld-String formatieren.
Zufallszahlengenerierung
Zufallszahlen und zufällige Strings in einem Bereich generieren.
Farbkonvertierung
Zwischen RGB- und HEX-Farben konvertieren.
UUID-Generierung
Einen UUID-v4-konformen eindeutigen Bezeichner generieren.
String-Kürzung
Einen String kürzen und Auslassungspunkte anhängen.
Array-Abflachung
Ein mehrdimensionales Array in eine Dimension abflachen.
Objekt-Zusammenführung
Mehrere Objekte tief zusammenführen.
Typ-Prüfung
JavaScript-Datentypen präzise bestimmen.
Event-Delegation
Event-Delegation über Event-Bubbling implementieren.
DOM-Manipulation
Dynamisch DOM-Elemente erstellen und manipulieren.
Formular-Validierung
Eine Sammlung gängiger Formular-Validierungsregeln.
Datei-Upload
Datei-Upload mit Fortschritt und Chunking-Unterstützung wrappen.
Bild-Lazy-Loading
Bild-Lazy-Loading mit IntersectionObserver implementieren.
In Zwischenablage kopieren
Eine browserübergreifende Zwischenablage-Kopiermethode.
Fullscreen API
Browser-Vollbildoperationen wrappen.
Geolocation
Geolocation-Informationen des Nutzers abrufen.
Web Worker
Einen Web Worker erstellen, um zeitaufwändige Aufgaben auszuführen.
Service Worker
Einen Service Worker für Offline-Caching registrieren.
IndexedDB-Operationen
IndexedDB-CRUD-Operationen wrappen.
Canvas-Zeichnung
Grundlegendes Canvas-Zeichnungsbeispiel.
Was this helpful?