diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/.gitignore b/.gitignore index 6f90fd190..1a71fb7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ sftp-config.json Thumbs.db +/svgs \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 591a4c831..b6b4b4bb8 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -1,76 +1,76 @@ # Eine Einführung in JavaScript -Mal sehen, was das Besondere an JavaScript ist, was wir damit erreichen können und welche anderen Technologien gut damit umgehen. +Mal sehen was das Besondere an JavaScript ist, was wir damit alles anstellen können und welche anderen Technologien gut damit umgehen können. ## Was ist JavaScript? -*JavaScript* wurde ursprünglich entwickelt, um *"Webseiten lebendig zu machen "*. +JavaScript wurde ursprünglich entwickelt, um "Webseiten lebendig zu machen". -Die Programme dieser Sprache werden *scripts* genannt. Sie können direkt im HTML der Seite geschrieben werden und werden automatisch beim laden der Seite ausgeführt. +Die Programme dieser Sprache werden *scripts* genannt. Diese kann man direkt ins HTML einer Seite schreiben welche dann beim Laden der Seite automatisch ausgeführt werden. -Diese Scripts werden im Klartext bereitgestellt und ausgeführt. Sie benötigen keine spezielle Vorbereitung oder Kompilierung um zu funktionieren. +Skripte werden als reiner Text bereitgestellt und ausgeführt. Sie benötigen keine spezielle Vorbereitung oder Kompilierung, um ausgeführt zu werden. -In diesem Aspekt unterscheidet sich JavaScript sehr von einer anderen Sprache namens [Java](https://en.wikipedia.org/wiki/Java_(programming_language)). +In diesem Aspekt unterscheidet sich JavaScript sehr von einer anderen Sprache namens [Java]https://de.wikipedia.org/wiki/Java_(Programmiersprache)). -```smart header="Warum wird es JavaScript genannt?" -Als JavaScript erstellt wurde, hatte es zunächst einen anderen Namen: "LiveScript". Aber Java war damals sehr beliebt, so dass beschlossen wurde, dass die Positionierung einer neuen Sprache als "jüngerer Bruder" von Java helfen würde. +```smart header="Warum heißt es JavaScript?" +Als JavaScript entwickelt wurde, hatte es zunächst einen anderen Namen: "LiveScript". Aber Java war zu dieser Zeit sehr verbreitet, so dass beschlossen wurde, dass die Etablierung einer neuen Sprache als "kleiner Bruder" von Java helfen würde. -Aber als es sich entwickelte, wurde JavaScript zu einer völlig unabhängigen Sprache mit einer eigenen Spezifikation namens [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), und jetzt hat es überhaupt keinen Bezug mehr zu Java. +In der weiteren Entwicklung wurde JavaScript zu einer völlig unabhängigen Sprache mit einer eigenen Spezifikation, [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), und hat darum überhaupt keinen Bezug mehr zu Java. ``` -Heute kann JavaScript nicht mehr nur im Browser ausgeführt werden. Es ist möglich JavaScript auch auf dem Server oder einem anderen beliebigen Geräte auszuführen, welches über ein Programm namens [JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine) verfügt. +Heute kann JavaScript nicht nur im Browser ausgeführt werden, sondern auch auf dem Server oder auf jedem Gerät, das über ein spezielles Programm namens [JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine) verfügt. Der Browser verfügt über eine eingebettete Engine, die manchmal auch als "JavaScript Virtual Machine" bezeichnet wird. -Verschiedene Engines haben unterschiedliche "Kodnamen". Zum Beispiel: +Verschiedene Engines haben unterschiedliche "Codenamen". Zum Beispiel: -- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome und Opera. -- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. -- ...Es existieren auch noch andere Kodnamen wie "Trident" und "Chakra" für verschiedene Versionen von IE, "ChakraCore" für Microsoft Edge, "Nitro" und "SquirrelFish" für Safari, usw. +- [V8](https://de.wikipedia.org/wiki/V8_(JavaScript-Implementierung)) -- in Chrome und Opera. +- [SpiderMonkey](https://de.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. +- ...Es gibt noch weitere Codenamen wie "Trident" und "Chakra" für verschiedene IE-Versionen, "ChakraCore" für Microsoft Edge, "Nitro" und "SquirrelFish" für Safari, usw. -Die obigen Begriffe sind gut zu merken, da sie in Entwicklerartikeln im Internet verwendet werden. Wir werden sie auch benutzen. Wenn zum Beispiel "ein Feature X von V8 unterstützt wird", dann funktioniert es wahrscheinlich in Chrome und Opera. +Die oben genannten Begriffe sollte man sich merken, weil sie in Entwicklerarikeln im Internet verwendet werden. Wir werden sie auch verwenden. Wenn, zum Beispiel, "ein Feature X von V8 unterstützt wird", dann funktioniert es wahrscheinlich in Chrome und Opera. -```smart header="Wie funktioniert die Engine?" +```smart header="Wie funktionieren Engines?" Engines sind kompliziert. Aber die Grundlagen sind einfach. -1. Die Engine (eingebettet, wenn es sich um einen Browser handelt) liest ("parses") den Script. -2. Danach wird der Script in die Maschinensprache übersetzt ("Kompilieren"). -3. Und zum Schluss wird der Maschienen Code ausgeführt, was ziemlich schnell passiert. +1. Die Engine (eingebettet, wenn es sich um einen Browser handelt) liest ("parst") das Script. +2. Danach wird das Script in Maschinensprache konvertiert ("kompiliert"). +3. Und zum Schluss wird der Maschienencode ausgeführt, was ziemlich schnell passiert. -Die Engine wendet in jedem Schritt des Prozesses Optimierungen an. Es beobachtet sogar das kompilierte Skript, während es läuft, analysiert die Daten, die durch es fließen, und optimiert den Maschinencode basierend auf diesem Wissen weiter. +Die Engine wendet in jedem Schritt des Prozesses Optimierungen an. Es beobachtet sogar das kompilierte Skript während es läuft, analysiert die Daten, welche sie verarbeitet, und optimiert den Maschinencode basierend auf diesem Wissen weiter. ``` -## Was kann in-browser JavaScript tun? +## Was kann im Browser ausgeführtes JavaScript? -Modernes JavaScript ist eine "sichere" Programmiersprache. Es bietet keinen Low-Level-Zugriff auf Speicher oder CPU, da es ursprünglich für Browser erstellt wurde. +Modernes JavaScript ist eine "sichere" Programmiersprache. Es bietet keinen Low-Level-Zugriff auf Speicher oder CPU, da es ursprünglich für Browser entwickelt wurde, die dies nicht benötigen. -Die Funktionen von JavaScript hängen stark von der Umgebung ab, in der es ausgeführt wird. Beispielsweise unterstützt [Node.js](https://wikipedia.org/wiki/Node.js) Funktionen, die es JavaScript z.B. ermöglichen, beliebige Dateien zu lesen oder zu schreiben, sowie Netzwerkanfragen durchzuführen. +Die Möglichkeiten von JavaScript hängen stark von der Umgebung ab, in der es ausgeführt wird. Beispielsweise unterstützt [Node.js](https://de.wikipedia.org/wiki/Node.js) Funktionen, die es JavaScript erlauben, beliebige Dateien zu lesen/schreiben, Netzwerkanfragen durchzuführen usw. -In-Browser JavaScript kann alles, was mit der Manipulation von Webseiten, der Interaktion mit dem Benutzer und dem Webserver zu tun hat. +Im Browser ausgeführtes JavaScript kann alles was mit der Manipulation von Webseiten, der Interaktion mit dem Benutzer und dem Webserver zu tun hat. -So ist beispielsweise In-Browser JavaScript in der Lage: +So ist beispielsweise im Browser laufendes JavaScript in der Lage: -- Der Seite neues HTML zu addieren, den existierenden content zu modifizieren, oder die Stile anzupassen. +- Der Seite neues HTML hinzuzufügen, den existierenden Inhalt zu modifizieren oder Stile anzupassen. - Auf Benutzeraktionen zu reagieren, zum Beispiel Mausklicks, Mauszeigerbewegungen oder Tastenanschläge. -- Anfragen and entfernte Server über das Netzwerk zu versenden und Daten hoch und runter zu laden (diese technologien werden [AJAX](https://en.wikipedia.org/wiki/Ajax_(programming)) und [COMET](https://en.wikipedia.org/wiki/Comet_(programming)) genannt). -- Lesen und schreiben von cookies, sowie das abfragen des Benutzers oder das anzeigen von Nachrichten. -- Speichern der Daten auf der client-side ("local storage"), also im Browser des Benutzers. +- Anfragen über das Netzwerk an Remote-Server senden, Herunter- und Hochladen von Dateien (sogenannte [AJAX](https://de.wikipedia.org/wiki/Ajax_(Programmierung))- und [COMET](https://en.wikipedia.org/wiki/Comet_(programming))- Technologien). +- Cookies auslesen und setzen, Fragen an den Besucher stellen, Nachrichten anzeigen. +- Daten auf der Client-Seite zu speichern ("lokale Speicherung"). ## Was kann JavaScript im Browser nicht tun? -Die Fähigkeiten von JavaScript im Browser sind aus Gründen der Sicherheit des Benutzers eingeschränkt. Ziel ist es, zu verhindern, dass eine bösartige Webseite auf private Informationen zugreift oder die Daten des Benutzers schädigt. +Die Möglichkeiten von JavaScript im Browser sind aus Sicherheitsgründen für den Benutzer eingeschränkt. Damit soll verhindert werden, dass eine bösartige Webseite Zugang zu privaten Informationen erhält oder die Daten des Benutzers schädigt. Beispiele für solche Beschränkungen sind: -- JavaScript auf einer Webseite darf keine beliebigen Dateien auf der Festplatte lesen/schreiben, sie kopieren oder Programme ausführen. Es hat keinen direkten Zugriff auf die Funktionen des Betriebssystems. +- JavaScript auf einer Webseite darf keine willkürlichen Dateien auf der Festplatte lesen, schreiben oder kopieren, oder Programme ausführen. Es hat keinen direkten Zugriff auf die Funktionen des Betriebssystems. - Moderne Browsers erlauben es mit Dateien zu arbeiten. Der Zugriff ist jedoch beschränkt und nur möglich wenn der Benutzer bestimmte Aktionen ausführt, z.B. die Datei in den Browser per "drag and drop" lädt oder sie via `` tag auswählt. +Moderne Browser erlauben es, mit Dateien zu arbeiten, aber der Zugriff ist begrenzt und nur möglich, wenn der Benutzer bestimmte Aktionen ausführt, wie z. B. eine Datei in ein Browserfenster "zu ziehen" oder sie über ein -Tag auszuwählen. - Es gibt auch Möglichkeiten mit der Kamera oder dem Mikrofon des Geräts zu interagieren. Dies benötigt aber die explizite Zustimmung des Benutzers. Deshalb kann eine JavaScript-enabled Website nicht heimlich die Webcam aktivieren, die Umgebung beobachten und die Informationen and die [NSA](https://en.wikipedia.org/wiki/National_Security_Agency) übermitteln. +Es gibt Möglichkeiten, mit Kamera/Mikrofon und anderen Geräten zu interagieren, aber sie erfordern die ausdrückliche Genehmigung des Benutzers. JavaScript auf einer Webseite darf also nicht heimlich eine Webcam aktivieren, die Umgebung beobachten und die Informationen an die [NSA](https://de.wikipedia.org/wiki/National_Security_Agency) senden. - Unterschiedliche Tabs und Fenster wissen in der Regel nicht voneinander. Es gibt jedoch Ausnahmen, bei welchen dies doch der Fall ist. Dies kann z.B. passieren, wenn durch JavaScript ein neues Fenster geöffnet wird. Aber selbst in diesem Fall kann es sein, dass JavaScript von einer Seite nicht auf die andere Seite zugreifen kann, wenn sie von verschiedenen Seiten (von einer anderen Domäne, einem anderen Protokoll oder Port) kommen. - Dies wird die "Same Origin Policy" genannt. Um das zu umgehen, müssen *beide Seiten* für den Datenaustausch übereinstimmen und einen speziellen JavaScript-Code enthalten, der dies behandelt. Wir werden das im Tutorial behandeln. +Dies wird die "Same-Origin-Policy" genannt. Um dies zu umgehen, müssen *beide Seiten* dem Datenaustausch zustimmen und einen speziellen JavaScript-Code enthalten, der dies ermöglicht. Wir werden das im Tutorial behandeln. Auch diese Einschränkung dient der Sicherheit des Benutzers. Eine Seite von `http://anysite.com`, die ein Benutzer geöffnet hat, darf nicht in der Lage sein, auf einen anderen Browser-Tab mit der URL `http://gmail.com` zuzugreifen und Informationen von dort zu stehlen. - JavaScript kann leicht über das Netz mit dem Server kommunizieren, von dem die aktuelle Seite stammt. Aber seine Fähigkeit, Daten von anderen Seiten/Domains zu empfangen, ist eingeschränkt. @@ -80,42 +80,42 @@ Obwohl es möglich ist, erfordert es eine ausdrückliche Zustimmung (ausgedrück Solche Einschränkungen bestehen nicht, wenn JavaScript außerhalb des Browsers, z.B. auf einem Server, verwendet wird. Moderne Browser erlauben auch Plugins/Erweiterungen, die unter Umständen nach erweiterten Rechten fragen. -## What makes JavaScript unique? +## Was macht JavaScript einzigartig? -Es gibt mindestens *drei* großartige Dinge über JavaScript: +Es gibt mindestens *drei* großartige Eigenschaften, welche JavaScript auszeichnen: ```compare -+ Volle integration in HTML und CSS. ++ Volle Integration in HTML und CSS. + Einfache Dinge werden einfach gemacht. -+ Unterstützung von allen gängigen Browsern und standardmäßig aktiviert. ++ Wird von allen gängigen Browsern unterstützt und ist standardmäßig aktiviert. ``` JavaScript ist die einzige Browser-Technologie, die diese drei Dinge vereint. Das macht JavaScript einzigartig. Deshalb ist es das am weitesten verbreitete Werkzeug zur Erstellung von Browser-Oberflächen. -Trotzdem erlaubt JavaScript auch die Erstellung von Servern, mobilen Anwendungen, etc. +Davon abgesehen ermöglicht JavaScript auch die Erstellung von Servern, mobilen Anwendungen, usw. ## Sprachen "über" JavaScript -Die Syntax von JavaScript ist nicht für jeden geeignet. Verschiedene Menschen wollen unterschiedliche Funktionen. +Die Syntax von JavaScript entspricht nicht jedem. Verschiedene Individuen bevorzugen verschiedene Funktionen. Das ist zu erwarten, denn Projekte und Anforderungen sind für jeden anders. -So sind vor kurzem eine Fülle neuer Sprachen erschienen, die in JavaScript *transpiled* (konvertiert) werden, bevor sie im Browser laufen. +So sind in letzter Zeit eine Fülle neuer Sprachen erschienen, die in JavaScript *transponiert* (konvertiert) werden, bevor sie im Browser laufen. -Moderne Werkzeuge machen die Transpilation sehr schnell und transparent und erlauben es den Entwicklern tatsächlich, in einer anderen Sprache zu programmieren und diese "unter der Haube" automatisch zu konvertieren. +Moderne Werkzeuge ermöglichen eine sehr schnelle und transparente Konvertierung, die es den Entwicklern tatsächlich erlauben in einer anderen Sprache zu programmieren, die diese dann, für die Entwickler nicht ersichtlich, automatisch konvertieren. Beispiele für solche Sprachen sind: -- [CoffeeScript](http://coffeescript.org/) ist ein "syntactic sugar" für JavaScript. Es führt eine kürzere Syntax ein, was uns erlaubt, klareren und präziseren Code zu schreiben. Usually, Ruby devs like it. -- [TypeScript](http://www.typescriptlang.org/) ist darauf konzentriert "strict data typing" hinzuzufügen. TypeScript verfolg das Ziel den Entwicklungsprozess und den Support für komplexe Systeme zu vereinfachen. Die Sprache wurde von Microsoft entwickelt. -- [Flow](http://flow.org/) fügt auch "data typing" hinzu, aber auf eine andere Art und Weise. Sie wurde von Facebook entwickelt. -- [Dart](https://www.dartlang.org/) ist eine eigenständige Sprache, die eine eigene Engine hat, die in Nicht-Browser-Umgebungen (wie z.B. mobilen Anwendungen) läuft, aber auch in JavaScript umgesetzt werden kann. Sie wurde von Google entwickelt. +- [CoffeeScript](http://coffeescript.org/) ist eine vereinfachte Schreibweise für JavaScript. Es führt eine kürzere Syntax ein, die es uns erlaubt, eindeutigeren und präziseren Code zu schreiben. Normalerweise mögen es Ruby-Entwickler. +- [TypeScript](http://www.typescriptlang.org/) konzentriert sich auf das Hinzufügen einer "strengen Datentypisierung", um die Entwicklung und Unterstützung komplexer Systeme zu vereinfachen. Es wird von Microsoft entwickelt. +- [Flow](http://flow.org/) fügt auch die Datentypisierung hinzu, jedoch auf andere Art und Weise. Entwickelt von Facebook. +- [Dart](https://www.dartlang.org/) ist eine eigenständige Sprache, die eine eigene Engine besitzt, die in Nicht-Browser-Umgebungen (wie mobilen Anwendungen) läuft, aber auch in JavaScript transponiert werden kann. Entwickelt von Google. -Es gibt noch mehr. Auch wenn wir eine der transpilierten Sprachen verwenden sollten wir auch JavaScript trozdem kennen. Es ist wichtig zu verstehen, was im Hintergrund passiert und was wir eigentlich tun. +Es gibt noch mehr. Natürlich sollten wir, selbst wenn wir eine der transponierten Sprachen verwenden, auch JavaScript beherrschen, um zu verstehen, was wir tun. ## Zusammenfassung - JavaScript wurde ursprünglich als reine Browser-Sprache entwickelt, wird aber mittlerweile auch in vielen anderen Umgebungen eingesetzt. - Heute hat JavaScript eine einzigartige Position als die am weitesten verbreitete Browsersprache mit voller Integration in HTML/CSS. -- Es gibt viele Sprachen, die auf JavaScript "transponiert" werden und bestimmte Funktionen bieten. Es wird empfohlen, sich diese zumindest kurz anzuschauen, nachdem man JavaScript beherrscht. +- Es gibt viele Sprachen, die in JavaScript "transponiert" werden und bestimmte Funktionen bieten. Es wird empfohlen, sich diese zumindest kurz anzuschauen, nachdem man JavaScript beherrscht. \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/limitations.svg b/1-js/01-getting-started/1-intro/limitations.svg index a7863c63c..76ea43fd7 100644 --- a/1-js/01-getting-started/1-intro/limitations.svg +++ b/1-js/01-getting-started/1-intro/limitations.svg @@ -1 +1 @@ -https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file +https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index e514a306f..0d0145100 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,7 +1,11 @@ # Handbücher und Spezifikationen +<<<<<<< HEAD Dieses Buch ist ein *Tutorial*. Es soll dir helfen, die Sprache nach und nach zu erlernen. Aber sobald du mit den Grundlagen vertraut bist, wirst du andere Quellen benötigen. +======= +This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ## Spezifikation @@ -9,24 +13,46 @@ Dieses Buch ist ein *Tutorial*. Es soll dir helfen, die Sprache nach und nach zu Die Tatsache dass es so festgschrieben ist, macht es am Anfgan etwas schwer verständlich. Wenn du also die vertrauenswürdigste Informationsquelle benötigst, dann ist die Spezifikation dir richtige Stelle. Sie ist jedoch nicht für den alltäglichen Gebrauch passend. +<<<<<<< HEAD Eine neue Spezifikationsversion wird jedes Jahr veröffentlicht. In der Zwischenzeit kann der letzte entwurf unter gefunden werden. +======= +A new specification version is released every year. Between these releases, the latest specification draft is at . +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Um mehr über die neuen bleeding-edge features, einschliesslich derjendigen, die "fast Standard" sind ("stage 3" genannt), siehe Vorschläge unter . +<<<<<<< HEAD Wenn du für den Browser entwickeln, dann gibt es noch weitere Spezifikationen, die im [zweiten Teil](info:browser-environment) des Tutorials behandelt werden. +======= +Also, if you're developing for the browser, then there are other specifications covered in the [second part](info:browser-environment) of the tutorial. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 ## Handbücher +<<<<<<< HEAD - **MDN (Mozilla) JavaScript Reference** ist ein Handbuch mit Beispielen und anderen Informationen. Es ist grossartig, sich über einzelne Sprachfunktionen, Methoden etc. ausführlich zu informieren. +======= +- **MDN (Mozilla) JavaScript Reference** is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 +<<<<<<< HEAD Man kann es unter finden. +<<<<<<< HEAD Obwohl es oft am besten ist, stattdessen eine Internetsuche zu verwenden. Benutze einfach "MDN [Begriff]" in der Anfrage, z.B. um nach der `parseInt` Funktion zu suchen. - **MSDN** – Microsoft Handbuch mit vielen Informationen, einschließlich JavaScript (oft als JScript bezeichnet). Wenn man etwas speziell für den Internet Explorer benötigt, sollte man besser dorthin gehen: . Wir können auch eine Internetsuche mit Phrasen wie "RegExp MSDN" oder "RegExp MSDN jscript" verwenden. +======= +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for `parseInt` function. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 +======= + You can find it at . + +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ## Kompatibilitäts-Tabellen @@ -34,9 +60,16 @@ JavaScript ist eine Entwicklungssprache, neue Funktionen werden regelmäßig hin Um ihre Unterstützung unter den Browser-basierten und anderen Engines zu sehen, siehe: +<<<<<<< HEAD - - pro Feature-Tabellen der Unterstützung, z.B. um zu sehen, welche Engines moderne Kryptographie-Funktionen unterstützen: . - - eine Tabelle mit Sprachfunktionen und Engines, die diese unterstützen oder nicht unterstützen. All diese Ressourcen sind in der realen Entwicklung nützlich, da sie wertvolle Informationen über sprachliche Details, ihre Unterstützung usw. enthalten. +======= +- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: . +- - a table with language features and engines that support those or don't support. + +All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Bitte merke dir diese (oder diese Seite) für die Fälle, in denen du vertiefte Informationen über eine bestimmte Funktion benötigst. diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index d03f03def..a05b7b542 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -1,46 +1,66 @@ -# Code editors +# Code-Editoren -A code editor is the place where programmers spend most of their time. +Der Code-Editor ist das Werkzeug, mit dem Programmierer die meiste Zeit verbringen. -There are two main types of code editors: IDEs and lightweight editors. Many people use one tool of each type. +Es gibt zwei Arten von Code-Editoren: IDEs und Quelltext-Editoren. Viele Programmierer nutzen je ein Exemplar jedes Typs. ## IDE -The term [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) refers to a powerful editor with many features that usually operates on a "whole project." As the name suggests, it's not just an editor, but a full-scale "development environment." +Der Begriff [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) bezieht sich auf einen mächtigen Editor mit vielen Nutzungsmöglichkeiten, der im Normalfall alle Arbeitsbereiche des Projekts abdeckt. Wie der Name schon andeutet, handelt es sich bei IDEs nicht nur um einen reinen Editor, sondern um eine komplette Entwicklungsumgebung- -An IDE loads the project (which can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), and integrates with a version management system (like [git](https://git-scm.com/)), a testing environment, and other "project-level" stuff. +Eine IDE lädt das Projekt (das aus vielen Dateien bestehen kann), erlaubt die Navigation zwischen Dateien, stellt eine Autovervollständigung für das gesamte Projekt (und nicht nur für die geöffnete Datei) bereit und ist mit einem Versionsverwaltungssystem (wie z.B.: [git](https://git-scm.com/)), einer Test-Umgebung und anderen Projektrelevanten Bereichen verknüpft. -If you haven't selected an IDE yet, consider the following options: +Falls du für dich noch keine IDE ausgesucht hast, sind die nachfolgenden Optionen empfehlenswert: +<<<<<<< HEAD +- [Visual Studio Code](https://code.visualstudio.com/) (Cross-Plattform, kostenlos). +- [WebStorm](http://www.jetbrains.com/webstorm/) (Cross-Plattform, ksotenpflichtig). +======= - [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). -- [WebStorm](http://www.jetbrains.com/webstorm/) (cross-platform, paid). +- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid). +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 -For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code". "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. It's also good at JavaScript. There's also a free version [Visual Studio Community](https://www.visualstudio.com/vs/community/). +Für Windows steht außerdem "Visual Studio" zur Verfügung, diese IDE sollte nicht mit "Visual Studio Code" verwechselt werden. "Visual Studio" ist ein kostenpflichtiger und umfangreicher Editor, der insbesondere für die Arbeit mit der .Net-Plattform geeignet ist. "Visual Studio" ist außerdem gut für Javascript geeignet. Eine kostenlose Version ist unter folgender Adresse verfügbar: [Visual Studio Community](https://www.visualstudio.com/vs/community/). -Many IDEs are paid, but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. +Viele IDEs sind kostenpflichtig, stellen aber kostenlose Test-Versionen zur Verfügung. Die Kosten sind im Verhältnis zum Gehalt eines qualifizierten Programmierers vernachlässigbar, also such einfach die Plattform aus, die dir am besten passt. -## Lightweight editors +## Quelltext-Editoren -"Lightweight editors" are not as powerful as IDEs, but they're fast, elegant and simple. +Quelltext-Editoren, im englischen auch "lightweight editors", also leichtgewichtige Editoren genannt, sind nicht so mächtig wie IDEs, aber dafür sind sie schnell, eleganz und einfach in der Nutzung. -They are mainly used to open and edit a file instantly. +Sie werden hauptsächlich dafür genutzt, Dateien schnell zu öffnen und zu bearbeiten. -The main difference between a "lightweight editor" and an "IDE" is that an IDE works on a project-level, so it loads much more data on start, analyzes the project structure if needed and so on. A lightweight editor is much faster if we need only one file. +Der hauptsächliche Unterschied zwischen einem Quelltext-Editor und einer IDE liegt darin, dass die IDE auf der Projekt-Ebene arbeitet, sie lädt also viele relevante Dateien zu Programmstart, analysiert die Projektstruktur und so weiter. Ein Quelltext-Editor arbeitet weitaus schneller, wenn wir nur eine Datei brauchen. -In practice, lightweight editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE. +In der Praxis verfügen auch viele Quelltext-Editoren über eine breite Auswahl an Plugins, darunter etwa Analyse-Werkzeuge für die Code-Syntax und Unterstützung zur Autovervollständigung, so dass sich eine klare Grenze zwischen IDEs und Quelltext-Editoren gar nicht ziehen lässt. -The following options deserve your attention: +<<<<<<< HEAD +Die nachfolgenden Optionen sind gute Beispiele für Quelltext-Editoren +======= +There are many options, for instance: +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 -- [Atom](https://atom.io/) (cross-platform, free). -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). -- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). +- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, free). -- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +<<<<<<< HEAD +- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) können ebenfalls sehr gut sein, wenn man damit umgehen kann. +======= +- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 -## Let's not argue +## ABER: Die Auswahl des Editors ist keinen Streit wert. -The editors in the lists above are those that either I or my friends whom I consider good developers have been using for a long time and are happy with. +Die Editoren in der Liste wurden entweder von mir oder von Freunden ausgewählt, die ich für gute Entwickler halte und die seit langer Zeit erfolgreich mit den aufgeführten Editoren gearbeitet haben. -There are other great editors in our big world. Please choose the one you like the most. +Aber es gibt natürlich auch andere Editoren in der großen Welt des Internets. Such dir einfach den Editor aus, mit dem du arbeiten möchtest. +<<<<<<< HEAD +Die Wahl des Editors ist, wie bei jedem anderen Werkzeug, eine persönliche Vorliebe und hängt von deinem Projekt, deinen Arbeitsgewohnheiten und persönlichen Vorlieben ab. +======= The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. + +The author's personal opinion: + +- I'd use [Visual Studio Code](https://code.visualstudio.com/) if I develop mostly frontend. +- Otherwise, if it's mostly another language/platform and partially frontend, then consider other editors, such as XCode (Mac), Visual Studio (Windows) or Jetbrains family (Webstorm, PHPStorm, RubyMine etc, depending on the language). +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index 29353fc0b..f92e4645a 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -9,7 +9,11 @@ Als erstes, lass uns sehen wie wir ein Skript einer Webseite hinzufügen. Für e ## Der "script"-Tag +<<<<<<< HEAD JavaScript-Programme können an jeder beliebigen Stelle eines HTML-Dokuments mit Hilfe des ` ``` +<<<<<<< HEAD Hier ist `/pfad/zum/script.js` ein absoluter Pfad zu dem Skript, aus dem Wurzelverzeichnis der Seite. Auch ein relativer Pfad der aktuellen Seite kann angeben werden. Beispielsweise, `src="script.js"` würde eine Datei `"script.js"` im aktuellen Verzeichnis bedeuten. +======= +Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"`, just like `src="./script.js"`, would mean a file `"script.js"` in the current folder. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 Außerdem können wir auch eine komplette URL angeben. Beispielsweise: diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 3e6796451..6abc26cdf 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -46,7 +46,11 @@ alert(3 + + 2); ``` +<<<<<<< HEAD Der Code gibt `6` aus, da JavaScript hier keine Semikolons einfügt. Es ist intuitiv klar, dass, wenn die Zeile mit einem Pluszeichen `"+"` endet, es sich um einen "unvollständigen Ausdruck" handelt, sodass das Semikolon nicht erforderlich ist. Und in diesem Fall funktioniert das wie vorgesehen. +======= +The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 **Es gibt jedoch Situationen, in denen JavaScript ein Semikolon nicht annimmt, wenn es wirklich benötigt wird.** @@ -56,19 +60,31 @@ Fehler, die in solchen Fällen auftreten, sind schwer zu finden und zu beheben. Wenn du ein konkretes Beispiel für einen solchen Fehler sehen möchten, lies den folgenden Code: ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` +<<<<<<< HEAD Über die Bedeutung der Klammern `[]` und `forEach` muss noch nicht nachgedacht werden. Wir werden sie später studieren. Denk vorerst nur an das Ergebnis des Codes: Es zeigt `1`, dann `2`. Fügen wir nun vor dem Code einen `alert` ein und beenden ihn *nicht* mit einem Semikolon: ```js run no-beautify alert("Es wird ein Fehler auftreten") +======= +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`. -[1, 2].forEach(alert) +Now let's remove the semicolon after the `alert`: + +```js run no-beautify +alert("Hello") +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 + +[1, 2].forEach(alert); ``` +<<<<<<< HEAD Wenn wir nun den Code ausführen, wird nur der erste `alert` angezeigt und dann haben wir einen Fehler! Aber alles ist wieder in Ordnung, wenn wir nach `alert` ein Semikolon einfügen: @@ -90,6 +106,23 @@ alert("Es wird ein Fehler auftreten")[1, 2].forEach(alert) ``` Aber es sollten zwei getrennte Anweisungen sein, nicht eine. Eine solche Verschmelzung ist in diesem Fall einfach falsch, daher der Fehler. Dies kann auch in anderen Situationen auftreten. +======= +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. + +If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more. + +That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement. + +Here's how the engine sees it: + +```js run no-beautify +alert("Hello")[1, 2].forEach(alert); +``` + +Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly. + +This can happen in other situations also. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 ```` Es wird empfohlen, Semikolons zwischen Anweisungen zu setzen, auch wenn diese durch Zeilenumbrüche getrennt sind. Diese Regel wird von der Community weitgehend übernommen. Lass uns noch einmal festhalten -- es ist möglich, Semikolons die meiste Zeit wegzulassen. Aber es ist sicherer -- besonders für Anfänger -- sie zu benutzen. diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index e46d8fbac..5aabdbef3 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -6,7 +6,11 @@ Das ist einfach: let ourPlanetName = "Earth"; ``` +<<<<<<< HEAD Beachte, wir könnten einen kürzeren Namen `planet` verwenden, aber es könnte nicht offensichtlich sein, auf welchen Planeten er sich bezieht. Es ist schön, etwas ausführlicher zu sein. Zumindest bis die Variable nicht zu lang ist. +======= +Note, we could use a shorter name `planet`, but it might not be obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 ## Der Name des aktuellen Besuchers diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index eac711d90..710f54c16 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -12,13 +12,24 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` +<<<<<<< HEAD Hier haben wir ein konstantes Geburtsdatum `birthday` und das Alter `age`, welches berechnet wird aus `birthday` mittels eines gewissen Codes (dieser wird der Kürze wegen nicht angegeben und weil Details hier keine Rolle spielen). +======= +Here we have a constant `birthday` for the date, and also the `age` constant. + +The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Wäre es richtig, für `birthday` Großbuchstaben zu verwenden? Für `age`? der sogar für beide ```js +<<<<<<< HEAD const BIRTHDAY = '18.04.1982'; // in Großbuchstaben? const AGE = someCode(BIRTHDAY); // in Großbuchstaben? -``` +======= +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 +``` diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 67291b9e5..8407ef764 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -24,7 +24,11 @@ Nun können wir sie mit Daten befüllen, indem wir den Zuweisungsoperator `=` ve let message; *!* +<<<<<<< HEAD message = 'Hello'; // speichere diese Zeichenkette +======= +message = 'Hello'; // store the string 'Hello' in the variable named message +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 */!* ``` @@ -63,7 +67,11 @@ let age = 25; let message = 'Hello'; ``` +<<<<<<< HEAD Einige Leute definieren auch mehrere Variablen in diesem mehrzeiligen Stil: +======= +Some people also define multiple variables in this multiline style: +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ```js no-beautify let user = 'John', @@ -95,22 +103,37 @@ In older scripts, you may also find another keyword: `var` instead of `let`: *!*var*/!* message = 'Hello'; ``` +<<<<<<< HEAD Das `var` Schlüsselwort ist *fast* dasselbe wie `let`. Es deklariert auch eine Variable, aber auf eine etwas andere, "altbackene" Weise. Es gibt subtile Unterschiede zwischen `let` und `var`, aber sie sind für uns noch nicht wichtig. Wir werden sie im Kapitel ausführlich behandeln. +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d ```` ## Eine Analogie aus dem wirklichen Leben Wir können das Konzept einer "Variablen" leicht verstehen, wenn wir sie uns als eine "Kiste" für Daten vorstellen, mit einem eindeutig benannten Aufkleber darauf. +<<<<<<< HEAD Zum Beispiel kann man sich die Variable `message` als eine Kiste vorstellen mit der Bezeichnung `"message"` und dem Wert `"Hello!"` darin: +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d ![](variable.svg) Wir können jeden Wert in die Kiste legen. +<<<<<<< HEAD Wir können den Wert auch so oft ändern, wie wir wollen: +======= +We can also change it as many times as we want: + +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ```js run let message; @@ -161,12 +184,20 @@ So, we should declare a variable once and then refer to it without `let`. ```` ```smart header="Functional languages" +<<<<<<< HEAD It's interesting to note that there exist [functional](https://en.wikipedia.org/wiki/Functional_programming) programming languages, like [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/) that forbid changing variable values. >>>>>>> d35baee32dcce127a69325c274799bb81db1afd8 +======= +It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 In solchen Sprachen ist der Wert, sobald er "in der Kiste" gespeichert ist, für immer da. Wenn wir etwas anderes speichern wollen, zwingt uns die Sprache dazu, eine neue Kiste zu erstellen (eine neue Variable zu deklarieren). Wir können die alte nicht wiederverwenden. +<<<<<<< HEAD Auch wenn es auf den ersten Blick etwas seltsam erscheint, sind diese Sprachen durchaus für seriöse Softwareentwicklung geeignet. Mehr noch, es gibt Bereiche wie Parallelberechnungen, in denen diese Einschränkung gewisse Vorteile bringt. Das Studium einer solchen Sprache (auch wenn man nicht vorhat, sie bald zu benutzen) wird empfohlen, um den Geist zu erweitern. +======= +Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ``` ## Benennen der Variablen [#variable-naming] @@ -204,23 +235,40 @@ let 1a; // kann nicht mit einer Ziffer beginnen let my-name; // Bindestriche '-' sind im Namen nicht erlaubt ``` +<<<<<<< HEAD ```smart header="Groß- und Kleinschreibung" Variablen mit den Namen `apple` und `AppLE` sind zwei unterschiedliche Variablen. ``` ````smart header="nicht-lateinische Buchstaben sind erlaubt, aber nicht empfohlen" Es ist möglich, jede Sprache, einschließlich kyrillischer Buchstaben oder sogar chinesische Schriftzeichen, wie diese zu verwenden: +======= +```smart header="Case matters" +Variables named `apple` and `APPLE` are two different variables. +``` + +````smart header="Non-Latin letters are allowed, but not recommended" +<<<<<<< HEAD +It is possible to use any language, including cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 +======= +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d ```js let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD <<<<<<< HEAD Technisch gesehen gibt es hier keinen Fehler, solche Namen sind erlaubt, aber es gibt eine internationale Tradition, Englisch in Variablennamen zu verwenden. Selbst wenn wir ein kleines Script schreiben, kann es ein langes Leben vor sich haben. Menschen aus anderen Ländern müssen es vielleicht irgendwann einmal lesen. ======= Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it some time. >>>>>>> d35baee32dcce127a69325c274799bb81db1afd8 +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d ```` ````warn header="Reservierte Namen" @@ -275,12 +323,24 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // Fehler, Konstante kann nicht neu zugewiesen werden! ``` +<<<<<<< HEAD Wenn ein Programmierer sicher ist, dass eine Variable sich nie ändern wird, kann er sie mit `const` deklarieren, um diese Tatsache zu garantieren und jedem klar zu kommunizieren. +<<<<<<< HEAD +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d ### Konstanten in Großbuchstaben +======= +### Uppercase constants +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 +<<<<<<< HEAD Es ist eine weit verbreitete Vorgehensweise, Konstanten als Alias für schwer zu merkende Werte zu verwenden, die bereits vor der Ausführung bekannt sind. +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d Solche Konstanten werden mit Großbuchstaben und Unterstrichen benannt. @@ -305,16 +365,33 @@ Vorteile: Wann sollten wir Großbuchstaben für eine Konstante verwenden und wann sollten wir sie normal benennen? Lass uns das klarstellen. +<<<<<<< HEAD Eine "Konstante" zu sein bedeutet nur, dass sich der Wert einer Variablen nie ändert. Aber es gibt Konstanten, die vor der Ausführung bekannt sind (wie ein hexadezimaler Wert für die Farbe rot) und es gibt Konstanten, die zur Laufzeit, also während der Ausführung, *berechnet* werden, sich aber nach ihrer anfänglichen Zuweisung nicht mehr ändern. +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d +<<<<<<< HEAD Zum Beispiel: +======= +For instance: + +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ```js const pageLoadTime = /* Zeit, die eine Website braucht, um geladen zu werden */; ``` +<<<<<<< HEAD Der Wert von `pageLoadTime` ist vor dem Laden der Seite nicht bekannt, daher wird er normal benannt. Aber es ist immer noch eine Konstante, weil er sich nach der Zuweisung nicht mehr ändert. +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d +<<<<<<< HEAD Mit anderen Worten, großgeschriebene Konstanten werden nur als Aliase für "hart kodierte" Werte verwendet. +======= +In other words, capital-named constants are only used as aliases for "hard-coded" values. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ## Dinge richtig benennen @@ -322,18 +399,31 @@ Apropos Variablen, es gibt noch eine extrem wichtige Sache. Ein Variablenname sollte eine saubere, offensichtliche Bedeutung haben, die die Daten beschreibt, die er speichert. +<<<<<<< HEAD Die Benennung von Variablen ist eine der wichtigsten und komplexesten Fähigkeiten in der Programmierung. Ein schneller Blick auf Variablennamen kann zeigen, welcher Code von einem Anfänger im Gegensatz zu einem erfahrenen Entwickler geschrieben wurde. In einem echten Projekt wird die meiste Zeit damit verbracht, eine bestehende Codebasis zu modifizieren und zu erweitern, anstatt etwas völlig Neues zu schreiben. Wenn wir zu irgendeinem Code zurückkehren, nachdem wir eine Weile etwas anderes gemacht haben, ist es viel einfacher Informationen zu finden, die gut beschriftet sind. Oder, mit anderen Worten, wenn die Variablen gute Namen haben. +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d Bitte denk über den richtigen Namen für eine Variable nach, bevor du sie deklarierst. Das wird sich ordentlich auszahlen. Einige Regeln, die gut zu befolgen sind: +<<<<<<< HEAD - Verwende menschenlesbare Namen, wie `userName` oder `shoppingCart`. - Halte dich fern von Abkürzungen oder Kürzel wie `a`, `b`, `c`, es sei denn, du weißt wirklich, was du tust. - Mach Namen maximal beschreibend und prägnant. Beispiele für schlechte Namen sind `data` und `value`. Solche Namen sagen nichts aus. Es ist nur in Ordnung, sie zu benutzen, wenn der Kontext des Codes es außergewöhnlich offensichtlich macht, auf welche Daten oder Werte die Variable verweist. - Mach dir mit dir selbst und deinem Team Bedingungen aus. Wenn ein Website Besucher "user" genannt wird, dann sollten verwandte Variablen `currentUser` oder `newUser` heißen, anstatt `currentVisitor` oder `newManInTown`. +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d Klingt einfach? Ist es auch, aber die Erstellung von beschreibenden und prägnanten Variablennamen ist es in der Praxis nicht. Nur zu. diff --git a/1-js/02-first-steps/04-variables/variable-change.svg b/1-js/02-first-steps/04-variables/variable-change.svg index 427a6388c..1b2679238 100644 --- a/1-js/02-first-steps/04-variables/variable-change.svg +++ b/1-js/02-first-steps/04-variables/variable-change.svg @@ -1 +1 @@ -"World!""Hello!"message \ No newline at end of file +"World!""Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/04-variables/variable.svg b/1-js/02-first-steps/04-variables/variable.svg index 5d15c9e4e..1c3d8b0cb 100644 --- a/1-js/02-first-steps/04-variables/variable.svg +++ b/1-js/02-first-steps/04-variables/variable.svg @@ -1 +1 @@ -"Hello!"message \ No newline at end of file +"Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 2c36d8c06..f3550cab5 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -46,6 +46,7 @@ Neben regulären Zahlen gibt es sogenannte "numerische Sonderwerte", die ebenfal alert( "keine Zahl" / 2 ); // NaN, eine solche Teilung ist falsch ``` +<<<<<<< HEAD `NaN` ist starr. Jede weitere Operation an `NaN` gibt` NaN` zurück: ```js run @@ -53,6 +54,17 @@ Neben regulären Zahlen gibt es sogenannte "numerische Sonderwerte", die ebenfal ``` Wenn sich also irgendwo in einem mathematischen Ausdruck ein `NaN` befindet, wird es zum gesamten Ergebnis weitergegeben. +======= + `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`: + + ```js run + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "not a number" / 2 - 1 ); // NaN + ``` + + So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result (there's only one exception to that: `NaN ** 0` is `1`). +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```smart header="Mathematische Operationen sind sicher" Mathematik ist in JavaScript "sicher". Wir können alles tun: durch Null dividieren, nicht numerische Zeichenfolgen als Zahlen behandeln usw. @@ -64,11 +76,32 @@ Spezielle numerische Werte gehören formal zum Typ "number". Natürlich sind sie Mehr über das Arbeiten mit Zahlen erfahren wir in diesem Kapitel . -## BigInt +## BigInt [#bigint-type] +<<<<<<< HEAD +<<<<<<< HEAD In JavaScript kann der Typ "Zahl" keine ganzzahligen Werte darstellen, die größer als (253-1) (das ist `9007199254740991`) oder kleiner als -(-253-1) für Negative sind. Es handelt sich um eine technische Einschränkung, die durch ihre interne Darstellung bedingt ist. +======= +In JavaScript, the "number" type cannot represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. It's a technical limitation caused by their internal representation. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 Für die meisten Zwecke reicht das völlig aus, aber manchmal brauchen wir wirklich große Zahlen, z.B. für die Kryptographie oder Zeitstempel mit Mikrosekunden-Genauigkeit. +======= +In JavaScript, the "number" type cannot safely represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. + +To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308), but outside of the safe integer range ±(253-1) there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored. + +For example, these two numbers (right above the safe range) are the same: + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +So to say, all odd integers greater than (253-1) can't be stored at all in the "number" type. + +For most purposes ±(253-1) range is quite enough, but sometimes we need the entire range of really big integers, e.g. for cryptography or microsecond-precision timestamps. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Der Typ `BigInt` wurde kürzlich der Sprache hinzugefügt, um Ganzzahlen beliebiger Länge darzustellen. @@ -81,10 +114,21 @@ const bigInt = 1234567890123456789012345678901234567890n; Da `BigInt`-Zahlen selten benötigt werden, behandeln wir sie hier nicht, sondern widmen ihnen ein eigenes Kapitel . Lies es, wenn du so große Zahlen brauchst. +<<<<<<< HEAD +<<<<<<< HEAD ```smart header="Compatability issues" Im Moment wird `BigInt` in Firefox/Chrome/Edge unterstützt, aber nicht in Safari/IE. +======= + +```smart header="Compatibility issues" +Right now, `BigInt` is supported in Firefox/Chrome/Edge/Safari, but not in IE. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 ``` +You can check [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) to know which versions of a browser are supported. + +======= +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d ## String Ein String in JavaScript muss in Anführungszeichen gesetzt werden. @@ -127,7 +171,11 @@ In diesem Kapitel werden wir uns eingehender mit Strings befassen . ```smart header="Es gibt keinen *Zeichen*-Typ." In einigen Sprachen gibt es einen speziellen "Zeichen"-Typ für ein einzelnes Zeichen. In der C-Sprache und in Java heißt er beispielsweise "char". +<<<<<<< HEAD In JavaScript gibt es so einen Typ nicht. Es gibt nur einen Typ: `string`. Eine Zeichenfolge kann aus nur einem oder mehreren Zeichen bestehen. +======= +In JavaScript, there is no such type. There's only one type: `string`. A string may consist of zero characters (be empty), one character or many of them. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 ``` ## Boolean (logische Werte) @@ -209,7 +257,12 @@ Der Typ `symbol` wird verwendet, um eindeutige Bezeichner für Objekte zu erstel Der Operator `typeof` gibt den Typ des Arguments zurück. Dies ist nützlich, wenn wir Werte verschiedener Typen unterschiedlich verarbeiten oder nur eine schnelle Überprüfung durchführen möchten. +<<<<<<< HEAD +<<<<<<< HEAD Es werden zwei Syntaxformen unterstützt: +======= +The `typeof` operator returns the type of the operand. It's useful when we want to process values of different types differently or just want to do a quick check. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 1. Als Operator: `typeof x`. 2. Als Funktion: `typeof(x)`. @@ -217,6 +270,9 @@ Es werden zwei Syntaxformen unterstützt: Mit anderen Worten, es funktioniert mit oder ohne Klammern. Das Ergebnis ist das Gleiche. Der Aufruf von `typeof x` gibt einen String mit dem Typ zurück: +======= +A call to `typeof x` returns a string with the type name: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js typeof undefined // "undefined" @@ -246,14 +302,34 @@ typeof alert // "function" (3) Die letzten drei Zeilen bedürfen möglicherweise einer zusätzlichen Erläuterung: +<<<<<<< HEAD 1. `Math` ist ein eingebautes Objekt, das mathematische Operationen liefert. Wir werden es im Kapitel lernen. Hier dient es nur als Beispiel für ein Objekt. 2. Das Ergebnis von `typeof null` ist `"object"`. Das ist ein offiziell anerkannter Fehler im `typeof`-Verhalten, der aus den frühen Tagen von JavaScript stammt und aus Kompatibilitätsgründen beibehalten wurde. Definitiv ist `null` kein Objekt. Es ist ein besonderer Wert mit einem eigenen Typ. 3. Das Ergebnis von `typeof alert` ist `"function"`, denn `alert` ist eine Funktion. Wir werden Funktionen in den nächsten Kapiteln untersuchen, wo wir auch sehen werden, dass es keinen speziellen "Funktionstyp" in JavaScript gibt. Funktionen gehören zum Objekttyp. Aber `typeof` behandelt sie anders und gibt `"function"` zurück. Auch das stammt aus den frühen Tagen von JavaScript. Technisch gesehen ist ein solches Verhalten nicht korrekt, kann aber in der Praxis bequem sein. ## Zusammenfassung +======= +1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object. +2. The result of `typeof null` is `"object"`. That's an officially recognized error in `typeof`, coming from very early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own. The behavior of `typeof` is wrong here. +3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice. + +```smart header="The `typeof(x)` syntax" +You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`. + +To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping. + +Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it. + +Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common. +``` + +## Summary +<<<<<<< HEAD +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Es gibt 8 grundlegende Datentypen in JavaScript. +<<<<<<< HEAD - `number` für Zahlen jeglicher Art: Ganzzahl oder Gleitkommazahl, ganze Zahlen sind auf ±253 begrenzt. - `bigint` steht für ganze Zahlen beliebiger Länge. - `string` für Zeichenketten. Eine String kann aus null oder mehreren Zeichen bestehen. Es gibt keine separaten Einzelzeichentyp. @@ -262,11 +338,42 @@ Es gibt 8 grundlegende Datentypen in JavaScript. - `undefined` für nicht zugewiesene Werte -- ein eigenständiger Typ mit einem einzelnen Wert `undefined`. - `object` für komplexere Datenstrukturen. - `symbol` für eindeutige Kennungen. +======= +- `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1). +- `bigint` is for integer numbers of arbitrary length. +- `string` for strings. A string may have zero or more characters, there's no separate single-character type. +- `boolean` for `true`/`false`. +- `null` for unknown values -- a standalone type that has a single value `null`. +- `undefined` for unassigned values -- a standalone type that has a single value `undefined`. +- `object` for more complex data structures. +- `symbol` for unique identifiers. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 +======= + +There are 8 basic data types in JavaScript. + +- Seven primitive data types: + - `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1). + - `bigint` for integer numbers of arbitrary length. + - `string` for strings. A string may have zero or more characters, there's no separate single-character type. + - `boolean` for `true`/`false`. + - `null` for unknown values -- a standalone type that has a single value `null`. + - `undefined` for unassigned values -- a standalone type that has a single value `undefined`. + - `symbol` for unique identifiers. +- And one non-primitive data type: + - `object` for more complex data structures. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Mit dem Operator `typeof` können wir sehen, welcher Typ in einer Variablen gespeichert ist. +<<<<<<< HEAD - Zwei Formen: `typeof x` oder `typeof(x)`. - Gibt einen String mit dem Namen des Typs zurück, wie `"string"`. - Für `null` gibt es `"object"` zurück -- dies ist ein Fehler in der Sprache, es ist eigentlich kein Objekt. +======= +- Usually used as `typeof x`, but `typeof(x)` is also possible. +- Returns a string with the name of the type, like `"string"`. +- For `null` returns `"object"` -- this is an error in the language, it's not actually an object. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 In den nächsten Kapiteln werden wir uns auf skalare Datentypen konzentrieren und, sobald wir mit ihnen vertraut sind, zu Objekten übergehen. diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md index ca2cd8b6c..f94e7ab31 100644 --- a/1-js/02-first-steps/06-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -4,7 +4,11 @@ Da wir den Browser als unsere Demo-Umgebung verwenden, wollen wir einige Funktio ## alert +<<<<<<< HEAD Diese haben wir bereits gesehen. Es zeigt eine Meldung an und wartet darauf, dass der Benutzer "OK" drückt. +======= +This one we've seen already. It shows a message and waits for the user to press "OK". +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 Zum Beispiel: @@ -30,8 +34,13 @@ Es wir ein modales Fenster mit einer Meldung angezeigt, ein Eingabefeld für den `default` : Ein optionaler zweiter Parameter, der vor-befüllte Wert für das Eingabefeld. +<<<<<<< HEAD ```smart header="Eckige Klammer in der Syntax `[...]`" Die eckigen Klammern um `default` in der obigen Syntax bedeuten, dass der Parameter optional, nicht erforderlich ist. +======= +```smart header="The square brackets in syntax `[...]`" +The square brackets around `default` in the syntax above denote that the parameter is optional, not required. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 ``` Der Besucher kann etwas in das Eingabefeld eingeben und OK drücken. Dann erhalten wir diesen Text im `Ergebnis`. Oder er kann die Eingabe beenden, indem er Abbrechen drückt oder `key:Esc` drückt, dann erhalten wir `Null` als `Ergebnis`. diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index 14a040f74..ddde02968 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -6,8 +6,13 @@ Zum Beispiel wandelt `alert` automatisch jeden Wert in den Datentyp "String" um Es gibt auch Fälle, in denen wir einen Wert explizit in den erwarteten Datentyp umwandeln müssen. +<<<<<<< HEAD ```smart header="Noch nicht von Objekten sprechen" In diesem Kapitel werden wir keine Objekte behandeln. Im Moment werden wir nur über Primitive sprechen. +======= +```smart header="Not talking about objects yet" +In this chapter, we won't cover objects. For now, we'll just be talking about primitives. +>>>>>>> 291b5c05b99452cf8a0d32bd32426926dbcc0ce0 Später, nachdem wir etwas über Objekte gelernt haben, werden wir im Kapitel sehen, wie Objekte hineinpassen. ``` @@ -34,7 +39,11 @@ Die String Umwandlung ist meist offensichtlich. Ein `false` wird zu `"false"`, ` ## Numerische Umwandlungen +<<<<<<< HEAD Numerische Umwandlung passiert automatisch in mathematischen Funktionen und Ausdrücken. +======= +Numeric conversion in mathematical functions and expressions happens automatically. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Zum Beispiel, wenn die Division `/` an Werten angewendet wird, die keine Zahlen ("Number") sind: @@ -69,8 +78,13 @@ Regeln zur numerischen Umwandlung: |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| +<<<<<<< HEAD |true und false | `1` und `0` | | `string` | Leerzeichen am Anfang und am Ende werden entfernt. Wenn der verbleibende String leer ist, ist das Ergebnis `0`. Andernfalls wird die Zahl aus dem String "gelesen". Ein Fehler ergibt `NaN`. | +======= +|true and false | `1` and `0` | +| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Beispiele: @@ -130,7 +144,11 @@ Die Umwandlung folgt diesen Regeln: |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | +<<<<<<< HEAD | `string` | Der String wird "als solches" gelesen, Leerzeichen auf beiden Seiten werden ignoriert. Ein leerer String wird zu `0`. Ein Fehler ergibt `NaN`. | +======= +| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 **`Boolean Umwandlung`** -- Tritt in logischen Operationen auf. Kann mit `Boolean(value)` explizit ausgeführt werden. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index 675aa72ba..4e05ffe74 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -9,7 +9,6 @@ true + false = 1 "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN -7 / 0 = Infinity " -9 " + 5 = " -9 5" // (3) " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) @@ -17,6 +16,7 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` +<<<<<<< HEAD 1. Die Addition mit einem String `"" + 1` konvertiert `1` in einen String: `"" + 1 = "1"`, und dann haben wir `"1" + 0`, die gleiche Regel wird angewendet. 2. Die Subtraktion `-` funktioniert (wie die meisten mathematischen Operationen) nur mit Zahlen. Sie konvertiert eine leere Zeichenfolge `""` in `0`. 3. Die Addition mit einem String fügt dem String die Zahl `5` hinzu. @@ -24,3 +24,12 @@ undefined + 1 = NaN // (6) 5. `null` wird nach der numerischen Umwandlung zu `0`. 6. `undefined` wird nach der numerischen Umwandlung zu `NaN`. 7. Leerzeichen werden am Anfang und Ende eines Strings abgeschnitten, wenn ein String in eine Zahl umgewandelt wird. Hier besteht der gesamte String aus Leerzeichen wie `\t`, `\n` und einem "regulären" Leerzeichen dazwischen. Ähnlich wie bei einem leeren String wird sie zu `0`. +======= +1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied. +2. The subtraction `-` (like most math operations) only works with numbers, it converts an empty string `""` to `0`. +3. The addition with a string appends the number `5` to the string. +4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). +5. `null` becomes `0` after the numeric conversion. +6. `undefined` becomes `NaN` after the numeric conversion. +7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index 5755e4a32..e19c3c528 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md index 04f73fbd0..209a0702c 100644 --- a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md @@ -9,7 +9,7 @@ let b = "2"; // prompt("Second number?", 2); alert(a + b); // 12 ``` -What we should to is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`. +What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`. For example, right before `prompt`: diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 7943b0ec2..0653647b5 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -50,23 +50,40 @@ Das Ergebnis von `a % b` ist der [Rest](https://en.wikipedia.org/wiki/Remainder) Zum Beispiel: ```js run +<<<<<<< HEAD alert( 5 % 2 ); // 1, der Rest von 5 geteilt durch 2 alert( 8 % 3 ); // 2, der Rest von 8 geteilt durch 3 +======= +alert( 5 % 2 ); // 1, the remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, the remainder of 8 divided by 3 +alert( 8 % 4 ); // 0, the remainder of 8 divided by 4 +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ``` ### Potenzierung ** + Der Exponentiationsoperator `a ** b` multipliziert `a` mit sich selbst `b` mal. + + Zum Beispiel: ```js run + alert( 2 ** 2 ); // 4 (2 mit sich selbst 2 mal multipliziert) alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 mal) alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 mal) + ``` -Mathematically, the exponentiation is defined for non-integer numbers as well. For example, a square root is an exponentiation by `1/2`: +<<<<<<< HEAD +Genau wie in der Mathematik ist der Potenzierungsoperator auch für nicht-ganzzahlige Zahlen definiert. +======= +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 + +Zum Beispiel ist eine Quadratwurzel eine Potenzierung durch ½: ```js run alert( 4 ** (1/2) ); // 2 (Die Potenz von 1/2 ist gleich der Quadratwurzel) @@ -76,7 +93,11 @@ alert( 8 ** (1/3) ); // 2 (Die Potenz von 1/3 ist gleich der dritten Wurzel) ## Binäre String-Verkettung + +<<<<<<< HEAD Lernen wir Funktionen von JavaScript-Operatoren kennen, die über das schulische Rechnen hinausgehen. +======= +Let's meet the features of JavaScript operators that are beyond school arithmetics. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 Normalerweise summiert der Plus-Operator `+` Zahlen. @@ -104,8 +125,15 @@ Hier ist ein komplexeres Beispiel: alert(2 + 2 + '1' ); // "41" und nicht "221" ``` + Dabei arbeiten die Operatoren einer nach dem anderen. Das erste `+` summiert zwei Zahlen, so dass es `4` zurückgibt, dann fügt das nächste `+` die Zeichenkette `1` hinzu, so dass es `4 + '1' = 41` ergibt. + +```js run +alert('1' + 2 + 2); // "122" and not "14" +``` + + Das binäre `+` ist der einzige Operator, der Zeichenketten auf diese Weise unterstützt. Andere arithmetische Operatoren arbeiten nur mit Zahlen und konvertieren ihre Operanden immer in Zahlen. Hier ist die Darstellung für Subtraktion und Division: @@ -180,11 +208,15 @@ Klammern überschreiben alle Prioritäten. Wenn wir mit der Standardreihenfolge In JavaScript gibt es viele Operatoren. Jeder Operator hat eine entsprechende Vorrangsnummer. Der mit der größeren Nummer wird zuerst ausgeführt. Bei gleicher Rangfolge erfolgt die Ausführung von links nach rechts. + Hier ist ein Auszug aus der [Ranglistentabelle](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (Du musst dir das nicht merken, aber beachte, dass unäre Operatoren höher sind als entsprechende binäre): + | Vorrang | Name | Zeichen | |------------|------|------| | ... | ... | ... | +<<<<<<< HEAD + | 17 | Unäres Plus | `+` | | 17 | Unäres Minus | `-` | | 16 | Potenzierung | `**` | @@ -192,15 +224,34 @@ Hier ist ein Auszug aus der [Ranglistentabelle](https://developer.mozilla.org/de | 15 | Division | `/` | | 13 | Addition | `+` | | 13 | Subtraktion | `-` | +======= +| 14 | unary plus | `+` | +| 14 | unary negation | `-` | +| 13 | exponentiation | `**` | +| 12 | multiplication | `*` | +| 12 | division | `/` | +| 11 | addition | `+` | +| 11 | subtraction | `-` | +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 | ... | ... | ... | | 3 | Zuweisung | `=` | | ... | ... | ... | +<<<<<<< HEAD Wie wir sehen können, hat das "unäre Plus" eine Priorität von `17`, die höher ist als die `13` der "Addition" (binäres Plus). Deshalb wirken in dem Ausdruck `"+apples + +oranges"` unäre Pluszeichen vor der Addition. + +======= +As we can see, the "unary plus" has a priority of `14` which is higher than the `11` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 + + ## Zuweisung -Beachten wir, dass eine Zuweisung `=` auch ein Operator ist. Es ist in der Ranglistentabelle mit der sehr niedrigen Priorität `3` aufgeführt. + +Beachten wir, dass eine Zuweisung `=` auch ein Operator ist. Sie ist in der Ranglistentabelle mit der sehr niedrigen Priorität `3` aufgeführt. + + Wenn wir also eine Variable wie `x = 2 * 2 + 1` zuweisen, werden zuerst die Berechnungen durchgeführt und dann das `=` ausgewertet, wobei das Ergebnis in `x` gespeichert wird. @@ -214,7 +265,11 @@ alert( x ); // 5 Die Tatsache, dass `=` ein Operator und kein "magisches" Sprachkonstrukt ist, hat eine interessante Implikation. -Die meisten Operatoren in JavaScript geben einen Wert zurück. Das ist bei `+` und `-` offensichtlich, gilt aber auch für `=`. + +Die meisten Operatoren in JavaScript geben einen Wert zurück. Das ist bei `+` und `-` offensichtlich, dies gilt aber auch für `=`. + + + Der Aufruf `x = Wert` schreibt den `Wert` in `x` *und gibt ihn dann zurück*. @@ -232,11 +287,11 @@ alert( a ); // 3 alert( c ); // 0 ``` -Im obigen Beispiel ist das Ergebnis des Ausdrucks `(a = b + 1)` der Wert, der `a` zugewiesen wurde (d.h. `3`). Es wird dann für weitere Auswertungen verwendet. +Im obigen Beispiel ist das Ergebnis des Ausdrucks `(a = b + 1)` der Wert, der `a` zugewiesen wurde (d.h. `3`). Dieser Wert wird dann für weitere Auswertungen verwendet. -Seltsamer Code, nicht wahr? Wir sollten verstehen, wie er funktioniert, denn manchmal sehen wir ihn in JavaScript-Bibliotheken. +Seltsamer Code, nicht wahr? Wir sollten uns über seine Funktionalität klar machen, denn Code wie dieser kommt in JavaScript-Bibliotheken vor. -Trotzdem, bitte schreibe Code nicht so. Solche Tricks machen Code bestimmt nicht klarer oder leserlicher. +Dennoch, bitte schreibe keinen Code auf diese Weise. Tricks wie diese machen Code weder klarer noch leserlicher. ### Verkettung von Zuweisungen @@ -256,7 +311,7 @@ alert( c ); // 4 Verkettete Zuweisungen werden von rechts nach links ausgewertet. Zuerst wird der ganz rechte Ausdruck `2 + 2` ausgewertet und dann den Variablen auf der linken Seite zugewiesen: `c`, `b` und `a`. Am Ende teilen sich alle Variablen einen einzigen Wert. -Noch einmal: Aus Gründen der Lesbarkeit ist es besser, solchen Code in wenige Zeilen aufzuteilen: +Auch hier gilt: Aufgrund der Lesbarkeit ist es besser, solchen Code in wenige Zeilen aufzuteilen: ```js c = 2 + 2; @@ -267,7 +322,7 @@ Das ist leichter zu lesen, besonders wenn man den Code mit den Augen schnell sca ## An Ort und Stelle modifizieren -Wir müssen oft einen Operator auf eine Variable anwenden und das neue Ergebnis in derselben Variable speichern. +Häufig müssen wir einen Operator auf eine Variable anwenden und das neue Ergebnis in derselben Variable speichern. Zum Beispiel: @@ -277,7 +332,7 @@ n = n + 5; n = n * 2; ``` -Diese Notation kann mit den Operatoren `+=` und `*=` gekürzt werden: +Diese Notation kann man mit den Operatoren `+=` und `*=` abkürzen: ```js run let n = 2; @@ -289,14 +344,18 @@ alert( n ); // 14 Für alle arithmetischen und bitweisen Operatoren gibt es kurze "modifizieren-und-zuweisen"-Operatoren: `/=`, `-=`, usw. -Solche Operatoren haben den gleichen Stellenwert wie eine normale Zuweisung, so dass sie nach den anderen Berechnungen durchgeführt werden: +Solche Operatoren haben den gleichen Stellenwert wie eine normale Zuweisung, sodass sie nach den anderen Berechnungen durchgeführt werden: ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // right part evaluated first, same as n *= 8 +<<<<<<< HEAD alert( n ); // 16 (der rechte Teil wird zuerst ausgewertet, wie n *= 8) +======= +alert( n ); // 16 +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 ``` ## Inkrementieren/Dekrementieren @@ -335,18 +394,18 @@ Beide Anweisungen tun dasselbe: erhöhen `counter` um `1`. Gibt es da einen Unterschied? Ja, aber wir können es nur sehen, wenn wir den zurückgegebenen Wert von `++/-` verwenden. -Lass uns klären. Wie wir wissen, geben alle Operatoren einen Wert zurück. Inkrementieren/Dekrementieren ist keine Ausnahme. Die Präfix-Form gibt den neuen Wert zurück, während die Postfix-Form den alten Wert zurück gibt (vor dem Inkrementieren/Dekrementieren). +Lass es uns aufklären. Wie wir wissen, geben alle Operatoren einen Wert zurück. Inkrementieren/Dekrementieren ist keine Ausnahme. Die Präfix-Form gibt den neuen Wert zurück, während die Postfix-Form den alten Wert zurück gibt (vor dem Inkrementieren/Dekrementieren). -Um den Unterschied zu sehen, hier ein Beispiel: +Um den Unterschied zu erkennen, hier ein Beispiel: ```js run let counter = 1; let a = ++counter; // (*) -alert(a); // *!*2*/!* +alert(a); // 2 ``` -In der Zeile `(*)` erhöht die *Präfix*-Form von `++counter` den Wert von `counter` und gibt den neuen Wert `2` zurück. Der `alert` zeigt also `2`. +In der Zeile `(*)` erhöht die *Präfix*-Form von `++counter` den Wert von `counter` und gibt den neuen Wert `2` zurück. Der `alert` gibt also `2` zurück. Verwenden wir nun die Postfix-Form: @@ -354,10 +413,10 @@ Verwenden wir nun die Postfix-Form: let counter = 1; let a = counter++; // (*) ++counter zu counter++ geändert -alert(a); // *!*1*/!* +alert(a); // 1 ``` -In der Zeile `(*)` erhöht die *Postfix*-Form `counter++` ebenfalls `counter`, gibt aber den *alten* Wert (vor dem Inkrementieren) zurück. Der `alert` zeigt also `1`. +In der Zeile `(*)` erhöht die *Postfix*-Form `counter++` ebenfalls den Wert von `counter`, gibt aber den *alten* Wert (vor dem Inkrementieren) zurück. Der `alert` gibt also `1` zurück. Zusammenfassend: @@ -369,13 +428,13 @@ Zusammenfassend: ++counter; alert( counter ); // 2, die obigen Zeilen haben dasselbe getan ``` -- Wenn wir einen Wert erhöhen *und* sofort das Ergebnis des Operators verwenden möchten, benötigen wir die Präfix-Formu: +- Wenn wir einen Wert erhöhen *und* sofort das Ergebnis des Operators verwenden möchten, benötigen wir die Präfix-Form: ```js run let counter = 0; alert( ++counter ); // 1 ``` -- Wenn wir einen Wert erhöhen, aber seinen vorherigen Wert verwenden möchten, benötigen wir die Postfix-Form: +- Wenn wir einen Wert erhöhen, aber seinen vorherigen Wert noch verwenden möchten, benötigen wir die Postfix-Form: ```js run let counter = 0; @@ -399,7 +458,7 @@ let counter = 1; alert( 2 * counter++ ); // 2, weil counter ++ den "alten" Wert zurückgibt ``` -Obwohl technisch in Ordnung, macht eine solche Notation Code normalerweise weniger lesbar. Eine Zeile macht mehrere Dinge -- nicht gut. +Obwohl dies technisch in Ordnung ist, macht eine solche Notation Code normalerweise unlesbarer. Eine Zeile in der mehrere Operationen ausgeführt werden -- nicht gut. Beim Lesen von Code kann ein schneller "vertikaler" Augenscan leicht etwas wie `counter++` übersehen, und es ist nicht offensichtlich, dass die Variable größer wird. @@ -428,13 +487,20 @@ Die Liste der Operatoren: - Rechtsverschiebung ( `>>` ) - Null füllende Rechtsverschiebung ( `>>>` ) -Diese Operatoren werden sehr selten verwendet, wenn wir mit Zahlen auf der untersten (bitweisen) Ebene herumspielen müssen. Wir werden diese Operatoren in absehbarer Zeit nicht brauchen, da die Webentwicklung kaum Gebrauch von ihnen macht, aber in einigen speziellen Bereichen, wie der Kryptographie, sind sie nützlich. Bei bedarf kannst du den Artikel [Bitweise Operatoren](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Bitwise_Operatoren) auf MDN lesen. + +Diese Operatoren werden sehr selten verwendet, z.B. wenn wir mit Zahlen auf der untersten (bitweisen) Ebene herumspielen müssen. Wir werden diese Operatoren in absehbarer Zeit nicht brauchen, da in der Webentwicklung kaum Gebrauch von ihnen gemacht wird, aber in einigen speziellen Bereichen, wie der Kryptographie, sind sie nützlich. Bei bedarf kannst du den Artikel [Bitweise Operatoren](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Bitwise_Operatoren) auf MDN lesen. + + +<<<<<<< HEAD +======= +These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) chapter on MDN when a need arises. +>>>>>>> 291b5c05b99452cf8a0d32bd32426926dbcc0ce0 ## Komma Der Komma-Operator `,` ist einer der seltensten und ungewöhnlichsten Operatoren. Manchmal wird damit kürzerer Code geschrieben, sodass wir ihn kennen müssen, um zu verstehen, was vor sich geht. -Mit dem Komma-Operator können wir mehrere Ausdrücke auswerten und durch ein Komma `,` trennen. Jeder von ihnen wird ausgewertet, aber nur das Ergebnis des letzten wird zurückgegeben. +Mit dem Komma-Operator können wir mehrere Ausdrücke auswerten und sie durch ein Komma `,` trennen. Jeder von ihnen wird ausgewertet, aber nur das Ergebnis des letzten Ausdrucks wird zurückgegeben. Zum Beispiel: @@ -449,14 +515,14 @@ alert( a ); // 7 (das Ergebnis von 3 + 4) Hier wird der erste Ausdruck `1 + 2` ausgewertet und sein Ergebnis verworfen. Dann wird `3 + 4` ausgewertet und als Ergebnis zurückgegeben. ```smart header="Komma hat eine sehr geringe Priorität" -Bitte beachten Sie, dass der Komma-Operator eine sehr niedrige Priorität hat, niedriger als `=`, daher sind Klammern im obigen Beispiel wichtig. +Bitte beachte, dass der Komma-Operator eine sehr niedrige Priorität hat, niedriger als `=`, daher sind Klammern im obigen Beispiel wichtig. -Ohne sie: `a = 1 + 2, 3 + 4` wertet zuerst `+` aus, summiert die Zahlen zu `a = 3, 7`, dann weist der Zuweisungsoperator `=` `a = 3` zu, und der Rest ist ignoriert. Es ist wie `(a = 1 + 2), 3 + 4`. +Ohne Klammern: `a = 1 + 2, 3 + 4` wertet zuerst `+` aus, summiert die Zahlen zu `a = 3, 7`, dann weist der Zuweisungsoperator `=` `a = 3` zu, der Rest wird ignoriert. Es ist wie `(a = 1 + 2), 3 + 4`. ``` -Warum brauchen wir einen Operator, der alles außer dem letzten Ausdruck wegwirft? +Wozu brauchen wir einen Operator, der alles außer dem letzten Ausdruck verwirft? -Manchmal wird es in komplexeren Konstrukten verwendet, um mehrere Aktionen in eine Zeile zu setzen. +Manchmal wird der Operator in komplexeren Konstrukten verwendet, um mehrere Aktionen in eine Zeile zu setzen. Zum Beispiel: @@ -467,4 +533,4 @@ for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { } ``` -Solche Tricks werden in vielen JavaScript-Frameworks verwendet. Deshalb erwähnen wir sie. In der Regel verbessern sie jedoch nicht die Lesbarkeit des Codes, daher sollten wir vor der Verwendung gut überlegen. +Solche Tricks werden in vielen JavaScript-Frameworks verwendet. Deshalb erwähnen wir sie. In der Regel verbessern sie jedoch nicht die Lesbarkeit des Codes, daher sollten wir uns ihre Verwendung gut überlegen. diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md index a86a9f73e..9fd324071 100644 --- a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -10,12 +10,12 @@ null == "\n0\n" → false null === +"\n0\n" → false ``` -Some of the reasons: +Begründungen: -1. Obviously, true. -2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`. -3. Again, dictionary comparison, first char of `"2"` is greater than the first char of `"1"`. -4. Values `null` and `undefined` equal each other only. -5. Strict equality is strict. Different types from both sides lead to false. -6. Similar to `(4)`, `null` only equals `undefined`. -7. Strict equality of different types. +1. Offensichtlich true. +2. Wörterbuchvergleich, daher false. `"a"` ist kleiner als `"p"`. +3. Wieder, Wörterbuchvergleich, erstes Zeichen `"2"` ist größer als das erste Zeichen `"1"`. +4. Die Werte `null` und `undefined` sind nur gegenseitig gleich. +5. Strikte Gleichheit ist streng. Verschiedene Typen auf beiden Seiten führen zu `false`. +6. Ähnlich wie `(4)`, `null` ist nur gleich `undefined`. +7. Strikte Gleichheit von verschiedenen Datentypen. diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md index be7f75ddd..4777faa75 100644 --- a/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Comparisons +# Vergleiche -What will be the result for these expressions? +Was wird das Ergebnis dieser Ausdrücke sein? ```js no-beautify 5 > 4 diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md index a323dc93d..a271a5885 100644 --- a/1-js/02-first-steps/09-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -1,47 +1,41 @@ -# Comparisons +# Vergleiche -We know many comparison operators from maths. +Wir kennen viele Vergleichsoperatoren aus der Mathematik: -In JavaScript they are written like this: +- Größer/kleiner als: a > b, a < b. +- Größer/kleiner als oder gleich: a >= b, a <= b. +- Gleich: `a == b` (beachte bitte das doppelte Gleichheitszeichen `=`. Ein einfaches Gleichheitszeiten `a = b` würde eine Zuweisung bedeuten). +- Ungleich. In der Mathematik lautet die Notation , aber in JavaScript wird es als eine Zuweisung mit einem Ausrufezeichen davor geschrieben: a != b. -- Greater/less than: a > b, a < b. -- Greater/less than or equals: a >= b, a <= b. -- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment. -- Not equals. In maths the notation is , but in JavaScript it's written as a != b. +## Das Ergebnis ist Boolean -In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities. +Wie alle anderen Operatoren liefert ein Vergleich einen Wert. In diesem Fall ist der Wert ein boolscher Wert. -At the end you'll find a good recipe to avoid "javascript quirks"-related issues. +- `true` -- bedeutet "ja", "richtig" oder "die Wahrheit". +- `false` -- bedeutet "nein", "falsch" oder "nicht die Wahrheit". -## Boolean is the result - -All comparison operators return a boolean value: - -- `true` -- means "yes", "correct" or "the truth". -- `false` -- means "no", "wrong" or "not the truth". - -For example: +Zum Beispiel: ```js run -alert( 2 > 1 ); // true (correct) -alert( 2 == 1 ); // false (wrong) -alert( 2 != 1 ); // true (correct) +alert( 2 > 1 ); // true (richtig) +alert( 2 == 1 ); // false (falsch) +alert( 2 != 1 ); // true (richtig) ``` -A comparison result can be assigned to a variable, just like any value: +Ein Vergleichsergebnis kann einer Variablen zugewiesen werden, genau wie jeder andere Wert: ```js run -let result = 5 > 4; // assign the result of the comparison -alert( result ); // true +let result = 5 > 4; // das Ergebnis des Vergleichs zuweisen +alert( result ); // true ``` -## String comparison +## String Vergleiche -To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order. +Um zu sehen, ob ein String größer als ein anderer ist, verwendet JavaScript die so genannte "Wörterbuch-" oder "lexikografische" Reihenfolge. -In other words, strings are compared letter-by-letter. +Mit anderen Worten, Strings werden Buchstabe für Buchstabe verglichen. -For example: +Zum Beispiel: ```js run alert( 'Z' > 'A' ); // true @@ -49,53 +43,53 @@ alert( 'Glow' > 'Glee' ); // true alert( 'Bee' > 'Be' ); // true ``` -The algorithm to compare two strings is simple: +Der Algorithmus zum Vergleichen zweier Strings ist einfach: -1. Compare the first character of both strings. -2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done. -3. Otherwise, if both strings' first characters are the same, compare the second characters the same way. -4. Repeat until the end of either string. -5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater. +1. Vergleiche das erste Zeichen der beiden Strings. +2. Wenn das erste Zeichen aus dem ersten String größer (oder kleiner) als das des anderen Strings ist, dann ist der erste String größer (oder kleiner) als der zweite. Das war's. +3. Andernfalls, wenn die ersten Zeichen beider Strings gleich sind, vergleiche die zweiten Zeichen auf die gleiche Weise. +4. Wiederhole dies bis zum Ende einer der beiden Strings. +5. Wenn beide Strings mit der gleichen Länge enden, dann sind sie gleich. Andernfalls ist der längere String größer. -In the examples above, the comparison `'Z' > 'A'` gets to a result at the first step while the strings `"Glow"` and `"Glee"` are compared character-by-character: +In den obigen Beispielen führt der Vergleich `'Z' > 'A'` im ersten Schritt zu einem Ergebnis, während die Zeichenketten `"Glow"` und `"Glee"` zeichenweise verglichen werden: -1. `G` is the same as `G`. -2. `l` is the same as `l`. -3. `o` is greater than `e`. Stop here. The first string is greater. +1. `G` ist gleich wie `G`. +2. `l` ist gleich wie `l`. +3. `o` ist größer als `e`. Halte hier an. Der erste String ist größer. -```smart header="Not a real dictionary, but Unicode order" -The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same. +```smart header="Kein echtes Wörterbuch, aber Unicode-Reihenfolge" +Der oben angegebene Vergleichsalgorithmus entspricht in etwa dem, der in Wörterbüchern oder Telefonbüchern verwendet wird, aber er ist nicht genau derselbe. -For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? The lowercase `"a"`. Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter . +Zum Beispiel, Groß/Kleinschreibung ist wichtig. Ein Großbuchstabe `"A"` ist nicht gleich dem Kleinbuchstaben `"a"`. Welcher ist größer? Der Kleinbuchstabe `"a"`. Und warum? Weil das Kleinbuchstabenzeichen einen größeren Index in der internen Codierungstabelle hat, die JavaScript verwendet (Unicode). Wir werden im Kapitel auf die spezifischen Details und die Konsequenzen dieser Tatsache zurückkommen. ``` -## Comparison of different types +## Vergleich von verschiedenen Typen -When comparing values of different types, JavaScript converts the values to numbers. +Beim Vergleichen von Werten verschiedener Typen konvertiert JavaScript die Werte in Zahlen. -For example: +Zum Beispiel: ```js run -alert( '2' > 1 ); // true, string '2' becomes a number 2 -alert( '01' == 1 ); // true, string '01' becomes a number 1 +alert( '2' > 1 ); // true, String '2' wird zur Zahl 2 +alert( '01' == 1 ); // true, String '01' wird zur Zahl 1 ``` -For boolean values, `true` becomes `1` and `false` becomes `0`. +Bei boolschen Werten wird `true` zu `1` und `false` zu `0`. -For example: +Zum Beispiel: ```js run alert( true == 1 ); // true alert( false == 0 ); // true ``` -````smart header="A funny consequence" -It is possible that at the same time: +````smart header="Eine lustige Auswirkung" +Es ist möglich, dass zur gleichen Zeit: -- Two values are equal. -- One of them is `true` as a boolean and the other one is `false` as a boolean. +- zwei Werte gleich sind. +- einer von denen als Boolean `true` und der andere als Boolean `false` ist. -For example: +Zum Beispiel: ```js run let a = 0; @@ -107,67 +101,67 @@ alert( Boolean(b) ); // true alert(a == b); // true! ``` -From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules. +Aus Sicht von JavaScript ist dieses Ergebnis ganz normal. Eine Gleichheitsprüfung konvertiert Werte unter Verwendung der numerischen Konvertierung (daher wird `"0"` zu `0`), während die explizite Konvertierung in `Boolean` einen anderen Regelsatz verwendet. ```` -## Strict equality +## Strikte Gleichheit -A regular equality check `==` has a problem. It cannot differentiate `0` from `false`: +Die normale Gleichheitsprüfung mit `==` hat ein Problem. Sie kann `0` nicht von `false` unterscheiden: ```js run alert( 0 == false ); // true ``` -The same thing happens with an empty string: +Dasselbe geschieht mit einem leeren String: ```js run alert( '' == false ); // true ``` -This happens because operands of different types are converted to numbers by the equality operator `==`. An empty string, just like `false`, becomes a zero. +Dies geschieht, weil Operanden unterschiedlichen Typs mit dem Gleichheitsoperator `==` in Zahlen umgewandelt werden. Ein leerer String wird, genau wie bei `false`, zu einer Null. -What to do if we'd like to differentiate `0` from `false`? +Was ist zu tun, wenn wir `0` von `false` unterscheiden wollen? -**A strict equality operator `===` checks the equality without type conversion.** +**Ein strikter Gleichheitsoperator `===` prüft auf Gleichheit ohne Datentypumwandlung.** -In other words, if `a` and `b` are of different types, then `a === b` immediately returns `false` without an attempt to convert them. +Mit anderen Worten, wenn `a` und `b` unterschiedliche Typen sind, gibt `a === b` sofort `false` zurück, ohne sie zu konvertieren. -Let's try it: +Versuchen wir es: ```js run -alert( 0 === false ); // false, because the types are different +alert( 0 === false ); // false, weil die Datentypen verschieden sind ``` -There is also a "strict non-equality" operator `!==` analogous to `!=`. +Es gibt auch einen "strikten Ungleichheits"-Operator `!==` analog zu `!=`. -The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors. +Der Operator für strikte Gleichheit ist etwas länger zu schreiben, macht aber deutlich, was vor sich geht, und lässt weniger Raum für Fehler. -## Comparison with null and undefined +## Vergleiche mit null und undefined -There's a non-intuitive behavior when `null` or `undefined` are compared to other values. +Es gibt ein nicht intuitives Verhalten, wenn `null` oder `undefined` mit anderen Werten verglichen wird. -For a strict equality check `===` -: These values are different, because each of them is a different type. +Für die strikte Gleichheitsprüfung `===` +: sind diese Werte unterschiedlich, weil jeder von ihnen ein verschiedener Datentyp ist. ```js run alert( null === undefined ); // false ``` -For a non-strict check `==` -: There's a special rule. These two are a "sweet couple": they equal each other (in the sense of `==`), but not any other value. +Für die nicht strikte Kontrolle `==` +: gibt es eine besondere Regel. Diese beiden sind ein "süßes Pärchen": sie sind gleich (im Sinne von `==`), aber nicht irgendeinem anderen Wert. ```js run alert( null == undefined ); // true ``` -For maths and other comparisons `< > <= >=` -: `null/undefined` are converted to numbers: `null` becomes `0`, while `undefined` becomes `NaN`. +Für mathematische und andere Vergleiche `< > <= >=` +: `null/undefined` werden in Zahlen umgewandelt: `null` wird zu `0`, während `undefined` zu `NaN` wird. -Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them. +Jetzt wollen wir sehen, was bei der Anwendung dieser Regeln passiert. Und, was noch wichtiger ist, wie man damit nicht in eine Falle tappt. -### Strange result: null vs 0 +### Seltsames Ergebnis: null vs 0 -Let's compare `null` with a zero: +Lass uns `null` mit `0` vergleichen: ```js run alert( null > 0 ); // (1) false @@ -175,15 +169,15 @@ alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) *!*true*/!* ``` -Mathematically, that's strange. The last result states that "`null` is greater than or equal to zero", so in one of the comparisons above it must be `true`, but they are both false. +Mathematisch gesehen ist das seltsam. Das letzte Ergebnis besagt, dass "`null` größer oder gleich `0` ist", so dass es in einem der obigen Vergleiche `true` sein muss, aber beide sind `false`. -The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, treating it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. +Der Grund ist, dass die Gleichheitsprüfung `==` und vergleichende Operatoren `> < >= <=` verschieden funktionieren. Vergleichende Operatoren wandeln `null` in eine Zahl um, behandeln es als `0`. Deshalb ist (3) `null >= 0` `true` und (1) `null > 0` `false`. -On the other hand, the equality check `==` for `undefined` and `null` is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) `null == 0` is false. +Auf der anderen Seite ist die Gleichheitsprüfung `==` für `undefined` und `null` so definiert, dass sie ohne Umwandlung einander gleich sind, aber nichts anderem. Deshalb ist (2) `null == 0` `false`. -### An incomparable undefined +### undefined ist nicht vergleichbar -The value `undefined` shouldn't be compared to other values: +Der Wert `undefined` sollte nicht mit anderen Werten verglichen werden: ```js run alert( undefined > 0 ); // false (1) @@ -191,24 +185,25 @@ alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` -Why does it dislike zero so much? Always false! +Warum mag es die Null so wenig? Immer `false`! + +Wir erhalten diese Ergebnisse, weil: -We get these results because: +- Vergleiche `(1)` und `(2)` ergeben `false`, weil `undefined` wird zu `NaN` umgewandelt und `NaN` ist ein spezieller numerischer Wert, der für alle Vergleiche `false` ergibt. +- Die Gleichheitsprüfung `(3)` ergibt `false`, weil `undefined` nur gleich `null` ist, und keinem anderen Wert. -- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons. -- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value. +### Probleme vermeiden -### Avoid problems +Warum sind wir diese Beispiele durchgegangen? Sollten wir uns ständig an diese Besonderheiten erinnern? Nun, nicht wirklich. Mit der Zeit werden diese kniffligen Dinge allmählich vertraut werden, aber es gibt einen soliden Weg, um Problemen damit auszuweichen: -Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them: +Behandele einfach jeden Vergleich mit `undefined/null` mit Ausnahme der strikten Gleichheit `===` mit besonderer Vorsicht. -- Treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. -- Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. +Verwende keine Vergleiche `>= > < <=` mit einer Variablen, die `null/undefined` sein kann, es sei denn, du bist dir wirklich sicher, was du tust. Wenn eine Variable diese Werte haben kann, überprüfe sie separat. -## Summary +## Zusammenfassung -- Comparison operators return a boolean value. -- Strings are compared letter-by-letter in the "dictionary" order. -- When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). -- The values `null` and `undefined` equal `==` each other and do not equal any other value. -- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea. +- Vergleichsoperatoren geben einen boolschen Wert zurück. +- Strings werden Buchstabe für Buchstabe in der "Wörterbuch"-Reihenfolge verglichen. +- Wenn Werte verschiedener Typen verglichen werden, werden sie in Zahlen umgewandelt (unter Ausschluss einer strikten Gleichheitsprüfung). +- Die Werte `null` und `undefined` sind gleich `==`, aber ansonsten ist nichts gleich den zwei Werten. +- Sei vorsichtig, wenn du Vergleiche wie `>` oder `<` mit Variablen verwendest, die gelegentlich `null/undefined` sein können. Es ist eine gute Idee, `null/undefined` separat zu prüfen. diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md index 51f1d4680..761dcc8a5 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md @@ -1,8 +1,8 @@ -**Yes, it will.** +**Ja, die Meldung wird angezeigt.** -Any string except an empty one (and `"0"` is not empty) becomes `true` in the logical context. +Jeder String außer einem leeren String (und `"0"` ist nicht leer) wird im logischen Kontext zu `true`. -We can run and check: +Wir können das überprüfen: ```js run if ("0") { diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md index 5f16cda85..3826b8d2e 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# if (a string with zero) +# if (String mit 0) -Will `alert` be shown? +Wird die Meldung angezeigt? ```js if ("0") { diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg index 25dc2744d..47b020aab 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg @@ -1 +1 @@ -BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript \ No newline at end of file +BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript \ No newline at end of file diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html index ea9966653..6b9a71609 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html @@ -5,12 +5,12 @@ diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index a4d943245..4b41c18bf 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -2,11 +2,13 @@ importance: 2 --- -# The name of JavaScript +# Der Name von JavaScript -Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?' +Schreibe unter Verwendung des `if..else`-Konstrukts ein Programm, das den Nutzer folgendes fragt: 'Wie lautet der "offizielle" Name von JavaScript?' + + +Wenn der Besucher "ECMAScript" eingibt, dann gib "Richtig!" aus, ansonsten gib "Das wusstest du nicht? ECMAScript!" aus. -If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!" ![](ifelse_task2.svg) diff --git a/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html b/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html index f168360dd..e504f4bf6 100644 --- a/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html +++ b/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html @@ -6,7 +6,7 @@ - + @@ -20,17 +20,17 @@ - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js index 89ba412ed..42731267e 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - it("raises to n-th power", function() { + it("erhöht auf die n-te Potenz", function() { assert.equal(pow(2, 3), 8); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js index c803f0e61..341841ef5 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - it("2 raised to power 3 is 8", function() { + it("2 erhöht auf Potenz 3 ist 8", function() { assert.equal(pow(2, 3), 8); }); - it("3 raised to power 4 is 81", function() { + it("3 erhöht auf Potenz 4 ist 81", function() { assert.equal(pow(3, 4), 81); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js index 8663952aa..7a0701803 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js @@ -2,7 +2,7 @@ describe("pow", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} zur Potenz 3 ist ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } diff --git a/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js index e5ce2ce43..244f439c0 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - describe("raises x to power 3", function() { + describe("erhöht x auf Potenz 3", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} zur Potenz 3 ist ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } diff --git a/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js index 75ff5e99f..fdb4912a1 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - describe("raises x to power 3", function() { + describe("erhöht x auf Potenz 3", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} zur Potenz 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } @@ -15,11 +15,11 @@ describe("pow", function() { }); - it("if n is negative, the result is NaN", function() { + it("für negatives n ist das Ergebnis NaN", function() { assert.isNaN(pow(2, -1)); }); - it("if n is not integer, the result is NaN", function() { + it("für nicht-ganzzahliges n ist das Ergebnis NaN", function() { assert.isNaN(pow(2, 1.5)); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js index 89ba412ed..42731267e 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - it("raises to n-th power", function() { + it("erhöht auf die n-te Potenz", function() { assert.equal(pow(2, 3), 8); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js index 75ff5e99f..61f73424a 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - describe("raises x to power 3", function() { + describe("erhöht x auf Potenz 3", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} zur Potenz 3 ist ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } @@ -15,11 +15,11 @@ describe("pow", function() { }); - it("if n is negative, the result is NaN", function() { + it("für negatives n ist das Ergebnis NaN", function() { assert.isNaN(pow(2, -1)); }); - it("if n is not integer, the result is NaN", function() { + it("für nicht-ganzzahliges n ist das Ergebnis NaN", function() { assert.isNaN(pow(2, 1.5)); }); diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 75db49d2f..62c5fd13f 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,54 +1,150 @@ -# Polyfills +# Polyfills and transpilers -The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](http://www.ecma-international.org/publications/standards/Ecma-262.htm). +<<<<<<< HEAD +JavaScript entwickelt sich stetig weiter. Neue Vorschläge für die Sprache erscheinen regelmäßig, sie werden analysiert und, wenn sie als würdig erachtet werden, an die Liste unter angehängt und dann zur [Spezifikation](http:// www.ecma-international.org/publications/standards/Ecma-262.htm) freigegeben. +======= +The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 -Teams behind JavaScript engines have their own ideas about what to implement first. They may decide to implement proposals that are in draft and postpone things that are already in the spec, because they are less interesting or just harder to do. +Teams hinter JavaScript-Engines haben ihre eigenen Vorstellungen davon, was zuerst implementiert werden soll. Sie können beschließen, Vorschläge zu implementieren, die sich im Entwurf befinden, und Dinge, die bereits in der Spezifikation enthalten sind, verschieben, weil sie weniger interessant oder einfach schwieriger zu machen sind. -So it's quite common for an engine to implement only the part of the standard. +<<<<<<< HEAD +Es ist also durchaus üblich, dass eine Engine nur einen Teil des Standards implementiert. +======= +So it's quite common for an engine to implement only part of the standard. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 -A good page to see the current state of support for language features is (it's big, we have a lot to study yet). +<<<<<<< HEAD +Eine gute Seite, um den aktuellen Stand der Unterstützung für Sprachfunktionen zu sehen, ist (es ist groß, wir haben noch viel zu lernen). +======= +A good page to see the current state of support for language features is (it's big, we have a lot to study yet). +>>>>>>> b258d7d5b635c88228f7556e14fbe5e5ca7f736d -## Babel +As programmers, we'd like to use most recent features. The more good stuff - the better! -When we use modern features of the language, some engines may fail to support such code. Just as said, not all features are implemented everywhere. +<<<<<<< HEAD +Wenn wir moderne Funktionen der Sprache verwenden, können einige Engines diesen Code möglicherweise nicht unterstützen. Wie gesagt, nicht alle Funktionen sind überall implementiert. -Here Babel comes to the rescue. +Hier kommt Babel zur Rettung. -[Babel](https://babeljs.io) is a [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler). It rewrites modern JavaScript code into the previous standard. +[Babel](https://babeljs.io) ist ein [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler). Es schreibt modernen JavaScript-Code in den vorherigen Standard um. -Actually, there are two parts in Babel: +Tatsächlich gibt es in Babel zwei Teile: -1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build systems like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that it's very easy to integrate into development process. +1. Erstens das Transpiler-Programm, das den Code umschreibt. Der Entwickler führt es auf seinem eigenen Computer aus. Es schreibt den Code in den älteren Standard um. Und dann wird der Code für die Benutzer an die Website geliefert. Moderne Projekt-Build-Systeme wie [webpack](http://webpack.github.io/) bieten die Möglichkeit, den Transpiler bei jeder Codeänderung automatisch auszuführen, sodass er sehr einfach in den Entwicklungsprozess integriert werden kann. -2. Second, the polyfill. +2. Zweitens, der polyfill. - New language features may include new built-in functions and syntax constructs. - The transpiler rewrites the code, transforming syntax constructs into older ones. But as for new built-in functions, we need to implement them. JavaScript is a highly dynamic language, scripts may add/modify any functions, so that they behave according to the modern standard. + Neue Sprachfeatures können neue integrierte Funktionen und Syntaxkonstrukte umfassen. + Der Transpiler schreibt den Code neu und wandelt Syntaxkonstrukte in ältere um. Aber was neue eingebaute Funktionen betrifft, müssen wir sie implementieren. JavaScript ist eine hochdynamische Sprache, Skripte können beliebige Funktionen hinzufügen/ändern, damit sie sich dem modernen Standard entsprechend verhalten. - A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. + Ein Skript, das neue Funktionen aktualisiert/hinzufügt, wird "Polyfill" genannt. Es "füllt" die Lücke und fügt fehlende Implementierungen hinzu. - Two interesting polyfills are: - - [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features. - - [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser. + Zwei interessante Polyfills sind: + - [core js](https://github.com/zloirock/core-js), der vieles unterstützt, ermöglicht es, nur benötigte Funktionen einzubinden. + - [polyfill.io](http://polyfill.io) Dienst, der ein Skript mit Polyfills bereitstellt, abhängig von den Funktionen und dem Browser des Benutzers. -So, if we're going to use modern language features, a transpiler and a polyfill are necessary. +Wenn wir moderne Sprachfunktionen verwenden möchten, sind ein Transpiler und ein Polyfill erforderlich. -## Examples in the tutorial +## Beispiele im Tutorial +======= +On the other hand, how to make our modern code work on older engines that don't understand recent features yet? +There are two tools for that: +1. Transpilers. +2. Polyfills. + +Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development. + +## Transpilers + +A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines. + +E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`. + +A transpiler would analyze our code and rewrite `height ?? 100` into `(height !== undefined && height !== null) ? height : 100`. + +```js +// before running the transpiler +height = height ?? 100; + +// after running the transpiler +height = (height !== undefined && height !== null) ? height : 100; +``` + +Now the rewritten code is suitable for older JavaScript engines. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 + +Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server. + +<<<<<<< HEAD +<<<<<<< HEAD ````online -Most examples are runnable at-place, like this: +Die meisten Beispiele sind direkt lauffähig, wie folgt: ```js run -alert('Press the "Play" button in the upper-right corner to run'); +alert('Drücken Sie die Schaltfläche "Starten" in der oberen rechten Ecke, um zu starten'); ``` -Examples that use modern JS will work only if your browser supports it. +Beispiele, die modernes JS verwenden, funktionieren nur, wenn Ihr Browser dies unterstützt. ```` ```offline -As you're reading the offline version, in PDF examples are not runnable. In EPUB some of them can run. +Während Sie die Offline-Version lesen, sind PDF-Beispiele nicht lauffähig. In EPUB können einige von ihnen ausgeführt werden. +``` + +Google Chrome ist in der Regel mit Sprachfunktionen auf dem neuesten Stand und eignet sich gut, um topaktuelle Demos ohne Transpiler auszuführen, aber auch andere moderne Browser funktionieren einwandfrei. +======= +Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there. +======= +Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there. +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 + +Modern project build systems, such as [webpack](https://webpack.js.org/), provide a means to run a transpiler automatically on every code change, so it's very easy to integrate into the development process. + +## Polyfills + +New language features may include not only syntax constructs and operators, but also built-in functions. + +For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`. + +In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. + +As we're talking about new functions, not syntax changes, there's no need to transpile anything here. We just need to declare the missing function. + +A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. + +For this particular case, the polyfill for `Math.trunc` is a script that implements it, like this: + +```js +if (!Math.trunc) { // if no such function + // implement it + Math.trunc = function(number) { + // Math.ceil and Math.floor exist even in ancient JavaScript engines + // they are covered later in the tutorial + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; +} ``` -Google Chrome is usually the most up-to-date with language features, good to run bleeding-edge demos without any transpilers, but other modern browsers also work fine. +JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones. + +One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need. + +## Summary + +In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines. + +Just don't forget to use a transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). They'll ensure that the code works. + +For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](https://webpack.js.org/) with the [babel-loader](https://github.com/babel/babel-loader) plugin. + +Good resources that show the current state of support for various features: +- - for pure JavaScript. +- - for browser-related functions. + +P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though. + +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md index 33eb89220..6878ca088 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# Multiply numeric properties by 2 +# Multiply numeric property values by 2 -Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`. +Create a function `multiplyNumeric(obj)` that multiplies all numeric property values of `obj` by `2`. For instance: diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 513f2f3e3..0fe5979fa 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -44,7 +44,7 @@ The resulting `user` object can be imagined as a cabinet with two signed files l ![user object](object-user.svg) -We can add, remove and read files from it any time. +We can add, remove and read files from it at any time. Property values are accessible using the dot notation: @@ -62,7 +62,7 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) -To remove a property, we can use `delete` operator: +To remove a property, we can use the `delete` operator: ```js delete user.age; @@ -92,30 +92,6 @@ let user = { ``` That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike. -````smart header="Object with const can be changed" -Please note: an object declared as `const` *can* be modified. - -For instance: - -```js run -const user = { - name: "John" -}; - -*!* -user.name = "Pete"; // (*) -*/!* - -alert(user.name); // Pete -``` - -It might seem that the line `(*)` would cause an error, but no. The `const` fixes the value of `user`, but not its contents. - -The `const` would give an error only if we try to set `user=...` as a whole. - -There's another way to make constant object properties, we'll cover it later in the chapter . -```` - ## Square brackets For multiword properties, the dot access doesn't work: @@ -225,13 +201,13 @@ let bag = { }; ``` -Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write. +Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write. So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets. ## Property value shorthand -In real code we often use existing variables as values for property names. +In real code, we often use existing variables as values for property names. For instance: @@ -276,7 +252,7 @@ let user = { ## Property names limitations -As we already know, a variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc. +As we already know, a variable cannot have a name equal to one of the language-reserved words like "for", "let", "return" etc. But for an object property, there's no such restriction: @@ -349,7 +325,7 @@ alert( "blabla" in user ); // false, user.blabla doesn't exist Please note that on the left side of `in` there must be a *property name*. That's usually a quoted string. -If we omit quotes, that means a variable, it should contain the actual name to be tested. For instance: +If we omit quotes, that means a variable should contain the actual name to be tested. For instance: ```js run let user = { age: 30 }; @@ -379,7 +355,7 @@ In the code above, the property `obj.test` technically exists. So the `in` opera Situations like this happen very rarely, because `undefined` should not be explicitly assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. -## The "for..in" loop +## The "for..in" loop [#forin] To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before. @@ -436,7 +412,7 @@ for (let code in codes) { */!* ``` -The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first. +The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first. But if we run the code, we see a totally different picture: @@ -448,9 +424,10 @@ The phone codes go in the ascending sorted order, because they are integers. So ````smart header="Integer properties? What's that?" The "integer property" term here means a string that can be converted to-and-from an integer without a change. -So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not: +So, `"49"` is an integer property name, because when it's transformed to an integer number and back, it's still the same. But `"+49"` and `"1.2"` are not: ```js run +// Number(...) explicitly converts to a number // Math.trunc is a built-in function that removes the decimal part alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property @@ -505,7 +482,7 @@ They store properties (key-value pairs), where: To access a property, we can use: - The dot notation: `obj.property`. -- Square brackets notation `obj["property"]`. Square brackets allow to take the key from a variable, like `obj[varWithKey]`. +- Square brackets notation `obj["property"]`. Square brackets allow taking the key from a variable, like `obj[varWithKey]`. Additional operators: - To delete a property: `delete obj.prop`. diff --git a/1-js/04-object-basics/01-object/object-user-delete.svg b/1-js/04-object-basics/01-object/object-user-delete.svg index 4bbf324b4..c5af7e7af 100644 --- a/1-js/04-object-basics/01-object/object-user-delete.svg +++ b/1-js/04-object-basics/01-object/object-user-delete.svg @@ -1 +1 @@ -nameisAdminuser \ No newline at end of file +nameisAdminuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-empty.svg b/1-js/04-object-basics/01-object/object-user-empty.svg index 5359c45cb..99edb0269 100644 --- a/1-js/04-object-basics/01-object/object-user-empty.svg +++ b/1-js/04-object-basics/01-object/object-user-empty.svg @@ -1 +1 @@ -emptyuser \ No newline at end of file +emptyuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-isadmin.svg b/1-js/04-object-basics/01-object/object-user-isadmin.svg index f4e7b09ae..e2cc0eaf1 100644 --- a/1-js/04-object-basics/01-object/object-user-isadmin.svg +++ b/1-js/04-object-basics/01-object/object-user-isadmin.svg @@ -1 +1 @@ -nameageisAdminuser \ No newline at end of file +nameageisAdminuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-props.svg b/1-js/04-object-basics/01-object/object-user-props.svg index 92958cfbe..b3d5c9b71 100644 --- a/1-js/04-object-basics/01-object/object-user-props.svg +++ b/1-js/04-object-basics/01-object/object-user-props.svg @@ -1 +1 @@ -nameagelikes birdsuser \ No newline at end of file +nameagelikes birdsuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user.svg b/1-js/04-object-basics/01-object/object-user.svg index f91e48143..f499fbc10 100644 --- a/1-js/04-object-basics/01-object/object-user.svg +++ b/1-js/04-object-basics/01-object/object-user.svg @@ -1 +1 @@ -nameageuser \ No newline at end of file +nameageuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object.svg b/1-js/04-object-basics/01-object/object.svg index 28582b94e..47431a6e1 100644 --- a/1-js/04-object-basics/01-object/object.svg +++ b/1-js/04-object-basics/01-object/object.svg @@ -1 +1 @@ -key1key2key3 \ No newline at end of file +key1key2key3 \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index c88872232..17d8f0a67 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -1,25 +1,29 @@ -# Object copying, references +# Objektreferenzen und Kopieren -One of the fundamental differences of objects vs primitives is that they are stored and copied "by reference". +Einer der grundlegenden Unterschiede zwischen Objekten und primitiven Werten besteht darin, dass Objekte "per Referenz" gespeichert und kopiert werden, während primitive Werte wie Strings, Zahlen, Booleans usw. immer als "vollständiger Wert" kopiert werden. -Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value". +Das ist leicht zu verstehen, wenn wir uns ansehen, was beim Kopieren eines Wertes passiert. -For instance: +Beginnen wir mit einem primitiven Wert, wie einem String. + +Hier kopieren wir `message` nach `phrase`: ```js let message = "Hello!"; let phrase = message; ``` -As a result we have two independent variables, each one is storing the string `"Hello!"`. +Als Ergebnis haben wir zwei unabhängige Variablen, jede speichert den String `"Hello!"`. ![](variable-copy-value.svg) -Objects are not like that. +Ein ziemlich offensichtliches Ergebnis, oder? + +Bei Objekten ist das nicht so. -**A variable stores not the object itself, but its "address in memory", in other words "a reference" to it.** +**Eine Variablenzuweisung mit einem Objekt speichert nicht das Objekt selbst, sondern seine "Adresse im Speicher" -- anders ausgedrückt "eine Referenz" darauf.** -Here's the picture for the object: +Sehen wir uns ein Beispiel einer solchen Variable an: ```js let user = { @@ -27,25 +31,35 @@ let user = { }; ``` +Und so wird es tatsächlich im Speicher abgelegt: + ![](variable-contains-reference.svg) -Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it. +Das Objekt wird irgendwo im Speicher abgelegt (rechts im Bild), während die `user`-Variable (links) eine "Referenz" darauf hat. + +Wir können uns eine Objektvariable wie `user` als ein Blatt Papier vorstellen, auf dem die Adresse des Objekts steht. -**When an object variable is copied -- the reference is copied, the object is not duplicated.** +Wenn wir Aktionen mit dem Objekt durchführen, z.B. eine Eigenschaft `user.name` anfordern, schaut die JavaScript-Engine nach, was sich an dieser Adresse befindet und führt die Operation am eigentlichen Objekt durch. -For instance: +Nun hier ist, warum das wichtig ist. + +**Wenn eine Objektvariable kopiert wird, wird die Referenz kopiert, aber das Objekt selbst wird nicht dupliziert.** + +Zum Beispiel: ```js no-beautify let user = { name: "John" }; -let admin = user; // copy the reference +let admin = user; // kopiere die Referenz ``` -Now we have two variables, each one with the reference to the same object: +Jetzt haben wir zwei Variablen, jede speichert eine Referenz auf dasselbe Objekt: ![](variable-copy-reference.svg) -We can use any variable to access the object and modify its contents: +Wie du sehen kannst, gibt es immer noch nur ein Objekt, aber jetzt mit zwei Variablen, die darauf verweisen. + +Wir können entweder die Variable verwenden, um auf das Objekt zuzugreifen und dessen Inhalt zu ändern: ```js run let user = { name: 'John' }; @@ -53,52 +67,72 @@ let user = { name: 'John' }; let admin = user; *!* -admin.name = 'Pete'; // changed by the "admin" reference +admin.name = 'Pete'; // geändert durch die "admin"-Referenz */!* -alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference +alert(*!*user.name*/!*); // 'Pete', Änderungen sind von der "user"-Referenz aus sichtbar ``` -The example above demonstrates that there is only one object. As if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use another key (`user`) we can see changes. - -## Comparison by reference +Es ist, als hätten wir einen Schrank mit zwei Schlüsseln und benutzen einen davon (`admin`), um hineinzukommen und Änderungen vorzunehmen. Dann, wenn wir später einen anderen Schlüssel (`user`) benutzen, öffnen wir immer noch denselben Schrank und können auf den geänderten Inhalt zugreifen. -The equality `==` and strict equality `===` operators for objects work exactly the same. +## Vergleich per Referenz -**Two objects are equal only if they are the same object.** +Zwei Objekte sind nur dann gleich, wenn sie dasselbe Objekt sind. -Here two variables reference the same object, thus they are equal: +Zum Beispiel sind hier `a` und `b` Referenzen auf dasselbe Objekt, also sind sie gleich: ```js run let a = {}; -let b = a; // copy the reference +let b = a; // kopiere die Referenz -alert( a == b ); // true, both variables reference the same object -alert( a === b ); // true +alert( a == b ); // wahr, beide Variablen verweisen auf dasselbe Objekt +alert( a === b ); // wahr ``` -And here two independent objects are not equal, even though both are empty: +Und hier sind zwei unabhängige Objekte nicht gleich, auch wenn sie gleich aussehen (beide sind leer): ```js run let a = {}; -let b = {}; // two independent objects +let b = {}; // zwei unabhängige Objekte -alert( a == b ); // false +alert( a == b ); // falsch ``` -For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons occur very rarely, usually as a result of a coding mistake. +Für Vergleiche wie `obj1 > obj2` oder für einen Vergleich mit einem primitiven Wert `obj == 5` werden Objekte in primitive Werte umgewandelt. Wir werden bald untersuchen, wie Objektumwandlungen funktionieren, aber um ehrlich zu sein, solche Vergleiche werden sehr selten benötigt -- normalerweise treten sie als Ergebnis eines Programmierfehlers auf. -## Cloning and merging, Object.assign +````smart header="Const-Objekte können verändert werden" +Eine wichtige Nebenwirkung der Speicherung von Objekten als Referenzen ist, dass ein `const` deklariertes Objekt verändert werden kann. -So, copying an object variable creates one more reference to the same object. +Zum Beispiel: -But what if we need to duplicate an object? Create an independent copy, a clone? +```js run +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` -That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time. +Es mag scheinen, dass die Zeile `(*)` einen Fehler verursacht, aber das tut sie nicht. Der Wert von `user` ist konstant, dieser muss immer auf dasselbe Objekt verweisen, aber Eigenschaften dieses Objekts können sich ändern. -But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level. +Anders ausgedrückt, die `const user` verursacht nur dann einen Fehler, wenn wir versuchen `user=...` als ganzes zu setzen. -Like this: +Das heißt aber auch, wenn wir wirklich konstante Objekteigenschaften benötigen, ist das auch möglich, jedoch mit völlig anderen Methoden. Das werden wir im Kapitel erwähnen. +```` + +## Klonen und Zusammenfügen, Object.assign [#cloning-and-merging-object-assign] + +Das Kopieren einer Objektvariablen schafft also eine weitere Referenz auf dasselbe Objekt. + +Aber was ist, wenn wir ein Objekt duplizieren müssen? + +Wir können ein neues Objekt erstellen und die Struktur des bestehenden Objekts nachbilden, indem wir über seine Eigenschaften iterieren und diese auf der primitiven Ebene kopieren. + +Das geht so: ```js run let user = { @@ -107,61 +141,65 @@ let user = { }; *!* -let clone = {}; // the new empty object +let clone = {}; // das neue leere Objekt -// let's copy all user properties into it +// lass uns alle Eigenschaften von user hineinkopieren for (let key in user) { clone[key] = user[key]; } */!* -// now clone is a fully independent object with the same content -clone.name = "Pete"; // changed the data in it +// jetzt ist clone ein vollständig unabhängiges Objekt mit demselben Inhalt +clone.name = "Pete"; // geänderte Daten darin -alert( user.name ); // still John in the original object +alert( user.name ); // immer noch John im Originalobjekt ``` -Also we can use the method [Object.assign](mdn:js/Object/assign) for that. +Wir können auch die Methode [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) verwenden. -The syntax is: +Die Syntax lautet: ```js -Object.assign(dest, [src1, src2, src3...]) +Object.assign(dest, ...sources) ``` -- The first argument `dest` is a target object. -- Further arguments `src1, ..., srcN` (can be as many as needed) are source objects. -- It copies the properties of all source objects `src1, ..., srcN` into the target `dest`. In other words, properties of all arguments starting from the second are copied into the first object. -- The call returns `dest`. +- Das erste Argument `dest` ist ein Zielobjekt. +- Die weiteren Argumente sind eine Liste von Quellobjekten. -For instance, we can use it to merge several objects into one: -```js +Es kopiert die Eigenschaften aller Quellobjekte in das Ziel `dest` und gibt es dann als Ergebnis zurück. + +Zum Beispiel wenn wir ein `user`-Objekt haben, dann fügen wir ihm ein paar Berechtigungen hinzu: + +```js run let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; *!* -// copies all properties from permissions1 and permissions2 into user +// kopiert alle Eigenschaften von permissions1 und permissions2 in user Object.assign(user, permissions1, permissions2); */!* -// now user = { name: "John", canView: true, canEdit: true } +// jetzt user = { name: "John", canView: true, canEdit: true } +alert(user.name); // John +alert(user.canView); // wahr +alert(user.canEdit); // wahr ``` -If the copied property name already exists, it gets overwritten: +Wenn der kopierte Eigenschaftsname bereits existiert, wird er überschrieben: ```js run let user = { name: "John" }; Object.assign(user, { name: "Pete" }); -alert(user.name); // now user = { name: "Pete" } +alert(user.name); // jetzt user = { name: "Pete" } ``` -We also can use `Object.assign` to replace `for..in` loop for simple cloning: +Wir können `Object.assign` auch verwenden, um eine einfache Objektkopie zu erstellen: -```js +```js run let user = { name: "John", age: 30 @@ -170,15 +208,20 @@ let user = { *!* let clone = Object.assign({}, user); */!* + +alert(clone.name); // John +alert(clone.age); // 30 ``` -It copies all properties of `user` into the empty object and returns it. +Hier kopiert es alle Eigenschaften von `user` in ein leeres Objekt und gibt es zurück. -## Nested cloning +Es gibt auch andere Methoden, um ein Objekt zu klonen, z.B. unter Verwendung der [Spread-Syntax](info:rest-parameters-spread) `clone = {...user}`, die später im Tutorial behandelt wird. -Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them? +## Geschachteltes Klonen -Like this: +Bis jetzt sind wir davon ausgegangen, dass alle Eigenschaften von `user` primitiv sind. Aber Eigenschaften können auch Referenzen auf andere Objekte sein. + +So wie hier: ```js run let user = { name: "John", @@ -191,9 +234,7 @@ let user = { alert( user.sizes.height ); // 182 ``` -Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes: - -Like this: +Jetzt reicht es nicht aus `clone.sizes = user.sizes` zu kopieren, denn `user.sizes` ist ein Objekt und wird per Referenz kopiert, sodass `clone` und `user` dieselben Größen teilen: ```js run let user = { @@ -206,23 +247,79 @@ let user = { let clone = Object.assign({}, user); -alert( user.sizes === clone.sizes ); // true, same object +alert( user.sizes === clone.sizes ); // wahr, gleiches Objekt + +// user und clone teilen sizes +user.sizes.width = 60; // ändere eine Eigenschaft an einer Stelle +alert(clone.sizes.width); // 60, bekomme das Ergebnis von der anderen +``` + +Um das zu beheben und `user` und `clone` wirklich zu separaten Objekten zu machen, sollten wir eine Klon-Schleife verwenden, die jeden Wert von `user[key]` überprüft und, wenn es ein Objekt ist, dann auch dessen Struktur repliziert. Das wird als "tiefes Klonen" oder "strukturiertes Klonen" bezeichnet. Es gibt die [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone)-Methode, die tiefes Klonen implementiert. + + +### structuredClone + +Der Aufruf `structuredClone(object)` klont das `object` mit all seinen geschachtelten Eigenschaften. + +So sieht es aus, wie wir es in unserem Beispiel nutzen können: + +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; -// user and clone share sizes -user.sizes.width++; // change a property from one place -alert(clone.sizes.width); // 51, see the result from the other one +*!* +let clone = structuredClone(user); +*/!* + +alert( user.sizes === clone.sizes ); // falsch, unterschiedliche Objekte + +// user und clone stehen jetzt in keiner Beziehung mehr +user.sizes.width = 60; // ändere eine Eigenschaft an einer Stelle +alert(clone.sizes.width); // 50, nicht verwandt ``` -To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". +Die `structuredClone`-Methode kann die meisten Datentypen klonen, wie Objekte, Arrays, primitive Werte. + +Sie unterstützt auch zirkuläre Referenzen, wenn eine Objekteigenschaft auf das Objekt selbst verweist (direkt oder über eine Kette von Referenzen). + +Zum Beispiel: + +```js run +let user = {}; +// erstellen wir eine zirkuläre Referenz: +// user.me verweist auf das user selbst +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // wahr +``` + +Wie du sehen kannst, verweist `clone.me` auf den `clone`, nicht auf den `user`! Die zirkuläre Referenz wurde also korrekt geklont. + +Allerdings gibt es Fälle, in denen `structuredClone` fehlschlägt. + +Zum Beispiel, wenn ein Objekt eine Funktionseigenschaft hat: + +```js run +// Fehler +structuredClone({ + f: function() {} +}); +``` -There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data). +Funktionseigenschaften werden nicht unterstützt. -We can use recursion to implement it. Or, not to reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). +Um solche komplexen Fälle zu handhaben, müssen wir möglicherweise eine Kombination von Klonmethoden verwenden, benutzerdefinierten Code schreiben oder, um das Rad nicht neu zu erfinden, eine vorhandene Implementierung verwenden, zum Beispiel [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) aus der JavaScript-Bibliothek [lodash](https://lodash.com). -## Summary +## Zusammenfassung -Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object. +Objekte werden per Referenz zugewiesen und kopiert. Anders ausgedrückt, eine Variable speichert nicht den "Objektwert", sondern eine "Referenz" (Adresse im Speicher) auf den Wert. Das Kopieren einer solchen Variablen oder das Übergeben als Funktionsargument kopiert diese Referenz, nicht das Objekt selbst. -All operations via copied references (like adding/removing properties) are performed on the same single object. +Alle Operationen über kopierte Referenzen (wie das Hinzufügen/Entfernen von Eigenschaften) werden am selben einzigen Objekt durchgeführt. -To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +Um eine "echte Kopie" (ein Klon) zu erstellen, können wir `Object.assign` für die sogenannte "flache Kopie" (geschachtelte Objekte werden per Referenz kopiert) oder eine "tiefes Klonen" Funktion `structuredClone` verwenden oder eine benutzerdefinierte Klonimplementierung wie [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) nutzen. diff --git a/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg b/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg index a59c9210e..267f04578 100644 --- a/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg +++ b/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg @@ -1 +1 @@ -username \ No newline at end of file +username \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg b/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg index 5d0bc1594..a847fb200 100644 --- a/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg +++ b/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg @@ -1 +1 @@ -useradminname \ No newline at end of file +useradminname \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/variable-copy-value.svg b/1-js/04-object-basics/02-object-copy/variable-copy-value.svg index e09f521fe..0d6ca67bc 100644 --- a/1-js/04-object-basics/02-object-copy/variable-copy-value.svg +++ b/1-js/04-object-basics/02-object-copy/variable-copy-value.svg @@ -1 +1 @@ -"Hello!"message"Hello!"phrase \ No newline at end of file +"Hello!"message"Hello!"phrase \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 672e26d43..1b576d629 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -14,8 +14,8 @@ Simply put, "reachable" values are those that are accessible or usable somehow. For instance: - - Local variables and parameters of the current function. - - Variables and parameters for other functions on the current chain of nested calls. + - The currently executing function, its local variables and parameters. + - Other functions on the current chain of nested calls, their local variables and parameters. - Global variables. - (there are some other, internal ones as well) @@ -23,7 +23,7 @@ Simply put, "reachable" values are those that are accessible or usable somehow. 2. Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references. - For instance, if there's an object in a local variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow. + For instance, if there's an object in a global variable, and that object has a property referencing another object, *that* object is considered reachable. And those that it references are also reachable. Detailed examples to follow. There's a background process in the JavaScript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that have become unreachable. @@ -74,7 +74,7 @@ Now if we do the same: user = null; ``` -...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed. +...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed. ## Interlinked objects @@ -169,11 +169,11 @@ The first step marks the roots: ![](garbage-collection-2.svg) -Then their references are marked: +Then we follow their references and mark referenced objects: ![](garbage-collection-3.svg) -...And their references, while possible: +...And continue to follow further references, while possible: ![](garbage-collection-4.svg) @@ -183,12 +183,12 @@ Now the objects that could not be visited in the process are considered unreacha We can also imagine the process as spilling a huge bucket of paint from the roots, that flows through all references and marks all reachable objects. The unmarked ones are then removed. -That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not affect the execution. +That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution. Some of the optimizations: -- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, they can be cleaned up aggressively. Those that survive for long enough, become "old" and are examined less often. -- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the garbage collection into pieces. Then the pieces are executed one by one, separately. That requires some extra bookkeeping between them to track changes, but we have many tiny delays instead of a big one. +- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. Those that survive for long enough, become "old" and are examined less often. +- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get many tiny delays instead of a big one. - **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. There exist other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so studying deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below. @@ -199,14 +199,14 @@ The main things to know: - Garbage collection is performed automatically. We cannot force or prevent it. - Objects are retained in memory while they are reachable. -- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole. +- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above. Modern engines implement advanced algorithms of garbage collection. A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) covers some of them. -If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). +If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). -[V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn the garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. -In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. +In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. diff --git a/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg b/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg index 2ae1f664c..a582ca64b 100644 --- a/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg +++ b/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg @@ -1 +1 @@ -<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file +<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-family.svg b/1-js/04-object-basics/03-garbage-collection/family-no-family.svg index 655d1982e..c73dd6a48 100644 --- a/1-js/04-object-basics/03-garbage-collection/family-no-family.svg +++ b/1-js/04-object-basics/03-garbage-collection/family-no-family.svg @@ -1 +1 @@ -<global>ObjectObjectfatherwifename: "John"name: "Ann"motherObjecthusbandfamily: null \ No newline at end of file +<global>ObjectObjectfatherwifename: "John"name: "Ann"motherObjecthusbandfamily: null \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg b/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg index 11f4ada35..6bd13c0e8 100644 --- a/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg +++ b/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg @@ -1 +1 @@ -Objectfamilyname: "Ann"motherObject<global> \ No newline at end of file +Objectfamilyname: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-father.svg b/1-js/04-object-basics/03-garbage-collection/family-no-father.svg index b76c868e0..fd1f20607 100644 --- a/1-js/04-object-basics/03-garbage-collection/family-no-father.svg +++ b/1-js/04-object-basics/03-garbage-collection/family-no-father.svg @@ -1 +1 @@ -ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file +ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family.svg b/1-js/04-object-basics/03-garbage-collection/family.svg index bec2f4ddc..fd0534874 100644 --- a/1-js/04-object-basics/03-garbage-collection/family.svg +++ b/1-js/04-object-basics/03-garbage-collection/family.svg @@ -1 +1 @@ -ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file +ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg index 2563c8185..5cac52e9a 100644 --- a/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg index acd5025e9..7dd3a693a 100644 --- a/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg index 4421ec784..106057787 100644 --- a/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg index 74adc8135..bd485adee 100644 --- a/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg index abb127ab2..2d85432bc 100644 --- a/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg @@ -1 +1 @@ -<global>unreachables \ No newline at end of file +<global>unreachables \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg index dc4cce1c7..191324354 100644 --- a/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg @@ -1 +1 @@ -username: "John"Objectadmin<global> \ No newline at end of file +username: "John"Objectadmin<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg index e75b8d465..07914a9ca 100644 --- a/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg @@ -1 +1 @@ -name: "John"Objectuser: null<global> \ No newline at end of file +name: "John"Objectuser: null<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg index 0191e3f07..15bd51afb 100644 --- a/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg @@ -1 +1 @@ -username: "John"Object<global> \ No newline at end of file +username: "John"Object<global> \ No newline at end of file diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md index c1aaf4f97..f33c9310e 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md @@ -7,7 +7,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); @@ -45,7 +45,7 @@ function makeUser() { } */!* }; -}; +} let user = makeUser(); diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md index 4784b082c..c6f8f9658 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md @@ -14,7 +14,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js index 1f71eda4c..4decb76dc 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js @@ -15,6 +15,11 @@ describe("calculator", function() { afterEach(function() { prompt.restore(); }); + + it('the read get two values and saves them as object properties', function () { + assert.equal(calculator.a, 2); + assert.equal(calculator.b, 3); + }); it("the sum is 5", function() { assert.equal(calculator.sum(), 5); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/task.md b/1-js/04-object-basics/04-object-methods/7-calculator/task.md index aa22608ec..82d0da030 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md +++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md @@ -6,7 +6,7 @@ importance: 5 Create an object `calculator` with three methods: -- `read()` prompts for two values and saves them as object properties. +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. - `sum()` returns the sum of saved values. - `mul()` multiplies saved values and returns the result. @@ -21,4 +21,3 @@ alert( calculator.mul() ); ``` [demo] - diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js index e98fe6410..a35c009cc 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js @@ -11,5 +11,6 @@ let ladder = { }, showStep: function() { alert(this.step); + return this; } }; \ No newline at end of file diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js index a2b17fcc4..b4f2459b7 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js @@ -32,6 +32,14 @@ describe('Ladder', function() { it('down().up().up().up() ', function() { assert.equal(ladder.down().up().up().up().step, 2); }); + + it('showStep() should return this', function() { + assert.equal(ladder.showStep(), ladder); + }); + + it('up().up().down().showStep().down().showStep()', function () { + assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0) + }); after(function() { ladder.step = 0; diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md index 2b47873fc..f215461dd 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md @@ -21,9 +21,9 @@ let ladder = { return this; */!* } -} +}; -ladder.up().up().down().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` We also can write a single call per line. For long chains it's more readable: @@ -33,7 +33,7 @@ ladder .up() .up() .down() - .up() + .showStep() // 1 .down() - .showStep(); // 1 + .showStep(); // 0 ``` diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md index eca9f4e92..7d2ef8c15 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,7 @@ importance: 2 # Chaining -There's a `ladder` object that allows to go up and down: +There's a `ladder` object that allows you to go up and down: ```js let ladder = { @@ -21,19 +21,21 @@ let ladder = { }; ``` -Now, if we need to make several calls in sequence, can do it like this: +Now, if we need to make several calls in sequence, we can do it like this: ```js ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 +ladder.down(); +ladder.showStep(); // 0 ``` -Modify the code of `up`, `down` and `showStep` to make the calls chainable, like this: +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: ```js -ladder.up().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` -Such approach is widely used across JavaScript libraries. +Such an approach is widely used across JavaScript libraries. diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index 75bd1856a..cea2b6a70 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -32,11 +32,11 @@ user.sayHi = function() { user.sayHi(); // Hello! ``` -Here we've just used a Function Expression to create the function and assign it to the property `user.sayHi` of the object. +Here we've just used a Function Expression to create a function and assign it to the property `user.sayHi` of the object. -Then we can call it. The user can now speak! +Then we can call it as `user.sayHi()`. The user can now speak! -A function that is the property of an object is called its *method*. +A function that is a property of an object is called its *method*. So, here we've got a method `sayHi` of the object `user`. @@ -51,7 +51,7 @@ let user = { // first, declare function sayHi() { alert("Hello!"); -}; +} // then add as a method user.sayHi = sayHi; @@ -81,7 +81,7 @@ user = { // method shorthand looks better, right? user = { *!* - sayHi() { // same as "sayHi: function()" + sayHi() { // same as "sayHi: function(){...}" */!* alert("Hello"); } @@ -90,7 +90,7 @@ user = { As demonstrated, we can omit `"function"` and just write `sayHi()`. -To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred. +To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases, the shorter syntax is preferred. ## "this" in methods @@ -160,14 +160,16 @@ let user = { let admin = user; user = null; // overwrite to make things obvious -admin.sayHi(); // Whoops! inside sayHi(), the old name is used! error! +*!* +admin.sayHi(); // TypeError: Cannot read property 'name' of null +*/!* ``` If we used `this.name` instead of `user.name` inside the `alert`, then the code would work. ## "this" is not bound -In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function. +In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function, even if it's not a method of an object. There's no syntax error in the following example: diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md index 7d8edd7ca..b2ec92a81 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md @@ -1,8 +1,8 @@ -Yes, it's possible. +Ja, es ist möglich. -If a function returns an object then `new` returns it instead of `this`. +Wenn eine Funktion ein Objekt zurückgibt, dann gibt `new` dieses statt `this` zurück. -So they can, for instance, return the same externally defined object `obj`: +So können sie zum Beispiel dasselbe extern definierte Objekt `obj` zurückgeben: ```js run no-beautify let obj = {}; diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index 8c1fea8eb..aed794e97 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -2,18 +2,18 @@ importance: 2 --- -# Two functions – one object +# Zwei Funktionen – ein Objekt -Is it possible to create functions `A` and `B` such as `new A()==new B()`? +Ist es möglich, Funktionen `A` und `B` zu erstellen, sodass `new A() == new B()`? ```js no-beautify function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` -If it is, then provide an example of their code. +Wenn es möglich ist, gib bitte ein Beispiel für deren Code. \ No newline at end of file diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js index 036053927..bba80e5c2 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js @@ -10,6 +10,11 @@ describe("calculator", function() { calculator = new Calculator(); calculator.read(); }); + + it("the read method asks for two values using prompt and remembers them in object properties", function() { + assert.equal(calculator.a, 2); + assert.equal(calculator.b, 3); + }); it("when 2 and 3 are entered, the sum is 5", function() { assert.equal(calculator.sum(), 5); diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md index 86bb65416..ed8c9d13b 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md @@ -18,6 +18,7 @@ function Calculator() { let calculator = new Calculator(); calculator.read(); -alert( "Sum=" + calculator.sum() ); -alert( "Mul=" + calculator.mul() ); +alert( "Summe=" + calculator.sum() ); +alert( "Produkt=" + calculator.mul() ); ``` + diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 60e7c373e..cc7c6c078 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -1,23 +1,19 @@ -importance: 5 +# Erstelle einen neuen Taschenrechner ---- +Erstelle eine Konstruktorfunktion `Calculator`, die Objekte mit 3 Methoden erstellt: -# Create new Calculator +- `read()` fordert zwei Werte an und speichert diese als Objekteigenschaften mit den Namen `a` und `b` entsprechend. +- `sum()` gibt die Summe dieser Eigenschaften zurück. +- `mul()` gibt das Produkt der Multiplikation dieser Eigenschaften zurück. -Create a constructor function `Calculator` that creates objects with 3 methods: - -- `read()` asks for two values using `prompt` and remembers them in object properties. -- `sum()` returns the sum of these properties. -- `mul()` returns the multiplication product of these properties. - -For instance: +Zum Beispiel: ```js let calculator = new Calculator(); calculator.read(); -alert( "Sum=" + calculator.sum() ); -alert( "Mul=" + calculator.mul() ); +alert( "Summe=" + calculator.sum() ); +alert( "Produkt=" + calculator.mul() ); ``` [demo] diff --git a/1-js/04-object-basics/06-constructor-new/3-accumulator/solution.md b/1-js/04-object-basics/06-constructor-new/3-accumulator/solution.md index eb145e79d..fe348b4c0 100644 --- a/1-js/04-object-basics/06-constructor-new/3-accumulator/solution.md +++ b/1-js/04-object-basics/06-constructor-new/3-accumulator/solution.md @@ -1,11 +1,9 @@ - - ```js run demo function Accumulator(startingValue) { this.value = startingValue; this.read = function() { - this.value += +prompt('How much to add?', 0); + this.value += +prompt('Wieviel soll hinzugefügt werden?', 0); }; } @@ -15,3 +13,4 @@ accumulator.read(); accumulator.read(); alert(accumulator.value); ``` + diff --git a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md index c2c44881e..65996b522 100644 --- a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md +++ b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md @@ -1,27 +1,23 @@ -importance: 5 +# Neuen Akkumulator erstellen ---- +Erstelle eine Konstruktorfunktion `Accumulator(startingValue)`. -# Create new Accumulator +Das Objekt, das es erstellt, sollte: -Create a constructor function `Accumulator(startingValue)`. +- Den "aktuellen Wert" in der Eigenschaft `value` speichern. Der Anfangswert wird auf den Argumentwert des Konstruktors `startingValue` gesetzt. +- Die `read()` Methode sollte `prompt` verwenden, um eine neue Zahl zu lesen und diese zu `value` hinzuzufügen. -Object that it creates should: +Anders ausgedrückt, die Eigenschaft `value` ist die Summe aller von Benutzern eingegebenen Werte mit dem Anfangswert `startingValue`. -- Store the "current value" in the property `value`. The starting value is set to the argument of the constructor `startingValue`. -- The `read()` method should use `prompt` to read a new number and add it to `value`. - -In other words, the `value` property is the sum of all user-entered values with the initial value `startingValue`. - -Here's the demo of the code: +Hier ist eine Demo des Codes: ```js -let accumulator = new Accumulator(1); // initial value 1 +let accumulator = new Accumulator(1); // Anfangswert 1 -accumulator.read(); // adds the user-entered value -accumulator.read(); // adds the user-entered value +accumulator.read(); // addiert den vom Benutzer eingegebenen Wert +accumulator.read(); // addiert den vom Benutzer eingegebenen Wert -alert(accumulator.value); // shows the sum of these values +alert(accumulator.value); // zeigt die Summe dieser Werte ``` [demo] diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index a885e35ff..1bc0e2a1b 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -1,17 +1,17 @@ -# Constructor, operator "new" +# Konstruktor, Operator "new" -The regular `{...}` syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on. +Die reguläre Syntax `{...}` erlaubt es uns, ein einzelnes Objekt zu erstellen. Aber oft müssen wir viele ähnliche Objekte erstellen, wie mehrere Benutzer oder Menüpunkte und so weiter. -That can be done using constructor functions and the `"new"` operator. +Das kann mit Konstruktorfunktionen und dem `"new"`-Operator erreicht werden. -## Constructor function +## Konstruktorfunktion -Constructor functions technically are regular functions. There are two conventions though: +Technisch gesehen sind Konstruktorfunktionen normale Funktionen. Es gibt jedoch zwei Konventionen: -1. They are named with capital letter first. -2. They should be executed only with `"new"` operator. +1. Sie beginnen mit einem Großbuchstaben. +2. Sie sollten nur mit dem `"new"`-Operator ausgeführt werden. -For instance: +Zum Beispiel: ```js run function User(name) { @@ -27,31 +27,31 @@ alert(user.name); // Jack alert(user.isAdmin); // false ``` -When a function is executed with `new`, it does the following steps: +Wenn eine Funktion mit `new` ausgeführt wird, geschieht Folgendes: -1. A new empty object is created and assigned to `this`. -2. The function body executes. Usually it modifies `this`, adds new properties to it. -3. The value of `this` is returned. +1. Ein neues leeres Objekt wird erstellt und `this` zugewiesen. +2. Der Funktionskörper wird ausgeführt. Normalerweise modifiziert er `this` und fügt neue Eigenschaften hinzu. +3. Der Wert von `this` wird zurückgegeben. -In other words, `new User(...)` does something like: +Anders ausgedrückt, `new User(...)` macht so etwas wie: ```js function User(name) { *!* - // this = {}; (implicitly) + // this = {}; (implizit) */!* - // add properties to this + // Eigenschaften zu this hinzufügen this.name = name; this.isAdmin = false; *!* - // return this; (implicitly) + // return this; (implizit) */!* } ``` -So `let user = new User("Jack")` gives the same result as: +Also gibt `let user = new User("Jack")` das gleiche Ergebnis wie: ```js let user = { @@ -60,148 +60,149 @@ let user = { }; ``` -Now if we want to create other users, we can call `new User("Ann")`, `new User("Alice")` and so on. Much shorter than using literals every time, and also easy to read. +Wenn wir nun andere Benutzer erstellen wollen, können wir `new User("Ann")`, `new User("Alice")` usw. aufrufen. Viel kürzer als jedes Mal Literale zu verwenden, und auch einfach zu lesen. -That's the main purpose of constructors -- to implement reusable object creation code. +Das ist der Hauptzweck von Konstruktoren - wiederverwendbaren Code zur Objekterstellung zu implementieren. -Let's note once again -- technically, any function can be used as a constructor. That is: any function can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. +Lass uns noch einmal festhalten - technisch gesehen kann jede Funktion (außer Pfeilfunktionen, da sie kein `this` haben) als Konstruktor verwendet werden. Sie kann mit `new` ausgeführt werden und wird das oben beschriebene Verfahren durchführen. „Beginnt mit Großbuchstaben“ ist eine allgemeine Vereinbarung, um klarzustellen, dass eine Funktion mit `new` ausgeführt werden soll. ````smart header="new function() { ... }" -If we have many lines of code all about creation of a single complex object, we can wrap them in constructor function, like this: +Wenn wir viele Zeilen Code haben, die sich alle um die Erstellung eines einzigen komplexen Objekts drehen, können wir sie in eine sofort aufgerufene Konstruktorfunktion einpacken: ```js -let user = new function() { +// eine Funktion erstellen und sofort mit new aufrufen +let user = new function() { this.name = "John"; this.isAdmin = false; - // ...other code for user creation - // maybe complex logic and statements - // local variables etc + // ...anderer Code für die Benutzererstellung + // vielleicht komplexe Logik und Anweisungen + // lokale Variablen etc. }; ``` -The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. +Dieser Konstruktor kann nicht erneut aufgerufen werden, weil er nirgendwo gespeichert ist, gerade erstellt und aufgerufen wurde. Also zielt dieser Trick darauf ab, den Code, der das einzelne Objekt konstruiert, einzukapseln, ohne weitere Wiederverwendung. ```` -## Constructor mode test: new.target +## Test des Konstruktormodus: new.target -```smart header="Advanced stuff" -The syntax from this section is rarely used, skip it unless you want to know everything. +```smart header="Fortgeschrittenes Thema" +Die Syntax aus diesem Abschnitt wird selten verwendet. Überspringe ihn, es sei denn, du willst alles wissen. ``` -Inside a function, we can check whether it was called with `new` or without it, using a special `new.target` property. +Innerhalb einer Funktion können wir überprüfen, ob sie mit `new` oder ohne aufgerufen wurde, indem wir eine spezielle `new.target`-Eigenschaft verwenden. -It is empty for regular calls and equals the function if called with `new`: +Sie ist undefiniert bei normalen Aufrufen und entspricht der Funktion, wenn sie mit `new` aufgerufen wird: ```js run function User() { alert(new.target); } -// without "new": +// ohne "new": *!* User(); // undefined */!* -// with "new": +// mit "new": *!* new User(); // function User { ... } */!* ``` -That can be used inside the function to know whether it was called with `new`, "in constructor mode", or without it, "in regular mode". +Das kann innerhalb der Funktion verwendet werden, um herauszufinden, ob sie im "Konstruktormodus" mit `new` oder im "normalen Modus" ohne `new` aufgerufen wurde. -We can also make both `new` and regular calls to do the same, like this: +Wir können auch ermöglichen, dass sowohl `new` als auch normale Aufrufe das Gleiche tun: ```js run function User(name) { - if (!new.target) { // if you run me without new - return new User(name); // ...I will add new for you + if (!new.target) { // wenn Du mich ohne new aufrufst + return new User(name); // ... füge ich new für Dich hinzu } this.name = name; } -let john = User("John"); // redirects call to new User +let john = User("John"); // leitet den Aufruf zu new User um alert(john.name); // John ``` -This approach is sometimes used in libraries to make the syntax more flexible. So that people may call the function with or without `new`, and it still works. +Dieser Ansatz wird manchmal in Bibliotheken verwendet, um die Syntax flexibler zu machen. So können Personen die Funktion mit oder ohne `new` aufrufen, und es funktioniert trotzdem. -Probably not a good thing to use everywhere though, because omitting `new` makes it a bit less obvious what's going on. With `new` we all know that the new object is being created. +Wahrscheinlich ist es aber keine gute Sache, diese überall zu verwenden, denn das Weglassen von `new` macht es etwas weniger offensichtlich, was vor sich geht. Mit `new` wissen wir alle, dass ein neues Objekt erstellt wird. -## Return from constructors +## Rückgabe aus Konstruktoren -Usually, constructors do not have a `return` statement. Their task is to write all necessary stuff into `this`, and it automatically becomes the result. +Normalerweise haben Konstruktoren keine `return`-Anweisung. Ihre Aufgabe ist es, alles Notwendige in `this` zu schreiben, und das wird automatisch zum Ergebnis. -But if there is a `return` statement, then the rule is simple: +Aber wenn es eine `return`-Anweisung gibt, dann ist die Regel einfach: -- If `return` is called with an object, then the object is returned instead of `this`. -- If `return` is called with a primitive, it's ignored. +- Wenn `return` mit einem Objekt aufgerufen wird, dann wird das Objekt anstelle von `this` zurückgegeben. +- Wenn `return` mit einem Primitiv aufgerufen wird, wird es ignoriert. -In other words, `return` with an object returns that object, in all other cases `this` is returned. +Anders ausgedrückt, `return` mit einem Objekt gibt dieses Objekt zurück, in allen anderen Fällen wird `this` zurückgegeben. -For instance, here `return` overrides `this` by returning an object: +Zum Beispiel, hier überschreibt `return` `this`, indem ein Objekt zurückgegeben wird: ```js run function BigUser() { this.name = "John"; - return { name: "Godzilla" }; // <-- returns this object + return { name: "Godzilla" }; // <-- gibt dieses Objekt zurück } -alert( new BigUser().name ); // Godzilla, got that object +alert( new BigUser().name ); // Godzilla, dieses Objekt erhalten ``` -And here's an example with an empty `return` (or we could place a primitive after it, doesn't matter): +Und hier ist ein Beispiel mit einer leeren `return`-Anweisung (oder wir könnten ein Primitiv danach setzen, spielt keine Rolle): ```js run function SmallUser() { this.name = "John"; - return; // <-- returns this + return; // <-- gibt dies zurück } alert( new SmallUser().name ); // John ``` -Usually constructors don't have a `return` statement. Here we mention the special behavior with returning objects mainly for the sake of completeness. +Normalerweise haben Konstruktoren keine `return`-Anweisung. Hier erwähnen wir das spezielle Verhalten mit der Rückgabe von Objekten hauptsächlich der Vollständigkeit halber. -````smart header="Omitting parentheses" -By the way, we can omit parentheses after `new`, if it has no arguments: +````smart header="Weglassen von Klammern" +Übrigens, wir können Klammern nach `new` weglassen: ```js -let user = new User; // <-- no parentheses -// same as +let user = new User; // <-- keine Klammern +// entspricht let user = new User(); ``` -Omitting parentheses here is not considered a "good style", but the syntax is permitted by specification. +Das Weglassen von Klammern wird hier nicht als "guter Stil" betrachtet, aber die Syntax wird durch die Spezifikation zugelassen. ```` -## Methods in constructor +## Methoden im Konstruktor -Using constructor functions to create objects gives a great deal of flexibility. The constructor function may have parameters that define how to construct the object, and what to put in it. +Die Verwendung von Konstruktorfunktionen zur Objekterstellung bietet eine große Flexibilität. Die Konstruktorfunktion kann Parameter haben, die definieren, wie das Objekt konstruiert werden soll und was eingefügt wird. -Of course, we can add to `this` not only properties, but methods as well. +Natürlich können wir `this` nicht nur Eigenschaften, sondern auch Methoden hinzufügen. -For instance, `new User(name)` below creates an object with the given `name` and the method `sayHi`: +Ein Beispiel, `new User(name)` unten erstellt ein Objekt mit dem gegebenen `name` und der Methode `sayHi`: ```js run function User(name) { this.name = name; this.sayHi = function() { - alert( "My name is: " + this.name ); + alert( "Mein Name ist: " + this.name ); }; } *!* let john = new User("John"); -john.sayHi(); // My name is: John +john.sayHi(); // Mein Name ist: John */!* /* @@ -212,19 +213,19 @@ john = { */ ``` -To create complex objects, there's a more advanced syntax, [classes](info:classes), that we'll cover later. +Um komplexe Objekte zu erstellen, gibt es eine fortgeschrittenere Syntax, [Klassen](info:classes), die wir später behandeln werden. -## Summary +## Zusammenfassung -- Constructor functions or, briefly, constructors, are regular functions, but there's a common agreement to name them with capital letter first. -- Constructor functions should only be called using `new`. Such a call implies a creation of empty `this` at the start and returning the populated one at the end. +- Konstruktorfunktionen oder kurz Konstruktoren sind reguläre Funktionen, aber es gibt eine allgemeine Vereinbarung, sie mit dem ersten Großbuchstaben zu benennen. +- Konstruktorfunktionen sollten nur mit `new` aufgerufen werden. Ein solcher Aufruf impliziert die Erstellung eines leeren `this` am Anfang und die Rückgabe des ausgefüllten am Ende. -We can use constructor functions to make multiple similar objects. +Wir können Konstruktorfunktionen verwenden, um mehrere ähnliche Objekte zu erstellen. -JavaScript provides constructor functions for many built-in language objects: like `Date` for dates, `Set` for sets and others that we plan to study. +JavaScript bietet Konstruktorfunktionen für viele eingebaute Sprachobjekte: wie `Date` für Datumsobjekte, `Set` für Mengen und andere, die wir noch behandeln werden. -```smart header="Objects, we'll be back!" -In this chapter we only cover the basics about objects and constructors. They are essential for learning more about data types and functions in the next chapters. +```smart header="Objekte, wir kommen zurück!" +In diesem Kapitel decken wir nur die Grundlagen über Objekte und Konstruktoren ab. Sie sind wesentlich, um mehr über Datentypen und Funktionen in den nächsten Kapiteln zu lernen. -After we learn that, we return to objects and cover them in-depth in the chapters and . +Nachdem wir das gelernt haben, kehren wir zu Objekten zurück und behandeln sie vertieft in den Kapiteln und . ``` diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md index 974689020..4c6029423 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -3,46 +3,94 @@ [recent browser="new"] -The optional chaining `?.` is an error-proof way to access nested object properties, even if an intermediate property doesn't exist. +The optional chaining `?.` is a safe way to access nested object properties, even if an intermediate property doesn't exist. -## The problem +## The "non-existing property" problem If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common. -For example, some of our users have addresses, but few did not provide them. Then we can't safely read `user.address.street`: +As an example, let's say we have `user` objects that hold the information about our users. + +Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them. + +In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error: ```js run -let user = {}; // the user happens to be without address +let user = {}; // a user without "address" property alert(user.address.street); // Error! ``` -Or, in the web development, we'd like to get an information about an element on the page, but it may not exist: +That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error. + +In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street"). + +...and another example. In Web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element. ```js run -// Error if the result of querySelector(...) is null -let html = document.querySelector('.my-element').innerHTML; +// document.querySelector('.elem') is null if there's no element +let html = document.querySelector('.elem').innerHTML; // error if it's null ``` -Before `?.` appeared in the language, the `&&` operator was used to work around that. +Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. -For example: +How can we do this? + +The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this: + +```js +let user = {}; + +alert(user.address ? user.address.street : undefined); +``` + +It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. + +Here's how the same would look for `document.querySelector`: + +```js run +let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; +``` + +We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good. + +For more deeply nested properties, it becomes even uglier, as more repetitions are required. + +E.g. let's get `user.address.street.name` in a similar fashion. + +```js +let user = {}; // user has no address + +alert(user.address ? user.address.street ? user.address.street.name : null : null); +``` + +That's just awful, one may even have problems understanding such code. + +There's a little better way to write it, using the `&&` operator: ```js run let user = {}; // user has no address -alert( user && user.address && user.address.street ); // undefined (no error) +alert( user.address && user.address.street && user.address.street.name ); // undefined (no error) ``` -AND'ing the whole path to the property ensures that all components exist, but is cumbersome to write. +AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal. + +As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times. + +That's why the optional chaining `?.` was added to the language. To solve this problem once and for all! ## Optional chaining -The optional chaining `?.` stops the evaluation and returns `undefined` if the part before `?.` is `undefined` or `null`. +The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. **Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.** -Here's the safe way to access `user.address.street`: +In other words, `value?.prop`: +- works as `value.prop`, if `value` exists, +- otherwise (when `value` is `undefined/null`) it returns `undefined`. + +Here's the safe way to access `user.address.street` using `?.`: ```js run let user = {}; // user has no address @@ -50,6 +98,14 @@ let user = {}; // user has no address alert( user?.address?.street ); // undefined (no error) ``` +The code is short and clean, there's no duplication at all. + +Here's an example with `document.querySelector`: + +```js run +let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element +``` + Reading the address with `user?.address` works even if `user` object doesn't exist: ```js run @@ -61,16 +117,14 @@ alert( user?.address.street ); // undefined Please note: the `?.` syntax makes optional the value before it, but not any further. -In the example above, `user?.` allows only `user` to be `null/undefined`. - -On the other hand, if `user` does exist, then it must have `user.address` property, otherwise `user?.address.street` gives an error at the second dot. +E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/undefined` (and returns `undefined` in that case), but that's only for `user`. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`. ```warn header="Don't overuse the optional chaining" We should use `?.` only where it's ok that something doesn't exist. -For example, if according to our coding logic `user` object must be there, but `address` is optional, then `user.address?.street` would be better. +For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. -So, if `user` happens to be undefined due to a mistake, we'll know about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug. +Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug. ``` ````warn header="The variable before `?.` must be declared" @@ -80,25 +134,27 @@ If there's no variable `user` at all, then `user?.anything` triggers an error: // ReferenceError: user is not defined user?.address; ``` -There must be `let/const/var user`. The optional chaining works only for declared variables. +The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables. ```` ## Short-circuiting As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist. -So, if there are any further function calls or side effects, they don't occur: +So, if there are any further function calls or operations to the right of `?.`, they won't be made. + +For instance: ```js run let user = null; let x = 0; -user?.sayHi(x++); // nothing happens +user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++ alert(x); // 0, value not incremented ``` -## Other cases: ?.(), ?.[] +## Other variants: ?.(), ?.[] The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets. @@ -107,39 +163,40 @@ For example, `?.()` is used to call a function that may not exist. In the code below, some of our users have `admin` method, and some don't: ```js run -let user1 = { +let userAdmin = { admin() { alert("I am admin"); } -} +}; + +let userGuest = {}; -let user2 = {}; +*!* +userAdmin.admin?.(); // I am admin +*/!* *!* -user1.admin?.(); // I am admin -user2.admin?.(); +userGuest.admin?.(); // nothing happens (no such method) */!* ``` -Here, in both lines we first use the dot `.` to get `admin` property, because the user object must exist, so it's safe read from it. +Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it. -Then `?.()` checks the left part: if the admin function exists, then it runs (for `user1`). Otherwise (for `user2`) the evaluation stops without errors. +Then `?.()` checks the left part: if the `admin` function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist. ```js run +let key = "firstName"; + let user1 = { firstName: "John" }; -let user2 = null; // Imagine, we couldn't authorize the user - -let key = "firstName"; +let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined - -alert( user1?.[key]?.something?.not?.existing); // undefined ``` Also we can use `?.` with `delete`: @@ -148,28 +205,29 @@ Also we can use `?.` with `delete`: delete user?.name; // delete user.name if user exists ``` -```warn header="We can use `?.` for safe reading and deleting, but not writing" -The optional chaining `?.` has no use at the left side of an assignment: +````warn header="We can use `?.` for safe reading and deleting, but not writing" +The optional chaining `?.` has no use on the left side of an assignment. +For example: ```js run -// the idea of the code below is to write user.name, if user exists +let user = null; user?.name = "John"; // Error, doesn't work -// because it evaluates to undefined = "John" +// because it evaluates to: undefined = "John" ``` +```` + ## Summary -The `?.` syntax has three forms: +The optional chaining `?.` syntax has three forms: 1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`. 2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`. -3. `obj?.method()` -- calls `obj.method()` if `obj` exists, otherwise returns `undefined`. +3. `obj.method?.()` -- calls `obj.method()` if `obj.method` exists, otherwise returns `undefined`. As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so. A chain of `?.` allows to safely access nested properties. -Still, we should apply `?.` carefully, only where it's ok that the left part doesn't to exist. - -So that it won't hide programming errors from us, if they occur. +Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md index 1ed73ed4b..10a98af0a 100644 --- a/1-js/04-object-basics/08-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -1,9 +1,16 @@ # Symbol type -By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types. +By specification, only two primitive types may serve as object property keys: -Till now we've been using only strings. Now let's see the benefits that symbols can give us. +- string type, or +- symbol type. + +Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`. + +Until now we've been using only strings. + +Now let's explore symbols, see what they can do for us. ## Symbols @@ -12,18 +19,17 @@ A "symbol" represents a unique identifier. A value of this type can be created using `Symbol()`: ```js -// id is a new symbol let id = Symbol(); ``` -Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes: +Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes: ```js // id is a symbol with the description "id" let id = Symbol("id"); ``` -Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn't affect anything. +Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn't affect anything. For instance, here are two symbols with the same description -- they are not equal: @@ -38,6 +44,8 @@ alert(id1 == id2); // false If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. JavaScript symbols are different. +So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them. + ````warn header="Symbols don't auto-convert to a string" Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert. @@ -53,6 +61,7 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another. If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here: + ```js run let id = Symbol("id"); *!* @@ -61,6 +70,7 @@ alert(id.toString()); // Symbol(id), now it works ``` Or get `symbol.description` property to show the description only: + ```js run let id = Symbol("id"); *!* @@ -72,6 +82,7 @@ alert(id.description); // id ## "Hidden" properties + Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite. For instance, if we're working with `user` objects, that belong to a third-party code. We'd like to add identifiers to them. @@ -92,9 +103,9 @@ alert( user[id] ); // we can access the data using the symbol as the key What's the benefit of using `Symbol("id")` over a string `"id"`? -As `user` objects belongs to another code, and that code also works with them, we shouldn't just add any fields to it. That's unsafe. But a symbol cannot be accessed accidentally, the third-party code probably won't even see it, so it's probably all right to do. +As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects. -Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other. +Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. Then that script can create its own `Symbol("id")`, like this: @@ -109,7 +120,7 @@ There will be no conflict between our and their identifiers, because symbols are ...But if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: -```js run +```js let user = { name: "John" }; // Our script uses "id" property @@ -121,7 +132,7 @@ user.id = "Their id value" // Boom! overwritten by another script! ``` -### Symbols in a literal +### Symbols in an object literal If we want to use a symbol in an object literal `{...}`, we need square brackets around it. @@ -133,7 +144,7 @@ let id = Symbol("id"); let user = { name: "John", *!* - [id]: 123 // not "id: 123" + [id]: 123 // not "id": 123 */!* }; ``` @@ -158,10 +169,10 @@ for (let key in user) alert(key); // name, age (no symbols) */!* // the direct access by the symbol works -alert( "Direct: " + user[id] ); +alert( "Direct: " + user[id] ); // Direct: 123 ``` -`Object.keys(user)` also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. +[Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties: @@ -206,12 +217,12 @@ Symbols inside the registry are called *global symbols*. If we want an applicati ```smart header="That sounds like Ruby" In some programming languages, like Ruby, there's a single symbol per name. -In JavaScript, as we can see, that's right for global symbols. +In JavaScript, as we can see, that's true for global symbols. ``` ### Symbol.keyFor -For global symbols, not only `Symbol.for(key)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol. +We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`: For instance: @@ -227,7 +238,7 @@ alert( Symbol.keyFor(sym2) ); // id The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and returns `undefined`. -That said, any symbols have `description` property. +That said, all symbols have the `description` property. For instance: @@ -268,10 +279,11 @@ Symbols are always different values, even if they have the same name. If we want Symbols have two main use cases: 1. "Hidden" object properties. + If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite. So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties. 2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on. -Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods. +Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs don't use these methods. diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index f6b715ce5..fa68da583 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -3,19 +3,40 @@ What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? -In that case, objects are auto-converted to primitives, and then the operation is carried out. +JavaScript doesn't allow you to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle addition (or other operators). + +In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. + +That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object! + +E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". + +So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake. + +In this chapter we'll cover how an object converts to primitive and how to customize it. + +We have two purposes: + +1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. +2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. + +## Conversion rules In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it. -1. All objects are `true` in a boolean context. There are only numeric and string conversions. +1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions. 2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. -3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts. +3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts. + +We can implement string and numeric conversion by ourselves, using special object methods. -## ToPrimitive +Now let's get into technical details, because it's the only way to cover the topic in-depth. -We can fine-tune string and numeric conversion, using special object methods. +## Hints -There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +How does JavaScript decide which conversion to apply? + +There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): `"string"` : For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: @@ -43,10 +64,12 @@ There are three variants of type conversion, so-called "hints", described in the let greater = user1 > user2; ``` + Most built-in mathematical functions also include such conversion. + `"default"` : Occurs in rare cases when the operator is "not sure" what type to expect. - For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if the a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used. @@ -60,21 +83,19 @@ There are three variants of type conversion, so-called "hints", described in the The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons. - In practice though, we don't need to remember these peculiar details, because all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we can do the same. +In practice though, things are a bit simpler. -```smart header="No `\"boolean\"` hint" -Please note -- there are only three hints. It's that simple. +All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same. -There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions. -``` +Still, it's important to know about all 3 hints, soon we'll see why. **To do the conversion, JavaScript tries to find and call three object methods:** 1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, 2. Otherwise if hint is `"string"` - - try `obj.toString()` and `obj.valueOf()`, whatever exists. + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. 3. Otherwise if hint is `"number"` or `"default"` - - try `obj.valueOf()` and `obj.toString()`, whatever exists. + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. ## Symbol.toPrimitive @@ -82,11 +103,14 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri ```js obj[Symbol.toPrimitive] = function(hint) { - // must return a primitive value + // here goes the code to convert this object to a primitive + // it must return a primitive value // hint = one of "string", "number", "default" }; ``` +If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. + For instance, here `user` object implements it: ```js run @@ -106,17 +130,16 @@ alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` -As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. - +As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. ## toString/valueOf -Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: -If there's no `Symbol.toPrimitive` then JavaScript tries to find them and try in the order: +- For the `"string"` hint: call `toString` method, and if it doesn't exist or if it returns an object instead of a primitive value, then call `valueOf` (so `toString` has the priority for string conversions). +- For other hints: call `valueOf`, and if it doesn't exist or if it returns an object instead of a primitive value, then call `toString` (so `valueOf` has the priority for maths). -- `toString -> valueOf` for "string" hint. -- `valueOf -> toString` otherwise. +Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method). @@ -136,9 +159,9 @@ alert(user.valueOf() === user); // true So if we try to use an object as a string, like in an `alert` or so, then by default we see `[object Object]`. -And the default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. +The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. -Let's implement these methods. +Let's implement these methods to customize the conversion. For instance, here `user` does the same as above using a combination of `toString` and `valueOf` instead of `Symbol.toPrimitive`: @@ -183,27 +206,27 @@ alert(user + 500); // toString -> John500 In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. -## Return types +### A conversion can return any primitive type The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive. -There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint `"number"`. +There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`. The only mandatory thing: these methods must return a primitive, not an object. ```smart header="Historical notes" For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript. -In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error. +In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error. ``` ## Further conversions As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers. -If we pass an object as an argument, then there are two stages: +If we pass an object as an argument, then there are two stages of calculations: 1. The object is converted to a primitive (using the rules described above). -2. If the resulting primitive isn't of the right type, it's converted. +2. If necessary for further calculations, the resulting primitive is also converted. For instance: @@ -230,7 +253,7 @@ let obj = { } }; -alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation ``` ## Summary @@ -240,16 +263,18 @@ The object-to-primitive conversion is called automatically by many built-in func There are 3 types (hints) of it: - `"string"` (for `alert` and other operations that need a string) - `"number"` (for maths) -- `"default"` (few operators) +- `"default"` (few operators, usually objects implement it the same way as `"number"`) -The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together. +The specification describes explicitly which operator uses which hint. The conversion algorithm is: 1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, 2. Otherwise if hint is `"string"` - - try `obj.toString()` and `obj.valueOf()`, whatever exists. + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. 3. Otherwise if hint is `"number"` or `"default"` - - try `obj.valueOf()` and `obj.toString()`, whatever exists. + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. + +All these methods must return a primitive to work (if defined). -In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes. +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md index fd22a4653..d8a9dc142 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md @@ -1,24 +1,24 @@ - -Try running it: +Probier es aus: ```js run -let str = "Hello"; +let str = 'Hallo'; str.test = 5; // (*) alert(str.test); ``` -Depending on whether you have `use strict` or not, the result may be: -1. `undefined` (no strict mode) -2. An error (strict mode). +Abhängig davon, ob du `use strict` verwendest oder nicht, kann das Ergebnis wie folgt aussehen: + +1. `undefined` (kein strict mode) +2. Ein Fehler (strict mode). -Why? Let's replay what's happening at line `(*)`: +Warum? Wiederholen wir, was in der Zeile `(*)` passiert: -1. When a property of `str` is accessed, a "wrapper object" is created. -2. In strict mode, writing into it is an error. -3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears, so in the last line `str` has no trace of the property. +1. Wenn auf eine Eigenschaft von`str` zugegriffen wird, wird ein " Wrapper-Objekt" erstellt. +2. Im strict mode, erzeugt eine Wertzuweisung einen Fehler. +3. Andernfalls wird die Bearbeitung der Eigenschaft fortgesetzt, das Objekt erhält die Eigenschaft `test`, aber danach verschwindet das "Wrapper-Objekt", so dass in der letzten Zeile `str` keine Rückschlüsse mehr auf die Eigenschaft hat. -**This example clearly shows that primitives are not objects.** +**Dieses Beispiel zeigt deutlich, dass Primitive keine Objekte sind.** -They can't store additional data. +Sie können keine zusätzlichen Daten speichern. diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md index 50c781ea5..21203c6e2 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md @@ -2,17 +2,16 @@ importance: 5 --- -# Can I add a string property? +# Kann ich eine Zeichenketten-Eigenschaft hinzufügen? - -Consider the following code: +Betrachte folgenden Code: ```js -let str = "Hello"; +let str = 'Hallo'; str.test = 5; alert(str.test); ``` -How do you think, will it work? What will be shown? +Was glaubst du, wird es funktionieren? Was wird gezeigt werden? diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index 6c13acda6..82e40662d 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -1,128 +1,129 @@ -# Methods of primitives +# Methoden von Primitiven -JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer). +JavaScript ermöglicht es uns, mit Primitiven (Zeichenketten, Zahlen usw.) zu arbeiten, als wären sie Objekte. Sie bieten auch Methoden an, die als solche aufgerufen werden können. Wir werden diese bald behandeln, doch zunächst werden wir sehen wie es funktioniert, weil Primitive natürlich keine Objekte sind (und hier werden wir es noch deutlicher machen). -Let's look at the key distinctions between primitives and objects. +Schauen wir uns die wichtigsten Unterschiede zwischen Primitiven und Objekten an. -A primitive +Ein Primitiv -- Is a value of a primitive type. -- There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`. +- Ist ein Wert eines primitiven Typs. +- Es gibt 7 primitive Typen: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` und `undefined`. -An object +Ein Objekt -- Is capable of storing multiple values as properties. -- Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript: functions, for example, are objects. +- Ist in der Lage, mehrere Werte als Eigenschaften zu speichern. +- Kann erstellt werden mit `{}`, zum Beispiel: `{name: "John", age: 30}`. Es gibt noch weitere Arten von Objekten in JavaScript: Funktionen zum Beispiel sind Objekte. -One of the best things about objects is that we can store a function as one of its properties. +Eines der größten Vorteile von Objekten ist, dass wir eine Funktion als eine ihrer Eigenschaften speichern können. ```js run let john = { - name: "John", - sayHi: function() { - alert("Hi buddy!"); - } + name: 'John', + sayHi: function () { + alert('Hi Kumpel!'); + }, }; -john.sayHi(); // Hi buddy! +john.sayHi(); // Hi Kumpel! ``` -So here we've made an object `john` with the method `sayHi`. +Hier haben wir also ein Objekt `john` mit der Methode `sayHi` erstellt. -Many built-in objects already exist, such as those that work with dates, errors, HTML elements, etc. They have different properties and methods. +Es existieren bereits viele eingebaute Objekte, z. B. solche, die mit Daten, Fehlern, HTML-Elementen usw. arbeiten. Sie haben unterschiedliche Eigenschaften und Methoden. -But, these features come with a cost! +Allerdings haben diese Besonderheiten auch ihren Preis! -Objects are "heavier" than primitives. They require additional resources to support the internal machinery. +Objekte sind "schwerer" als Primitive. Sie brauchen zusätzliche Ressourcen, um die interne Funktionalität zu unterstützen. -## A primitive as an object +## Ein Primitiv als Objekt -Here's the paradox faced by the creator of JavaScript: +Hier ist das Paradoxon, mit dem sich der Erfinder von JavaScript auseinandersetzte: -- There are many things one would want to do with a primitive like a string or a number. It would be great to access them as methods. -- Primitives must be as fast and lightweight as possible. +- Es gibt viele Sachen, die man mit einem Primitiv wie einer Zeichenkette oder einer Zahl machen möchte. Es wäre hervorragend, wenn man auf sie in Form von Methoden zugreifen könnte. +- Primitive müssen so schnell und leicht wie möglich sein. -The solution looks a little bit awkward, but here it is: +Die Lösung sieht ein wenig umständlich aus, aber hier ist sie: -1. Primitives are still primitive. A single value, as desired. -2. The language allows access to methods and properties of strings, numbers, booleans and symbols. -3. In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed. +1. Primitive sind nach wie vor primitiv. Ein einziger Wert, wie es sein soll.. +2. Die Sprache ermöglicht den Zugriff auf Methoden und Eigenschaften von Zeichenketen, Zahlen, boolschen Werten und Symbolen. +3. Damit das funktioniert, wird ein spezieller "Objekt-Wrapper" erstellt, der die zusätzlichen Funktionalitäten bereitstellt, und anschließend wieder zerstört wird. -The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean` and `Symbol`. Thus, they provide different sets of methods. +Die "Objektwrappers" sind für jeden primitiven Typ unterschiedlich und heißen: `String`, `Number`, `Boolean` und `Symbol`. Daher stellen sie unterschiedliche Sätze von Methoden zur Verfügung. -For instance, there exists a string method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized `str`. +Es gibt zum Beispiel eine String-Methode [str.toUpperCase()](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) die ein großgeschriebenes `str` zurückgibt. -Here's how it works: +Und so funktioniert's: ```js run -let str = "Hello"; +let str = 'Hallo'; -alert( str.toUpperCase() ); // HELLO +alert(str.toUpperCase()); // HALLO ``` -Simple, right? Here's what actually happens in `str.toUpperCase()`: +Einfach, nicht wahr? Folgendes passiert tatsächlich in `str.toUpperCase()`: -1. The string `str` is a primitive. So in the moment of accessing its property, a special object is created that knows the value of the string, and has useful methods, like `toUpperCase()`. -2. That method runs and returns a new string (shown by `alert`). -3. The special object is destroyed, leaving the primitive `str` alone. +1. Die Zeichenkette `str` ist ein Primitiv. Beim Zugriff auf ihre Eigenschaft wird also ein spezielles Objekt erstellt, das den Wert der Zeichenkette kennt und über nützliche Methoden verfügt, wie `toUpperCase()`. +2. Diese Methode wird ausgeführt und gibt eine neue Zeichenkette zurück (angezeigt durch `alert`). +3. Das spezielle Objekt wird zerstört, so dass das Primitiv `str` übrig bleibt. -So primitives can provide methods, but they still remain lightweight. +Primitive können also Methoden bereitstellen, bleiben aber dennoch leichtgewichtig. -The JavaScript engine highly optimizes this process. It may even skip the creation of the extra object at all. But it must still adhere to the specification and behave as if it creates one. +Die JavaScript-Engine optimiert dieses Verfahren erheblich. Es kann sogar sein, dass sie die Erstellung des zusätzlichen Objekts ganz überspringt. Sie muss sich aber trotzdem an die Spezifikation halten und sich so verhalten, als ob sie ein Objekt erstellt. -A number has methods of its own, for instance, [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to the given precision: +Eine Zahl hat ihre eigenen Methoden, z. B. [toFixed(n)](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rundet die Zahl auf die vorgegebene Genauigkeit: ```js run let n = 1.23456; -alert( n.toFixed(2) ); // 1.23 +alert(n.toFixed(2)); // 1.23 ``` -We'll see more specific methods in chapters and . +Wir werden weitere spezifische Methoden in den Kapiteln und sehen. +````warn header="Konstrukteure `String/Number/Boolean` sind nur für den internen Gebrauch bestimmt Einige Sprachen wie Java erlauben es uns, explizit "Wrapper-Objekte" für Primitive zu erstellen, indem wir eine Syntax wie`new Number(1)`or`new Boolean(false)`. -````warn header="Constructors `String/Number/Boolean` are for internal use only" -Some languages like Java allow us to explicitly create "wrapper objects" for primitives using a syntax like `new Number(1)` or `new Boolean(false)`. +In JavaScript ist das aus historischen Gründen auch möglich, aber höchst **nicht empfehlenswert**. Die Ergebnisse werden an mehreren Stellen verrückt. -In JavaScript, that's also possible for historical reasons, but highly **unrecommended**. Things will go crazy in several places. - -For instance: +Zum Beispiel: ```js run -alert( typeof 0 ); // "number" +alert(typeof 0); // "number" -alert( typeof new Number(0) ); // "object"! +alert(typeof new Number(0)); // "object"! ``` -Objects are always truthy in `if`, so here the alert will show up: +Objekte sind immer effektiv wahr in `if`, also wird hier alert angezeigt : ```js run let zero = new Number(0); -if (zero) { // zero is true, because it's an object - alert( "zero is truthy!?!" ); +if (zero) { + // zero ist wahr, weil es ein Objekt ist + alert('zero ist effektiv wahr!?!'); } ``` -On the other hand, using the same functions `String/Number/Boolean` without `new` is a totally sane and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive). +Andererseits ist die Verwendung der selben Funktionen`String/Number/Boolean` ohne `new` eine völlig vernünftige und nützliche Sache. Sie konvertieren einen Wert in den entsprechenden Typ: in eine Zeichenkette, eine Zahl oder einen booleschen Wert (primitiv). + +Beispielsweise ist Folgendes durchaus zulässig: -For example, this is entirely valid: ```js -let num = Number("123"); // convert a string to number +let num = Number('123'); // konvertiert eine Zeichenkette zu einer Zahl. ``` -```` + +````` -````warn header="null/undefined have no methods" -The special primitives `null` and `undefined` are exceptions. They have no corresponding "wrapper objects" and provide no methods. In a sense, they are "the most primitive". +````warn header="null/undefined haben keine Methoden" +Die speziellen Primitive `null` und `undefined` sind Ausnahmen. Sie haben keine entsprechenden "Wrapper-Objekte" und bieten keine Methoden. In gewissem Sinne sind sie "die primitivsten". -An attempt to access a property of such value would give the error: +Ein Versuch, auf eine Eigenschaft mit einem solchen Wert zuzugreifen, würde diesen Fehler erzeugen: ```js run alert(null.test); // error -```` +````` -## Summary +## Zusammenfassung -- Primitives except `null` and `undefined` provide many helpful methods. We will study those in the upcoming chapters. -- Formally, these methods work via temporary objects, but JavaScript engines are well tuned to optimize that internally, so they are not expensive to call. +- Primitive außer `null` und `undefined` verfügen über viele hilfreiche Methoden. Wir werden diese in den kommenden Kapiteln untersuchen. +- Formal funktionieren diese Methoden über temporäre Objekte, aber JavaScript-Engines sind gut darauf abgestimmt, dies intern zu optimieren, so dass der Aufruf dieser Methoden nicht aufwendig ist. diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index a17a4671a..4bcd74512 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -28,6 +28,6 @@ Note that `63.5` has no precision loss at all. That's because the decimal part ` ```js run -alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index e768f4d47..8e41f673d 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -2,9 +2,9 @@ In modern JavaScript, there are two types of numbers: -1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. +1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. -2. BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't exceed 253 or be less than -253. As bigints are used in few special areas, we devote them a special chapter . +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . So here we'll talk about regular numbers. Let's expand our knowledge of them. @@ -16,45 +16,56 @@ Imagine we need to write 1 billion. The obvious way is: let billion = 1000000000; ``` -But in real life, we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers. +We also can use underscore `_` as the separator: -In JavaScript, we shorten a number by appending the letter `"e"` to the number and specifying the zeroes count: +```js +let billion = 1_000_000_000; +``` + +Here the underscore `_` plays the role of the "[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above. + +In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers. + +In JavaScript, we can shorten a number by appending the letter `"e"` to it and specifying the zeroes count: ```js run let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes -alert( 7.3e9 ); // 7.3 billions (7,300,000,000) +alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000) ``` -In other words, `"e"` multiplies the number by `1` with the given zeroes count. +In other words, `e` multiplies the number by `1` with the given zeroes count. ```js -1e3 = 1 * 1000 -1.23e6 = 1.23 * 1000000 +1e3 === 1 * 1000; // e3 means *1000 +1.23e6 === 1.23 * 1000000; // e6 means *1000000 ``` -Now let's write something very small. Say, 1 microsecond (one millionth of a second): +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): ```js -let ms = 0.000001; +let mсs = 0.000001; ``` -Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as: +Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could write the same as: ```js -let ms = 1e-6; // six zeroes to the left from 1 +let mcs = 1e-6; // five zeroes to the left from 1 ``` -If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. +If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. In other words, a negative number after `"e"` means a division by 1 with the given number of zeroes: ```js // -3 divides by 1 with 3 zeroes -1e-3 = 1 / 1000 (=0.001) +1e-3 === 1 / 1000; // 0.001 // -6 divides by 1 with 6 zeroes -1.23e-6 = 1.23 / 1000000 (=0.00000123) +1.23e-6 === 1.23 / 1000000; // 0.00000123 + +// an example with a bigger number +1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times ``` ### Hex, binary and octal numbers @@ -92,13 +103,13 @@ alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` -The `base` can vary from `2` to `36`. By default it's `10`. +The `base` can vary from `2` to `36`. By default, it's `10`. Common use cases for this are: - **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. - **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. -- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base `36`: +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: ```js run alert( 123456..toString(36) ); // 2n9c @@ -107,9 +118,10 @@ Common use cases for this are: ```warn header="Two dots to call a method" Please note that two dots in `123456..toString(36)` is not a typo. If we want to call a method directly on a number, like `toString` in the example above, then we need to place two dots `..` after it. -If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method. +If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method. Also could write `(123456).toString(36)`. + ``` ## Rounding @@ -125,7 +137,7 @@ There are several built-in functions for rounding: : Rounds up: `3.1` becomes `4`, and `-1.1` becomes `-1`. `Math.round` -: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4` and `-1.1` becomes `-1`. +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. `Math.trunc` (not supported by Internet Explorer) : Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`. @@ -135,8 +147,10 @@ Here's the table to summarize the differences between them: | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | @@ -148,11 +162,11 @@ There are two ways to do so: 1. Multiply-and-divide. - For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100` (or a bigger power of 10), call the rounding function and then divide it back. + For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100`, call the rounding function and then divide it back. ```js run let num = 1.23456; - alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 + alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. The method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to `n` digits after the point and returns a string representation of the result. @@ -169,20 +183,20 @@ There are two ways to do so: alert( num.toFixed(1) ); // "12.4" ``` - Please note that result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end: + Please note that the result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end: ```js run let num = 12.34; alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits ``` - We can convert it to a number using the unary plus or a `Number()` call: `+num.toFixed(5)`. + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. ## Imprecise calculations -Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign. +Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign. -If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity: +If a number is really huge, it may overflow the 64-bit storage and become a special numeric value `Infinity`: ```js run alert( 1e500 ); // Infinity @@ -190,7 +204,7 @@ alert( 1e500 ); // Infinity What may be a little less obvious, but happens quite often, is the loss of precision. -Consider this (falsy!) test: +Consider this (falsy!) equality test: ```js run alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* @@ -204,13 +218,19 @@ Strange! What is it then if not `0.3`? alert( 0.1 + 0.2 ); // 0.30000000000000004 ``` -Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone. +Ouch! Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone. But why does this happen? A number is stored in memory in its binary form, a sequence of bits - ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form. -In other words, what is `0.1`? It is one divided by ten `1/10`, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. So, division by powers `10` is guaranteed to work well in the decimal system, but division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to work, but `1/10` becomes an endless binary fraction. @@ -230,14 +250,14 @@ That's why `0.1 + 0.2` is not exactly `0.3`. ```smart header="Not only JavaScript" The same issue exists in many other programming languages. -PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format. +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. ``` Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): ```js run let sum = 0.1 + 0.2; -alert( sum.toFixed(2) ); // 0.30 +alert( sum.toFixed(2) ); // "0.30" ``` Please note that `toFixed` always returns a string. It ensures that it has 2 digits after the decimal point. That's actually convenient if we have an e-shopping and need to show `$0.30`. For other cases, we can use the unary plus to coerce it into a number: @@ -254,7 +274,7 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` -So, multiply/divide approach reduces the error, but doesn't remove it totally. +So, the multiply/divide approach reduces the error, but doesn't remove it totally. Sometimes we could try to evade fractions at all. Like if we're dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut "tails" when needed. @@ -276,7 +296,7 @@ Another funny consequence of the internal representation of numbers is the exist That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero. -In most cases the distinction is unnoticeable, because operators are suited to treat them as the same. +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. ``` ## Tests: isFinite and isNaN @@ -296,7 +316,7 @@ They belong to the type `number`, but are not "normal" numbers, so there are spe alert( isNaN("str") ); // true ``` - But do we need this function? Can't we just use the comparison `=== NaN`? Sorry, but the answer is no. The value `NaN` is unique in that it does not equal anything, including itself: + But do we need this function? Can't we just use the comparison `=== NaN`? Unfortunately not. The value `NaN` is unique in that it does not equal anything, including itself: ```js run alert( NaN === NaN ); // false @@ -320,18 +340,46 @@ let num = +prompt("Enter a number", ''); alert( isFinite(num) ); ``` -Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. +Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. + +````smart header="`Number.isNaN` and `Number.isFinite`" +[Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) and [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) methods are the more "strict" versions of `isNaN` and `isFinite` functions. They do not autoconvert their argument into a number, but check if it belongs to the `number` type instead. + +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. -```smart header="Compare with `Object.is`" + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Note the difference: + alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type + alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion + ``` + +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false -There is a special built-in method [`Object.is`](mdn:js/Object/is) that compares values like `===`, but is more reliable for two edge cases: + // Note the difference: + alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type + alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123 + ``` + +In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforward than `isNaN` and `isFinite` functions. In practice though, `isNaN` and `isFinite` are mostly used, as they're shorter to write. +```` + +```smart header="Comparison with `Object.is`" +There is a special built-in method `Object.is` that compares values like `===`, but is more reliable for two edge cases: 1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. -2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's true, because internally the number has a sign bit that may be different even if all other bits are zeroes. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. In all other cases, `Object.is(a, b)` is the same as `a === b`. -This way of comparison is often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +We mention `Object.is` here, because it's often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). ``` @@ -345,7 +393,7 @@ alert( +"100px" ); // NaN The sole exception is spaces at the beginning or at the end of the string, as they are ignored. -But in real life we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. That's what `parseInt` and `parseFloat` are for. @@ -383,7 +431,7 @@ JavaScript has a built-in [Math](https://developer.mozilla.org/en/docs/Web/JavaS A few examples: `Math.random()` -: Returns a random number from 0 to 1 (not including 1) +: Returns a random number from 0 to 1 (not including 1). ```js run alert( Math.random() ); // 0.1234567894322 @@ -391,8 +439,8 @@ A few examples: alert( Math.random() ); // ... (any random numbers) ``` -`Math.max(a, b, c...)` / `Math.min(a, b, c...)` -: Returns the greatest/smallest from the arbitrary number of arguments. +`Math.max(a, b, c...)` and `Math.min(a, b, c...)` +: Returns the greatest and smallest from the arbitrary number of arguments. ```js run alert( Math.max(3, 5, -10, 0, 1) ); // 5 @@ -400,13 +448,13 @@ A few examples: ``` `Math.pow(n, power)` -: Returns `n` raised the given power +: Returns `n` raised to the given power. ```js run alert( Math.pow(2, 10) ); // 2 in power 10 = 1024 ``` -There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object. +There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math). ## Summary @@ -421,6 +469,13 @@ For different numeral systems: - `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ≤ base ≤ 36`. - `num.toString(base)` converts a number to a string in the numeral system with the given `base`. +For regular number tests: + +- `isNaN(value)` converts its argument to a number and then tests it for being `NaN` +- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN` +- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity` + For converting values like `12pt` and `100px` to a number: - Use `parseInt/parseFloat` for the "soft" conversion, which reads a number from a string and then returns the value they could read before the error. @@ -432,4 +487,4 @@ For fractions: More mathematical functions: -- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small, but can cover basic needs. +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index f7a332d0d..4e6663c4d 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -1,19 +1,14 @@ -We can't "replace" the first character, because strings in JavaScript are immutable. +Wir können das erste Zeichen nicht "ersetzen", da Strings in JavaScript unveränderlich sind. -But we can make a new string based on the existing one, with the uppercased first character: +Wir können jedoch einen neuen String basierend auf dem bestehenden erstellen, mit einem großgeschriebenen ersten Buchstaben: ```js let newStr = str[0].toUpperCase() + str.slice(1); ``` -There's a small problem though. If `str` is empty, then `str[0]` is `undefined`, and as `undefined` doesn't have the `toUpperCase()` method, we'll get an error. +Es gibt jedoch ein kleines Problem. Wenn `str` leer ist, dann ist `str[0]` `undefined`, und da `undefined` nicht die Methode `toUpperCase()` besitzt, erhalten wir einen Fehler. -There are two variants here: - -1. Use `str.charAt(0)`, as it always returns a string (maybe empty). -2. Add a test for an empty string. - -Here's the 2nd variant: +Der einfachste Ausweg ist, eine Überprüfung auf einen leeren String hinzuzufügen, so wie hier: ```js run demo function ucFirst(str) { @@ -24,4 +19,3 @@ function ucFirst(str) { alert( ucFirst("john") ); // John ``` - diff --git a/1-js/05-data-types/03-string/1-ucfirst/task.md b/1-js/05-data-types/03-string/1-ucfirst/task.md index ed8a1e6a7..559c0fe51 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/task.md +++ b/1-js/05-data-types/03-string/1-ucfirst/task.md @@ -2,11 +2,10 @@ importance: 5 --- -# Uppercase the first character +# Den ersten Buchstaben großschreiben -Write a function `ucFirst(str)` that returns the string `str` with the uppercased first character, for instance: +Schreibe eine Funktion `ucFirst(str)`, die den String `str` zurückgibt, wobei der erste Buchstabe großgeschrieben ist, beispielsweise: ```js ucFirst("john") == "John"; ``` - diff --git a/1-js/05-data-types/03-string/2-check-spam/solution.md b/1-js/05-data-types/03-string/2-check-spam/solution.md index de8dde57d..47738aed9 100644 --- a/1-js/05-data-types/03-string/2-check-spam/solution.md +++ b/1-js/05-data-types/03-string/2-check-spam/solution.md @@ -1,4 +1,4 @@ -To make the search case-insensitive, let's bring the string to lower case and then search: +Um die Suche unabhängig von Groß- und Kleinschreibung zu gestalten, bringen wir den String in Kleinbuchstaben und suchen dann: ```js run demo function checkSpam(str) { @@ -11,4 +11,3 @@ alert( checkSpam('buy ViAgRA now') ); alert( checkSpam('free xxxxx') ); alert( checkSpam("innocent rabbit") ); ``` - diff --git a/1-js/05-data-types/03-string/2-check-spam/task.md b/1-js/05-data-types/03-string/2-check-spam/task.md index 98b5dd8a0..d617663ab 100644 --- a/1-js/05-data-types/03-string/2-check-spam/task.md +++ b/1-js/05-data-types/03-string/2-check-spam/task.md @@ -2,15 +2,14 @@ importance: 5 --- -# Check for spam +# Überprüfung auf Spam -Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false`. +Schreibe eine Funktion `checkSpam(str)`, die `true` zurückgibt, wenn `str` 'viagra' oder 'XXX' enthält, ansonsten `false`. -The function must be case-insensitive: +Die Funktion muss Groß- und Kleinschreibung ignorieren: ```js checkSpam('buy ViAgRA now') == true checkSpam('free xxxxx') == true checkSpam("innocent rabbit") == false ``` - diff --git a/1-js/05-data-types/03-string/3-truncate/solution.md b/1-js/05-data-types/03-string/3-truncate/solution.md index 5546c47ee..114ab3b16 100644 --- a/1-js/05-data-types/03-string/3-truncate/solution.md +++ b/1-js/05-data-types/03-string/3-truncate/solution.md @@ -1,6 +1,6 @@ -The maximal length must be `maxlength`, so we need to cut it a little shorter, to give space for the ellipsis. +Die maximale Länge muss `maxlength` sein, daher müssen wir den Text ein wenig kürzen, um Platz für die Auslassungspunkte zu schaffen. -Note that there is actually a single unicode character for an ellipsis. That's not three dots. +Beachte, dass es tatsächlich ein einzelnes Unicode-Zeichen für eine Auslassung gibt. Das sind nicht drei Punkte. ```js run demo function truncate(str, maxlength) { diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index 6382029f4..fa9de8f7b 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -2,16 +2,16 @@ importance: 5 --- -# Truncate the text +# Kürze den Text -Create a function `truncate(str, maxlength)` that checks the length of the `str` and, if it exceeds `maxlength` -- replaces the end of `str` with the ellipsis character `"…"`, to make its length equal to `maxlength`. +Erstelle eine Funktion `truncate(str, maxlength)`, die die Länge des Strings `str` überprüft und - falls diese `maxlength` übersteigt - das Ende von `str` mit dem Auslassungszeichen `"…"` ersetzt, um seine Länge an `maxlength` anzupassen. -The result of the function should be the truncated (if needed) string. +Das Ergebnis der Funktion sollte der gekürzte (falls nötig) String sein. -For instance: +Zum Beispiel: ```js -truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" -truncate("Hi everyone!", 20) = "Hi everyone!" +truncate("Hi everyone!", 20) == "Hi everyone!" ``` diff --git a/1-js/05-data-types/03-string/4-extract-currency/task.md b/1-js/05-data-types/03-string/4-extract-currency/task.md index feb16e642..b69a5649f 100644 --- a/1-js/05-data-types/03-string/4-extract-currency/task.md +++ b/1-js/05-data-types/03-string/4-extract-currency/task.md @@ -2,15 +2,14 @@ importance: 4 --- -# Extract the money +# Extrahiere den Geldbetrag -We have a cost in the form `"$120"`. That is: the dollar sign goes first, and then the number. +Wir haben Kosten in der Form `"$120"`. Das heißt: das Dollarzeichen steht zuerst, und dann die Zahl. -Create a function `extractCurrencyValue(str)` that would extract the numeric value from such string and return it. +Erstelle eine Funktion `extractCurrencyValue(str)`, die den numerischen Wert aus einem solchen String extrahiert und ihn zurückgibt. -The example: +Das Beispiel: ```js alert( extractCurrencyValue('$120') === 120 ); // true ``` - diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 765823d7c..7778ab821 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -1,23 +1,23 @@ # Strings -In JavaScript, the textual data is stored as strings. There is no separate type for a single character. +In JavaScript werden Textdaten als Zeichenketten (Strings) gespeichert. Es gibt keinen separaten Typ für einzelne Zeichen. -The internal format for strings is always [UTF-16](https://en.wikipedia.org/wiki/UTF-16), it is not tied to the page encoding. +Das interne Format für Zeichenketten ist immer [UTF-16](https://de.wikipedia.org/wiki/UTF-16), es ist nicht an die Seitenkodierung gebunden. -## Quotes +## Anführungszeichen -Let's recall the kinds of quotes. +Erinnern wir uns an die Arten von Anführungszeichen. -Strings can be enclosed within either single quotes, double quotes or backticks: +Zeichenketten können entweder in einfache Anführungszeichen, doppelte Anführungszeichen oder Backticks eingeschlossen werden: ```js -let single = 'single-quoted'; -let double = "double-quoted"; +let single = 'einfach-quotiert'; +let double = "doppelt-quotiert"; -let backticks = `backticks`; +let backticks = `Backticks`; ``` -Single and double quotes are essentially the same. Backticks, however, allow us to embed any expression into the string, by wrapping it in `${…}`: +Einfache und doppelte Anführungszeichen sind im Wesentlichen gleich. Backticks erlauben es uns jedoch, jeden Ausdruck in die Zeichenkette einzubetten, indem wir ihn mit `${…}` umgeben: ```js run function sum(a, b) { @@ -27,250 +27,238 @@ function sum(a, b) { alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3. ``` -Another advantage of using backticks is that they allow a string to span multiple lines: +Ein weiterer Vorteil der Verwendung von Backticks besteht darin, dass sie ermöglichen, dass eine Zeichenkette mehrere Zeilen umfasst: ```js run -let guestList = `Guests: +let guestList = `Gäste: * John * Pete * Mary `; -alert(guestList); // a list of guests, multiple lines +alert(guestList); // eine Gästeliste, mehrere Zeilen ``` -Looks natural, right? But single or double quotes do not work this way. +Sieht natürlich aus, nicht wahr? Aber einfache oder doppelte Anführungszeichen funktionieren nicht auf diese Weise. -If we use them and try to use multiple lines, there'll be an error: +Wenn wir sie verwenden und versuchen, mehrere Zeilen zu verwenden, gibt es einen Fehler: ```js run -let guestList = "Guests: // Error: Unexpected token ILLEGAL +let guestList = "Gäste: // Fehler: Unerwartetes Token ILLEGAL * John"; ``` -Single and double quotes come from ancient times of language creation when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile. +Einfache und doppelte Anführungszeichen stammen aus der alten Zeit der Gestaltung von Programmiersprachen, als die Notwendigkeit für mehrzeilige Zeichenketten nicht berücksichtigt wurde. Backticks erschienen viel später und sind daher vielseitiger. -Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. This is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +Backticks erlauben es uns auch, eine "Template-Funktion" vor dem ersten Backtick anzugeben. Die Syntax lautet: func`string`. Die Funktion `func` wird automatisch aufgerufen, erhält die Zeichenkette `string` und eingebettete Ausdrücke und kann sie verarbeiten. Diese Funktion wird "tagged templates" genannt, sie ist selten zu sehen, aber du kannst darüber auf MDN lesen unter: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). -## Special characters +## Spezielle Zeichen -It is still possible to create multiline strings with single and double quotes by using a so-called "newline character", written as `\n`, which denotes a line break: +Es ist immer noch möglich, mehrzeilige Zeichenketten mit einfachen und doppelten Anführungszeichen zu erstellen, indem man ein sogenanntes "neue Zeile-Zeichen", dargestellt als `\n`, verwendet, das einen Zeilenumbruch darstellt: ```js run -let guestList = "Guests:\n * John\n * Pete\n * Mary"; +let guestList = "Gäste:\n * John\n * Pete\n * Mary"; -alert(guestList); // a multiline list of guests +alert(guestList); // eine mehrzeilige Gästeliste, wie oben ``` -For example, these two lines are equal, just written differently: +Als einfacheres Beispiel sind diese beiden Zeilen gleich, nur unterschiedlich geschrieben: ```js run -let str1 = "Hello\nWorld"; // two lines using a "newline symbol" +let str1 = "Hallo\nWelt"; // zwei zeilen mit einem "neue Zeile-Symbol" -// two lines using a normal newline and backticks -let str2 = `Hello -World`; +// zwei zeilen mit einer normalen neuen Zeile und Backticks +let str2 = `Hallo +Welt`; alert(str1 == str2); // true ``` -There are other, less common "special" characters. +Es gibt andere, weniger gebräuchliche spezielle Zeichen: -Here's the full list: - -| Character | Description | +| Zeichen | Beschreibung | |-----------|-------------| -|`\n`|New line| -|`\r`|Carriage return: not used alone. Windows text files use a combination of two characters `\r\n` to represent a line break. | -|`\'`, `\"`|Quotes| +|`\n`|Neue Zeile| +|`\r`|In Windows-Textdateien wird ein Zeilenumbruch durch eine Kombination von zwei Zeichen `\r\n` dargestellt, während es in Nicht-Windows-Betriebssystemen nur `\n` ist. Das ist historisch bedingt, die meisten Windows-Programme verstehen auch `\n`. | +|`\'`, `\"`, \\`|Anführungszeichen| |`\\`|Backslash| -|`\t`|Tab| -|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- kept for compatibility, not used nowadays. | -|`\xXX`|Unicode character with the given hexadecimal unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.| -|`\uXXXX`|A unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. It must be exactly 4 hex digits. | -|`\u{X…XXXXXX}` (1 to 6 hex characters)|A unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two unicode symbols, taking 4 bytes. This way we can insert long codes. | - -Examples with unicode: +|`\t`|Tabulator| +|`\b`, `\f`, `\v`| Backspace, Formularvorschub, Vertikaler Tabulator -- nur der Vollständigkeit halber erwähnt, stammen aus alter Zeit, werden heutzutage nicht genutzt (du kannst sie direkt vergessen). | -```js run -alert( "\u00A9" ); // © -alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long unicode) -alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long unicode) -``` +Wie du siehst, beginnen alle speziellen Zeichen mit dem Backslash-Zeichen `\`. Es wird auch als "Maskierungszeichen" ("escape character") bezeichnet. -All special characters start with a backslash character `\`. It is also called an "escape character". - -We might also use it if we wanted to insert a quote into the string. - -For instance: +Weil es so besonders ist, wenn wir einen tatsächlichen Backslash `\` innerhalb der Zeichenkette zeigen müssen, müssen wir ihn verdoppeln: ```js run -alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! +alert( `Der Backslash: \\` ); // Der Backslash: \ ``` -As you can see, we have to prepend the inner quote by the backslash `\'`, because otherwise it would indicate the string end. +Sogenannte maskierte Anführungszeichen `\'`, `\"`, \\` werden verwendet, um ein Anführungszeichen in eine Zeichenkette mit den gleichen Anführungszeichen einzufügen. -Of course, only to the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead: +Zum Beispiel: ```js run -alert( `I'm the Walrus!` ); // I'm the Walrus! +alert( 'Ich*!*\'*/!* bin das Walross!' ); // *!*Ich bin*/!* das Walross! ``` -Note that the backslash `\` serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above. - -But what if we need to show an actual backslash `\` within the string? +Wie du sehen kannst, müssen wir dem inneren Anführungszeichen ein Backslash `\'` voranstellen, da es sonst das Ende der Zeichenkette anzeigen würde. -That's possible, but we need to double it like `\\`: +Natürlich müssen nur jene Anführungszeichen maskiert werden, die gleich wie die umgebenden sind. Also könnten wir als elegantere Lösung stattdessen auf doppelte Anführungszeichen oder Backticks wechseln: ```js run -alert( `The backslash: \\` ); // The backslash: \ +alert( "Ich bin das Walross!" ); // Ich bin das Walross! ``` -## String length +Neben diesen speziellen Zeichen gibt es auch eine spezielle Notation für Unicode-Codes `\u…`, sie wird selten verwendet und ist im optionalem Kapitel über [Unicode](info:unicode) behandelt. -The `length` property has the string length: +## Zeichenkettenlänge + +Die Eigenschaft `length` gibt die Länge der Zeichenkette an: ```js run -alert( `My\n`.length ); // 3 +alert( `Mein\n`.length ); // 3 ``` -Note that `\n` is a single "special" character, so the length is indeed `3`. +Beachte, dass `\n` ein einzelnes "spezielles" Zeichen ist und die Länge tatsächlich `3` ist. -```warn header="`length` is a property" -People with a background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work. +```warn header="`length` ist eine Eigenschaft" +Personen mit Erfahrung in einigen anderen Sprachen vertippen sich manchmal, indem sie `str.length()` anstelle von einfach `str.length` aufrufen. Das funktioniert nicht. -Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. +Bitte beachte, dass `str.length` eine numerische Eigenschaft ist, keine Funktion. Es ist nicht notwendig, Klammern dahinter zu setzen. Nicht `.length()`, sondern `.length`. ``` -## Accessing characters +## Auf Zeichen zugreifen -To get a character at position `pos`, use square brackets `[pos]` or call the method [str.charAt(pos)](mdn:js/String/charAt). The first character starts from the zero position: +Um ein Zeichen an der Position `pos` zu erhalten, verwende eckige Klammern `[pos]` oder rufe die Methode [str.at(pos)](mdn:js/String/at) auf. Das erste Zeichen beginnt bei der Position Null: ```js run -let str = `Hello`; +let str = `Hallo`; -// the first character +// das erste Zeichen alert( str[0] ); // H -alert( str.charAt(0) ); // H +alert( str.at(0) ); // H -// the last character +// das letzte Zeichen alert( str[str.length - 1] ); // o +alert( str.at(-1) ); // o ``` -The square brackets are a modern way of getting a character, while `charAt` exists mostly for historical reasons. +Wie du sehen kannst, hat die Methode `.at(pos)` den Vorteil, dass sie negative Positionen zulässt. Wenn `pos` negativ ist, wird es vom Ende der Zeichenkette gezählt. + +Also bedeutet `.at(-1)` das letzte Zeichen und `.at(-2)` das davor usw. -The only difference between them is that if no character is found, `[]` returns `undefined`, and `charAt` returns an empty string: +Die eckigen Klammern geben `undefined` für negative Indizes zurück, zum Beispiel: ```js run -let str = `Hello`; +let str = `Hallo`; -alert( str[1000] ); // undefined -alert( str.charAt(1000) ); // '' (an empty string) +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l ``` -We can also iterate over characters using `for..of`: +Wir können auch mit `for..of` über Zeichen iterieren: ```js run -for (let char of "Hello") { - alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc) +for (let char of "Hallo") { + alert(char); // H,e,l,l,o (char wird "H", dann "e", dann "l" usw) } ``` -## Strings are immutable +## Zeichenketten sind unveränderlich -Strings can't be changed in JavaScript. It is impossible to change a character. +Zeichenketten können in JavaScript nicht verändert werden. Es ist unmöglich, ein Zeichen zu ändern. -Let's try it to show that it doesn't work: +Versuchen wir es, um zu zeigen, dass es nicht funktioniert: ```js run let str = 'Hi'; -str[0] = 'h'; // error -alert( str[0] ); // doesn't work +str[0] = 'h'; // Fehler +alert( str[0] ); // funktioniert nicht ``` -The usual workaround is to create a whole new string and assign it to `str` instead of the old one. +Die übliche Vorgehensweise besteht darin, eine ganz neue Zeichenkette zu erstellen und sie anstelle der alten `str` zuzuweisen. -For instance: +Zum Beispiel: ```js run let str = 'Hi'; -str = 'h' + str[1]; // replace the string +str = 'h' + str[1]; // ersetze die Zeichenkette alert( str ); // hi ``` -In the following sections we'll see more examples of this. +In den folgenden Abschnitten werden wir weitere Beispiele dafür sehen. -## Changing the case +## Die Groß-/Kleinschreibung ändern -Methods [toLowerCase()](mdn:js/String/toLowerCase) and [toUpperCase()](mdn:js/String/toUpperCase) change the case: +Die Methoden [toLowerCase()](mdn:js/String/toLowerCase) und [toUpperCase()](mdn:js/String/toUpperCase) ändern die Groß-/Kleinschreibung: ```js run -alert( 'Interface'.toUpperCase() ); // INTERFACE -alert( 'Interface'.toLowerCase() ); // interface +alert( 'Schnittstelle'.toUpperCase() ); // SCHNITTSTELLE +alert( 'Schnittstelle'.toLowerCase() ); // schnittstelle ``` -Or, if we want a single character lowercased: +Oder wenn wir nur einen einzelnen Buchstaben kleingeschrieben haben wollen: -```js -alert( 'Interface'[0].toLowerCase() ); // 'i' +```js run +alert( 'Schnittstelle'[0].toLowerCase() ); // 's' ``` -## Searching for a substring +## Nach einer Teilzeichenkette suchen -There are multiple ways to look for a substring within a string. +Es gibt mehrere Möglichkeiten, innerhalb einer Zeichenkette nach einer Teilzeichenkette zu suchen. ### str.indexOf -The first method is [str.indexOf(substr, pos)](mdn:js/String/indexOf). +Die erste Methode ist [str.indexOf(substr, pos)](mdn:js/String/indexOf). -It looks for the `substr` in `str`, starting from the given position `pos`, and returns the position where the match was found or `-1` if nothing can be found. +Sie sucht `substr` in `str`, beginnend bei der gegebenen Position `pos`, und gibt die Position zurück, an der die Übereinstimmung gefunden wurde oder `-1`, wenn nichts gefunden werden kann. -For instance: +Zum Beispiel: ```js run -let str = 'Widget with id'; +let str = 'Widget mit id'; -alert( str.indexOf('Widget') ); // 0, because 'Widget' is found at the beginning -alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive +alert( str.indexOf('Widget') ); // 0, weil 'Widget' am Anfang gefunden wird +alert( str.indexOf('widget') ); // -1, nicht gefunden, die Suche ist groß-/kleinschreibungsempfindlich -alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id) +alert( str.indexOf("id") ); // 1, "id" wird an der Position 1 gefunden (..idget mit id) ``` -The optional second parameter allows us to search starting from the given position. +Der optionale zweite Parameter ermöglicht es uns, die Suche ab einer bestimmten Position zu starten. -For instance, the first occurrence of `"id"` is at position `1`. To look for the next occurrence, let's start the search from position `2`: +Zum Beispiel ist das erste Vorkommen von `"id"` an Position `1`. Um nach dem nächsten Vorkommen zu suchen, starten wir die Suche ab Position `2`: ```js run -let str = 'Widget with id'; +let str = 'Widget mit id'; -alert( str.indexOf('id', 2) ) // 12 +alert( str.indexOf('id', 2) ) // 11 ``` -If we're interested in all occurrences, we can run `indexOf` in a loop. Every new call is made with the position after the previous match: +Wenn wir an allen Vorkommen interessiert sind, können wir `indexOf` in einer Schleife ausführen. Jeder neue Aufruf erfolgt mit der Position nach dem vorherigen Treffer: ```js run -let str = 'As sly as a fox, as strong as an ox'; +let str = 'So listig wie ein Fuchs, so stark wie ein Ochse'; -let target = 'as'; // let's look for it +let target = 'so'; // danach wollen wir suchen let pos = 0; while (true) { let foundPos = str.indexOf(target, pos); if (foundPos == -1) break; - alert( `Found at ${foundPos}` ); - pos = foundPos + 1; // continue the search from the next position + alert( `Gefunden bei ${foundPos}` ); + pos = foundPos + 1; // setze die Suche ab der nächsten Position fort } ``` -The same algorithm can be layed out shorter: +Der gleiche Algorithmus kann kürzer dargestellt werden: ```js run -let str = "As sly as a fox, as strong as an ox"; -let target = "as"; +let str = "So listig wie ein Fuchs, so stark wie ein Ochse"; +let target = "so"; *!* let pos = -1; @@ -281,232 +269,192 @@ while ((pos = str.indexOf(target, pos + 1)) != -1) { ``` ```smart header="`str.lastIndexOf(substr, position)`" -There is also a similar method [str.lastIndexOf(substr, position)](mdn:js/String/lastIndexOf) that searches from the end of a string to its beginning. +Es gibt auch eine ähnliche Methode [str.lastIndexOf(substr, position)](mdn:js/String/lastIndexOf), die vom Ende eines Strings zum Anfang durchsucht. -It would list the occurrences in the reverse order. +Sie würde die Vorkommen in umgekehrter Reihenfolge auflisten. ``` -There is a slight inconvenience with `indexOf` in the `if` test. We can't put it in the `if` like this: +Ein kleines Problem bei `indexOf` ist die Verwendung im `if`. Wir können es nicht wie folgt in die `if`-Bedingung setzen: ```js run -let str = "Widget with id"; +let str = "Widget mit id"; if (str.indexOf("Widget")) { - alert("We found it"); // doesn't work! + alert("Wir haben es gefunden"); // funktioniert nicht! } ``` -The `alert` in the example above doesn't show because `str.indexOf("Widget")` returns `0` (meaning that it found the match at the starting position). Right, but `if` considers `0` to be `false`. +Das `alert` im Beispiel oben erscheint nicht, weil `str.indexOf("Widget")` `0` zurückgibt (das bedeutet, dass es die Übereinstimmung am Anfang gefunden hat). Richtig, aber `if` betrachtet `0` als `false`. -So, we should actually check for `-1`, like this: +Wir sollten also tatsächlich nach `-1` überprüfen, so wie hier: ```js run -let str = "Widget with id"; +let str = "Widget mit id"; *!* if (str.indexOf("Widget") != -1) { */!* - alert("We found it"); // works now! + alert("Wir haben es gefunden"); // jetzt funktioniert es! } ``` -#### The bitwise NOT trick - -One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. - -In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`. - -For instance: - -```js run -alert( ~2 ); // -3, the same as -(2+1) -alert( ~1 ); // -2, the same as -(1+1) -alert( ~0 ); // -1, the same as -(0+1) -*!* -alert( ~-1 ); // 0, the same as -(-1+1) -*/!* -``` - -As we can see, `~n` is zero only if `n == -1` (that's for any 32-bit signed integer `n`). - -So, the test `if ( ~str.indexOf("...") )` is truthy only if the result of `indexOf` is not `-1`. In other words, when there is a match. - -People use it to shorten `indexOf` checks: - -```js run -let str = "Widget"; - -if (~str.indexOf("Widget")) { - alert( 'Found it!' ); // works -} -``` - -It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used in old code, so we should understand it. - -Just remember: `if (~str.indexOf(...))` reads as "if found". - -To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check is correct only if a string is not that long. - -Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below). - ### includes, startsWith, endsWith -The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` contains `substr` within. +Die modernere Methode [str.includes(substr, pos)](mdn:js/String/includes) gibt `true/false` zurück, je nachdem, ob `str` `substr` enthält. -It's the right choice if we need to test for the match, but don't need its position: +Das ist die richtige Wahl, wenn wir auf das Vorhandensein testen müssen, aber dessen Position nicht benötigen: ```js run -alert( "Widget with id".includes("Widget") ); // true +alert( "Widget mit id".includes("Widget") ); // true -alert( "Hello".includes("Bye") ); // false +alert( "Hallo".includes("Tschüss") ); // false ``` -The optional second argument of `str.includes` is the position to start searching from: +Das optionale zweite Argument von `str.includes` ist die Position, ab der gesucht werden soll: ```js run alert( "Widget".includes("id") ); // true -alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id" +alert( "Widget".includes("id", 3) ); // false, ab Position 3 gibt es kein "id" ``` -The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say: +Die Methoden [str.startsWith](mdn:js/String/startsWith) und [str.endsWith](mdn:js/String/endsWith) tun genau das, was sie ausdrücken: ```js run -alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid" -alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get" +alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" beginnt mit "Wid" +alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" endet mit "get" ``` -## Getting a substring +## Einen Teilstring erhalten -There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`. +Es gibt in JavaScript drei Methoden, um einen Teilstring zu erhalten: `substring`, `substr` und `slice`. `str.slice(start [, end])` -: Returns the part of the string from `start` to (but not including) `end`. +: Gibt den Teil der Zeichenkette von `start` bis (aber nicht einschließlich) `end` zurück. - For instance: + Zum Beispiel: ```js run let str = "stringify"; - alert( str.slice(0, 5) ); // 'strin', the substring from 0 to 5 (not including 5) - alert( str.slice(0, 1) ); // 's', from 0 to 1, but not including 1, so only character at 0 + alert( str.slice(0, 5) ); // 'strin', der Teilstring von 0 bis 5 (5 nicht eingeschlossen) + alert( str.slice(0, 1) ); // 's', von 0 bis 1, aber nicht inklusive 1, also nur das Zeichen bei 0 ``` - If there is no second argument, then `slice` goes till the end of the string: + Wenn es keinen zweiten Argument gibt, dann geht `slice` bis zum Ende der Zeichenkette: ```js run let str = "st*!*ringify*/!*"; - alert( str.slice(2) ); // 'ringify', from the 2nd position till the end + alert( str.slice(2) ); // 'ringify', von der 2. Position bis zum Ende ``` - Negative values for `start/end` are also possible. They mean the position is counted from the string end: + Negative Werte für `start/end` sind ebenfalls möglich. Sie bedeuten, dass die Position vom Ende des Strings gezählt wird: ```js run let str = "strin*!*gif*/!*y"; - // start at the 4th position from the right, end at the 1st from the right + // beginne bei der 4. Position von rechts, endet bei der 1. von rechts alert( str.slice(-4, -1) ); // 'gif' ``` `str.substring(start [, end])` -: Returns the part of the string *between* `start` and `end`. +: Gibt den Teil der Zeichenkette *zwischen* `start` und `end` zurück (end nicht eingeschlossen). - This is almost the same as `slice`, but it allows `start` to be greater than `end`. + Dies ist fast das Gleiche wie `slice`, aber es erlaubt `start`, größer als `end` zu sein (in diesem Fall werden einfach die `start`- und `end`-Werte getauscht). - For instance: + Zum Beispiel: ```js run let str = "st*!*ring*/!*ify"; - // these are same for substring + // diese sind gleich für substring alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" - // ...but not for slice: - alert( str.slice(2, 6) ); // "ring" (the same) - alert( str.slice(6, 2) ); // "" (an empty string) + // ...aber nicht für slice: + alert( str.slice(2, 6) ); // "ring" (das gleiche) + alert( str.slice(6, 2) ); // "" (ein leerer String) ``` - Negative arguments are (unlike slice) not supported, they are treated as `0`. + Negative Argumente werden (im Gegensatz zu slice) nicht unterstützt und als `0` behandelt. `str.substr(start [, length])` -: Returns the part of the string from `start`, with the given `length`. +: Gibt den Teil der Zeichenkette von `start` bis zur gegebenen Länge `length` zurück. - In contrast with the previous methods, this one allows us to specify the `length` instead of the ending position: + Im Gegensatz zu den vorherigen Methoden erlaubt diese, die `length` anstelle der Endposition anzugeben: ```js run let str = "st*!*ring*/!*ify"; - alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters + alert( str.substr(2, 4) ); // 'ring', ab der 2. Position 4 Zeichen bekommen ``` - The first argument may be negative, to count from the end: + Das erste Argument kann negativ sein, um vom Ende zu zählen: ```js run let str = "strin*!*gi*/!*fy"; - alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters + alert( str.substr(-4, 2) ); // 'gi', ab der 4. Position 2 Zeichen bekommen ``` -Let's recap these methods to avoid any confusion: + Diese Methode ist im [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) der Sprachspezifikation enthalten. Das bedeutet, dass sie nur von in Browsern gehosteten Javascript-Engines unterstützt werden sollte, und es wird nicht empfohlen, sie zu verwenden. In der Praxis wird sie jedoch überall unterstützt. + +Lass uns diese Methoden rekapitulieren, um jegliche Verwirrung zu vermeiden: -| method | selects... | negatives | +| Methode | selektiert... | negatives | |--------|-----------|-----------| -| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | -| `substring(start, end)` | between `start` and `end` | negative values mean `0` | -| `substr(start, length)` | from `start` get `length` characters | allows negative `start` | +| `slice(start, end)` | von `start` bis `end` (ohne `end` einzuschließen) | erlaubt negative Werte | +| `substring(start, end)` | zwischen `start` und `end` (ohne `end` einzuschließen) | negative Werte bedeuten `0` | +| `substr(start, length)` | von `start` `length` Zeichen holen | erlaubt negatives `start` | + +```smart header="Welche soll man wählen?" +Alle können die Aufgabe erfüllen. Formal hat `substr` einen kleinen Nachteil: Es wird nicht in der Hauptspezifikation von JavaScript beschrieben, sondern in Anhang B, der Browser-spezifische Funktionen umfasst, die hauptsächlich aus historischen Gründen existieren. Daher könnte es sein, dass Nicht-Browser-Umgebungen sie nicht unterstützen. Aber in der Praxis funktioniert sie überall. -```smart header="Which one to choose?" -All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere. +Von den anderen beiden Varianten ist `slice` ein bisschen flexibler, es erlaubt negative Argumente und ist kürzer zu schreiben. -Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. So, it's enough to remember solely `slice` of these three methods. +Praktisch gesehen ist es also genug, sich nur `slice` zu merken. ``` -## Comparing strings +## Strings vergleichen -As we know from the chapter , strings are compared character-by-character in alphabetical order. +Wie wir aus dem Kapitel wissen, werden Strings Zeichen-für-Zeichen in alphabetischer Reihenfolge verglichen. -Although, there are some oddities. +Allerdings gibt es einige Kuriositäten. -1. A lowercase letter is always greater than the uppercase: +1. Ein Kleinbuchstabe ist immer größer als ein Großbuchstabe: ```js run alert( 'a' > 'Z' ); // true ``` -2. Letters with diacritical marks are "out of order": +2. Buchstaben mit diakritischen Zeichen fallen "aus der Reihe": ```js run alert( 'Österreich' > 'Zealand' ); // true ``` - This may lead to strange results if we sort these country names. Usually people would expect `Zealand` to come after `Österreich` in the list. + Das kann zu seltsamen Ergebnissen führen, wenn wir diese Ländernamen sortieren. Normalerweise würde man erwarten, dass `Zealand` nach `Österreich` in der Liste kommt. -To understand what happens, let's review the internal representation of strings in JavaScript. +Um zu verstehen, was passiert, sollten wir uns bewusst sein, dass Zeichenketten in Javascript mit [UTF-16](https://en.wikipedia.org/wiki/UTF-16) kodiert sind. Das heißt: Jeder Buchstabe hat einen entsprechenden numerischen Code. -All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back. +Es gibt spezielle Methoden, die es ermöglichen, den Buchstaben für den Code zu erhalten und umgekehrt: `str.codePointAt(pos)` -: Returns the code for the character at position `pos`: +: Gibt eine Dezimalzahl zurück, die den Code für das Zeichen an der Position `pos` repräsentiert: ```js run - // different case letters have different codes - alert( "z".codePointAt(0) ); // 122 + // Unterschiedliche Groß- und Kleinbuchstaben haben unterschiedliche Codes alert( "Z".codePointAt(0) ); // 90 + alert( "z".codePointAt(0) ); // 122 + alert( "z".codePointAt(0).toString(16) ); // 7a (wenn wir einen Hexadezimalwert benötigen) ``` `String.fromCodePoint(code)` -: Creates a character by its numeric `code` +: Erstellt einen Buchstaben anhand seines numerischen `code` ```js run alert( String.fromCodePoint(90) ); // Z + alert( String.fromCodePoint(0x5a) ); // Z (wir können auch einen Hexwert als Argument verwenden) ``` - We can also add unicode characters by their codes using `\u` followed by the hex code: - - ```js run - // 90 is 5a in hexadecimal system - alert( '\u005a' ); // Z - ``` - -Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them: +Schauen wir uns jetzt die Zeichen mit den Codes `65..220` an (das lateinische Alphabet und ein bisschen extra), indem wir eine Zeichenkette aus ihnen erstellen: ```js run let str = ''; @@ -515,165 +463,60 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Ausgabe: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` -See? Capital characters go first, then a few special ones, then lowercase characters, and `Ö` near the end of the output. +Siehst Du? Großbuchstaben kommen zuerst, dann einige Sonderzeichen, dann Kleinbuchstaben, und `Ö` fast am Ende der Ausgabe. -Now it becomes obvious why `a > Z`. +Jetzt wird klar, warum `a > Z`. -The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90). +Die Zeichen werden anhand ihres numerischen Codes verglichen. Der größere Code bedeutet, dass das Zeichen größer ist. Der Code für `a` (97) ist größer als der Code für `Z` (90). -- All lowercase letters go after uppercase letters because their codes are greater. -- Some letters like `Ö` stand apart from the main alphabet. Here, it's code is greater than anything from `a` to `z`. +- Alle Kleinbuchstaben folgen nach den Großbuchstaben, weil ihre Codes größer sind. +- Einige Buchstaben wie `Ö` stehen abseits vom Hauptalphabet. Hier ist sein Code größer als alles von `a` bis `z`. -### Correct comparisons +### Korrekte Vergleiche [#correct-comparisons] -The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages. +Der "richtige" Algorithmus für den Vergleich von Zeichenketten ist komplizierter, als es scheint, weil die Alphabetisierung für verschiedene Sprachen unterschiedlich ist. -So, the browser needs to know the language to compare. +Daher muss der Browser die Sprache kennen, um zu vergleichen. -Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). +Zum Glück unterstützen moderne Browser den Internationalisierungsstandard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). -It provides a special method to compare strings in different languages, following their rules. +Er stellt eine spezielle Methode zur Verfügung, um Zeichenketten in verschiedenen Sprachen gemäß ihren Regeln zu vergleichen. -The call [str.localeCompare(str2)](mdn:js/String/localeCompare) returns an integer indicating whether `str` is less, equal or greater than `str2` according to the language rules: +Der Aufruf von [str.localeCompare(str2)](mdn:js/String/localeCompare) gibt eine Ganzzahl zurück, die angibt, ob `str` kleiner, gleich oder größer als `str2` gemäß den Sprachregeln ist: -- Returns a negative number if `str` is less than `str2`. -- Returns a positive number if `str` is greater than `str2`. -- Returns `0` if they are equivalent. +- Gibt eine negative Nummer zurück, wenn `str` kleiner als `str2` ist. +- Gibt eine positive Nummer zurück, wenn `str` größer als `str2` ist. +- Gibt `0` zurück, wenn sie gleichwertig sind. -For instance: +Beispielsweise: ```js run alert( 'Österreich'.localeCompare('Zealand') ); // -1 ``` -This method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), which allows it to specify the language (by default taken from the environment, letter order depends on the language) and setup additional rules like case sensitivity or should `"a"` and `"á"` be treated as the same etc. - -## Internals, Unicode - -```warn header="Advanced knowledge" -The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters or other rare symbols. - -You can skip the section if you don't plan to support them. -``` - -### Surrogate pairs - -All frequently used characters have 2-byte codes. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation. - -But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair". - -The length of such symbols is `2`: - -```js run -alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X -alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY -alert( '𩷶'.length ); // 2, a rare Chinese hieroglyph -``` - -Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! - -We actually have a single symbol in each of the strings above, but the `length` shows a length of `2`. - -`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. - -Getting a symbol can be tricky, because surrogate pairs are treated as two characters: - -```js run -alert( '𝒳'[0] ); // strange symbols... -alert( '𝒳'[1] ); // ...pieces of the surrogate pair -``` - -Note that pieces of the surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. - -Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. - -In the case above: - -```js run -// charCodeAt is not surrogate-pair aware, so it gives codes for parts - -alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff -alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff -``` - -You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. - -### Diacritical marks and normalization - -In many languages there are symbols that are composed of the base character with a mark above/under it. - -For instance, the letter `a` can be the base character for: `àáâäãåā`. Most common "composite" character have their own code in the UTF-16 table. But not all of them, because there are too many possible combinations. - -To support arbitrary compositions, UTF-16 allows us to use several unicode characters: the base character followed by one or many "mark" characters that "decorate" it. - -For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. - -```js run -alert( 'S\u0307' ); // Ṡ -``` - -If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. - -For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. - -For example: - -```js run -alert( 'S\u0307\u0323' ); // Ṩ -``` - -This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different unicode compositions. - -For instance: - -```js run -let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below -let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above - -alert( `s1: ${s1}, s2: ${s2}` ); - -alert( s1 == s2 ); // false though the characters look identical (?!) -``` - -To solve this, there exists a "unicode normalization" algorithm that brings each string to the single "normal" form. - -It is implemented by [str.normalize()](mdn:js/String/normalize). - -```js run -alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true -``` - -It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). - -```js run -alert( "S\u0307\u0323".normalize().length ); // 1 - -alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true -``` - -In reality, this is not always the case. The reason being that the symbol `Ṩ` is "common enough", so UTF-16 creators included it in the main table and gave it the code. +Diese Methode hat tatsächlich zwei zusätzliche Argumente, die in [der Dokumentation](mdn:js/String/localeCompare) spezifiziert sind und es uns ermöglichen, die Sprache festzulegen (standardmäßig aus der Umgebung abgeleitet, Buchstabenreihenfolge hängt von der Sprache ab) und zusätzliche Regeln einzustellen, wie Empfindlichkeit für Groß-/Kleinschreibung oder ob `"a"` und `"á"` als dasselbe behandelt werden sollen usw. -If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. +## Zusammenfassung -## Summary +- Es gibt 3 Arten von Anführungszeichen. Backticks erlauben es, dass eine Zeichenkette mehrere Zeilen umfasst und Ausdrücke `${…}` eingebettet werden können. +- Wir können Sonderzeichen verwenden, wie z.B. einen Zeilenumbruch `\n`. +- Um ein Zeichen zu erhalten, benutze: `[]` oder die Methode `at`. +- Um eine Teilzeichenkette zu erhalten, benutze: `slice` oder `substring`. +- Um eine Zeichenkette in Klein-/Großbuchstaben umzuwandeln, verwende: `toLowerCase/toUpperCase`. +- Um nach einer Teilzeichenkette zu suchen, verwende: `indexOf` oder `includes/startsWith/endsWith` für einfache Überprüfungen. +- Um Zeichenketten entsprechend der Sprache zu vergleichen, verwende: `localeCompare`, sonst werden sie nach Zeichencodes verglichen. -- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`. -- Strings in JavaScript are encoded using UTF-16. -- We can use special characters like `\n` and insert letters by their unicode using `\u...`. -- To get a character, use: `[]`. -- To get a substring, use: `slice` or `substring`. -- To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. -- To look for a substring, use: `indexOf`, or `includes/startsWith/endsWith` for simple checks. -- To compare strings according to the language, use: `localeCompare`, otherwise they are compared by character codes. +Es gibt mehrere andere hilfreiche Methoden in Zeichenketten: -There are several other helpful methods in strings: +- `str.trim()` -- entfernt ("trimmt") Leerzeichen am Anfang und Ende der Zeichenkette. +- `str.repeat(n)` -- wiederholt die Zeichenkette `n`-mal. +- ...und mehr, zu finden im [Handbuch](mdn:js/String). -- `str.trim()` -- removes ("trims") spaces from the beginning and end of the string. -- `str.repeat(n)` -- repeats the string `n` times. -- ...and more to be found in the [manual](mdn:js/String). +Zeichenketten haben auch Methoden zur Durchführung von Such-/Ersetzungsvorgängen mit regulären Ausdrücken. Das ist jedoch ein großes Thema, daher wird es in einem separaten Tutorialabschnitt erklärt . -Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section . +Außerdem, wie bisher bekannt, ist es wichtig zu wissen, dass Zeichenketten auf der Unicode-Kodierung basieren und daher Probleme beim Vergleich auftreten können. Es gibt mehr über Unicode im Kapitel . diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md index daadf494b..7e1ca3bde 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md @@ -57,9 +57,9 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` -The solution has a time complexety of [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer. +The solution has a time complexity of [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer. -For big arrays (1000, 10000 or more items) such algorithms can lead to a serious sluggishness. +For big arrays (1000, 10000 or more items) such algorithms can lead to serious sluggishness. # Fast solution @@ -91,4 +91,4 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 The algorithm requires exactly 1 array pass, so the time complexity is O(n). -You can find more detail information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. +You can find more detailed information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 16d14071f..d4551c79c 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -8,7 +8,7 @@ Let's try 5 array operations. 1. Create an array `styles` with items "Jazz" and "Blues". 2. Append "Rock-n-Roll" to the end. -3. Replace the value in the middle by "Classics". Your code for finding the middle value should work for any arrays with odd length. +3. Replace the value in the middle with "Classics". Your code for finding the middle value should work for any arrays with odd length. 4. Strip off the first value of the array and show it. 5. Prepend `Rap` and `Reggae` to the array. diff --git a/1-js/05-data-types/04-array/3-call-array-this/task.md b/1-js/05-data-types/04-array/3-call-array-this/task.md index 340c5feef..f1e13499c 100644 --- a/1-js/05-data-types/04-array/3-call-array-this/task.md +++ b/1-js/05-data-types/04-array/3-call-array-this/task.md @@ -11,7 +11,7 @@ let arr = ["a", "b"]; arr.push(function() { alert( this ); -}) +}); arr[2](); // ? ``` diff --git a/1-js/05-data-types/04-array/array-pop.svg b/1-js/05-data-types/04-array/array-pop.svg index 351916051..82b112b4a 100644 --- a/1-js/05-data-types/04-array/array-pop.svg +++ b/1-js/05-data-types/04-array/array-pop.svg @@ -1 +1 @@ -0123"Apple""Orange""Pear""Lemon"length = 4clear012"Apple""Orange""Pear"length = 3 \ No newline at end of file +0123"Apple""Orange""Pear""Lemon"length = 4clear012"Apple""Orange""Pear"length = 3 \ No newline at end of file diff --git a/1-js/05-data-types/04-array/array-shift.svg b/1-js/05-data-types/04-array/array-shift.svg index 09236b9d1..9485a3c96 100644 --- a/1-js/05-data-types/04-array/array-shift.svg +++ b/1-js/05-data-types/04-array/array-shift.svg @@ -1 +1 @@ -123"Orange""Pear""Lemon"length = 423"Orange""Pear""Lemon"length = 3clearmove elements to the left0"Apple"012"Orange""Pear""Lemon"11 \ No newline at end of file +123"Orange""Pear""Lemon"length = 423"Orange""Pear""Lemon"length = 3clearmove elements to the left0"Apple"012"Orange""Pear""Lemon"11 \ No newline at end of file diff --git a/1-js/05-data-types/04-array/array-speed.svg b/1-js/05-data-types/04-array/array-speed.svg index 5660cd5ec..41f7d998b 100644 --- a/1-js/05-data-types/04-array/array-speed.svg +++ b/1-js/05-data-types/04-array/array-speed.svg @@ -1 +1 @@ -0123popunshiftpushshift \ No newline at end of file +0123popunshiftpushshift \ No newline at end of file diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 33498f40a..e71e86a5b 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -92,6 +92,38 @@ let fruits = [ The "trailing comma" style makes it easier to insert/remove items, because all lines become alike. ```` +## Get last elements with "at" + +[recent browser="new"] + +Let's say we want the last element of the array. + +Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`. + +Although, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally. + +We can explicitly calculate the last element index and then access it: `fruits[fruits.length - 1]`. + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +alert( fruits[fruits.length-1] ); // Plum +``` + +A bit cumbersome, isn't it? We need to write the variable name twice. + +Luckily, there's a shorter syntax: `fruits.at(-1)`: + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +// same as fruits[fruits.length-1] +alert( fruits.at(-1) ); // Plum +``` + +In other words, `arr.at(i)`: +- is exactly the same as `arr[i]`, if `i >= 0`. +- for negative values of `i`, it steps back from the end of the array. ## Methods pop/push, shift/unshift @@ -121,9 +153,9 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out). -Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end. +Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements, both to/from the beginning or the end. -In computer science the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). +In computer science, the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). **Methods that work with the end of the array:** @@ -138,6 +170,8 @@ In computer science the data structure that allows this, is called [deque](https alert( fruits ); // Apple, Orange ``` + Both `fruits.pop()` and `fruits.at(-1)` return the last element of the array, but `fruits.pop()` also modifies the array by removing it. + `push` : Append the element to the end of the array: @@ -193,7 +227,7 @@ An array is a special kind of object. The square brackets used to access a prope They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object. -Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. +Remember, there are only eight basic data types in JavaScript (see the [Data types](info:types) chapter for more info). Array is an object and thus behaves like an object. For instance, it is copied by reference: @@ -209,7 +243,7 @@ arr.push("Pear"); // modify the array by reference alert( fruits ); // Banana, Pear - 2 items now ``` -...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast. +...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast. But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object. @@ -247,7 +281,7 @@ Why is it faster to work with the end of an array than with its beginning? Let's fruits.shift(); // take 1 element from the start ``` -It's not enough to take and remove the element with the number `0`. Other elements need to be renumbered as well. +It's not enough to take and remove the element with the index `0`. Other elements need to be renumbered as well. The `shift` operation must do 3 things: @@ -365,11 +399,11 @@ There is one more syntax to create an array: let arr = *!*new Array*/!*("Apple", "Pear", "etc"); ``` -It's rarely used, because square brackets `[]` are shorter. Also there's a tricky feature with it. +It's rarely used, because square brackets `[]` are shorter. Also, there's a tricky feature with it. If `new Array` is called with a single argument which is a number, then it creates an array *without items, but with the given length*. -Let's see how one can shoot themself in the foot: +Let's see how one can shoot themselves in the foot: ```js run let arr = new Array(2); // will it create an array of [2] ? @@ -379,9 +413,7 @@ alert( arr[0] ); // undefined! no elements. alert( arr.length ); // length 2 ``` -In the code above, `new Array(number)` has all elements `undefined`. - -To evade such surprises, we usually use square brackets, unless we really know what we're doing. +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. ## Multidimensional arrays @@ -394,7 +426,7 @@ let matrix = [ [7, 8, 9] ]; -alert( matrix[1][1] ); // 5, the central element +alert( matrix[0][1] ); // 2, the second value of the first inner array ``` ## toString @@ -429,25 +461,77 @@ alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` +## Don't compare arrays with == + +Arrays in JavaScript, unlike some other programming languages, shouldn't be compared with operator `==`. + +This operator has no special treatment for arrays, it works with them as with any objects. + +Let's recall the rules: + +- Two objects are equal `==` only if they're references to the same object. +- If one of the arguments of `==` is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter . +- ...With an exception of `null` and `undefined` that equal `==` each other and nothing else. + +The strict comparison `===` is even simpler, as it doesn't convert types. + +So, if we compare arrays with `==`, they are never the same, unless we compare two variables that reference exactly the same array. + +For example: +```js run +alert( [] == [] ); // false +alert( [0] == [0] ); // false +``` + +These arrays are technically different objects. So they aren't equal. The `==` operator doesn't do item-by-item comparison. + +Comparison with primitives may give seemingly strange results as well: + +```js run +alert( 0 == [] ); // true + +alert('0' == [] ); // false +``` + +Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`. + +Then the comparison process goes on with the primitives, as described in the chapter : + +```js run +// after [] was converted to '' +alert( 0 == '' ); // true, as '' becomes converted to number 0 + +alert('0' == '' ); // false, no type conversion, different strings +``` + +So, how to compare arrays? + +That's simple: don't use the `==` operator. Instead, compare them item-by-item in a loop or using iteration methods explained in the next chapter. + ## Summary Array is a special kind of object, suited to storing and managing ordered data items. -- The declaration: +The declaration: - ```js - // square brackets (usual) - let arr = [item1, item2...]; +```js +// square brackets (usual) +let arr = [item1, item2...]; - // new Array (exceptionally rare) - let arr = new Array(item1, item2...); - ``` +// new Array (exceptionally rare) +let arr = new Array(item1, item2...); +``` - The call to `new Array(number)` creates an array with the given length, but without elements. +The call to `new Array(number)` creates an array with the given length, but without elements. - The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods. - If we shorten `length` manually, the array is truncated. +Getting the elements: + +- we can get element by its index, like `arr[0]` +- also we can use `at(i)` method that allows negative indexes. For negative values of `i`, it steps back from the end of the array. If `i >= 0`, it works same as `arr[i]`. + We can use an array as a deque with the following operations: - `push(...items)` adds `items` to the end. @@ -460,4 +544,8 @@ To loop over the elements of the array: - `for (let item of arr)` -- the modern syntax for items only, - `for (let i in arr)` -- never use. -We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter . +To compare arrays, don't use the `==` operator (as well as `>`, `<` and others), as they have no special treatment for arrays. They handle them as any objects, and it's not what we usually want. + +Instead you can use `for..of` loop to compare arrays item-by-item. + +We will continue with arrays and study more methods to add, remove, extract elements and sort arrays in the next chapter . diff --git a/1-js/05-data-types/04-array/queue.svg b/1-js/05-data-types/04-array/queue.svg index 0ed2f1cd7..e89a3dcd2 100644 --- a/1-js/05-data-types/04-array/queue.svg +++ b/1-js/05-data-types/04-array/queue.svg @@ -1 +1 @@ -pushshift \ No newline at end of file +pushshift \ No newline at end of file diff --git a/1-js/05-data-types/04-array/stack.svg b/1-js/05-data-types/04-array/stack.svg index dcc600e71..ae0c474d0 100644 --- a/1-js/05-data-types/04-array/stack.svg +++ b/1-js/05-data-types/04-array/stack.svg @@ -1 +1 @@ -pushpop \ No newline at end of file +pushpop \ No newline at end of file diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md index d3c8f8eb1..7f0082357 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -4,7 +4,7 @@ importance: 4 # Create keyed object from array -Let's say we received an array of users in the form `{id:..., name:..., age... }`. +Let's say we received an array of users in the form `{id:..., name:..., age:... }`. Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/task.md b/1-js/05-data-types/05-array-methods/2-filter-range/task.md index 18b2c1d9b..46e47c93d 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/task.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/task.md @@ -4,7 +4,7 @@ importance: 4 # Filter range -Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements between `a` and `b` in it and returns an array of them. +Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements with values higher or equal to `a` and lower or equal to `b` and return a result as an array. The function should not modify the array. It should return the new array. diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js index db32d9a11..241b74c6e 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js @@ -4,13 +4,13 @@ describe("filterRangeInPlace", function() { let arr = [5, 3, 8, 1]; - filterRangeInPlace(arr, 1, 4); + filterRangeInPlace(arr, 2, 5); - assert.deepEqual(arr, [3, 1]); + assert.deepEqual(arr, [5, 3]); }); it("doesn't return anything", function() { assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4)); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js index 45ef1619d..f62452a5f 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js @@ -10,14 +10,14 @@ function Calculator() { let split = str.split(' '), a = +split[0], op = split[1], - b = +split[2] + b = +split[2]; if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } return this.methods[op](a, b); - } + }; this.addMethod = function(name, func) { this.methods[name] = func; diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md index 5d8bf4a13..2d8d4fb0e 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md @@ -25,7 +25,7 @@ alert( usersMapped[0].id ); // 1 alert( usersMapped[0].fullName ); // John Smith ``` -Please note that in for the arrow functions we need to use additional brackets. +Please note that in the arrow functions we need to use additional brackets. We can't write like this: ```js diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md index 9f1ade707..cfaf9761a 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md @@ -1,6 +1,6 @@ ```js run no-beautify function sortByAge(arr) { - arr.sort((a, b) => a.age > b.age ? 1 : -1); + arr.sort((a, b) => a.age - b.age); } let john = { name: "John", age: 25 }; diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 301696440..853645958 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,6 @@ # Array methods -Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups. +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. ## Add/remove items @@ -32,19 +32,19 @@ alert( arr.length ); // 3 The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. -That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now. +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. So, special methods should be used. -The [arr.splice(start)](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements. +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. The syntax is: ```js -arr.splice(index[, deleteCount, elem1, ..., elemN]) +arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` -It starts from the position `index`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. +It modifies `arr` starting from the index `start`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. This method is easy to grasp by examples. @@ -62,7 +62,7 @@ alert( arr ); // ["I", "JavaScript"] Easy, right? Starting from the index `1` it removed `1` element. -In the next example we remove 3 elements and replace them with the other two: +In the next example, we remove 3 elements and replace them with the other two: ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +84,7 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- array of removed elements ``` -The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`: +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5 ### slice -The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`. +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. The syntax is: @@ -124,7 +124,7 @@ arr.slice([start], [end]) It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed. -It's similar to a string method `str.slice`, but instead of substrings it makes subarrays. +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. For instance: @@ -206,7 +206,7 @@ The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for ever The syntax: ```js arr.forEach(function(item, index, array) { - // ... do something with item + // ... do something with an item }); ``` @@ -234,12 +234,13 @@ Now let's cover methods that search in an array. ### indexOf/lastIndexOf and includes -The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters: +The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters: - `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. -- `arr.lastIndexOf(item, from)` -- same, but looks for from right to left. - `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. + For instance: ```js run @@ -252,21 +253,33 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero. +Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. -If we want to check for inclusion, and don't want to know the exact index, then `arr.includes` is preferred. +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. -Also, a very minor difference of `includes` is that it correctly handles `NaN`, unlike `indexOf/lastIndexOf`: +The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. + +```js run +let fruits = ['Apple', 'Orange', 'Apple'] + +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) +``` + +````smart header="The `includes` method handles `NaN` correctly" +A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`: ```js run const arr = [NaN]; -alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN) +alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) alert( arr.includes(NaN) );// true (correct) ``` +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +```` -### find and findIndex +### find and findIndex/findLastIndex -Imagine we have an array of objects. How do we find an object with the specific condition? +Imagine we have an array of objects. How do we find an object with a specific condition? Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy. @@ -284,7 +297,7 @@ The function is called for elements of the array, one after another: - `index` is its index. - `array` is the array itself. -If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned. +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`: @@ -300,11 +313,30 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` -In real life arrays of objects is a common thing, so the `find` method is very useful. +In real life, arrays of objects are a common thing, so the `find` method is very useful. Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used. -The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself and `-1` is returned when nothing is found. +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. + +The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. + +Here's an example: + +```js run +let users = [ + {id: 1, name: "John"}, + {id: 2, name: "Pete"}, + {id: 3, name: "Mary"}, + {id: 4, name: "John"} +]; + +// Find the index of the first John +alert(users.findIndex(user => user.name == 'John')); // 0 + +// Find the index of the last John +alert(users.findLastIndex(user => user.name == 'John')); // 3 +``` ### filter @@ -389,6 +421,7 @@ Literally, all elements are converted to strings for comparisons. For strings, l To use our own sorting order, we need to supply a function as the argument of `arr.sort()`. The function should compare two arbitrary values and return: + ```js function compare(a, b) { if (a > b) return 1; // if the first value is greater than the second @@ -417,15 +450,16 @@ alert(arr); // *!*1, 2, 15*/!* Now it works as intended. -Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. -The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. +The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. -By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them: +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); + return a - b; }); ``` @@ -492,7 +526,7 @@ Here's the situation from real life. We are writing a messaging app, and the per The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`. -In the example below, we split by a comma followed by space: +In the example below, we split by a comma followed by a space: ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -559,9 +593,9 @@ Arguments: - `index` -- is its position. - `array` -- is the array. -As function is applied, the result of the previous function call is passed to the next one as the first argument. +As the function is applied, the result of the previous function call is passed to the next one as the first argument. -So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of `reduce`. +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. Sounds complicated? @@ -630,8 +664,7 @@ arr.reduce((sum, current) => sum + current); So it's advised to always specify the initial value. -The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left. - +The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. ## Array.isArray @@ -641,7 +674,7 @@ So `typeof` does not help to distinguish a plain object from an array: ```js run alert(typeof {}); // object -alert(typeof []); // same +alert(typeof []); // object (same) ``` ...But arrays are used so often that there's a special method for that: [Array.isArray(value)](mdn:js/Array/isArray). It returns `true` if the `value` is an array, and `false` otherwise. @@ -656,7 +689,7 @@ alert(Array.isArray([])); // true Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`. -That parameter is not explained in the sections above, because it's rarely used. But for completeness we have to cover it. +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. Here's the full syntax of these methods: @@ -700,7 +733,7 @@ alert(soldiers[1].age); // 23 If in the example above we used `users.filter(army.canJoin)`, then `army.canJoin` would be called as a standalone function, with `this=undefined`, thus leading to an instant error. -A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The former is used more often, as it's a bit easier to understand for most people. +A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The latter is used more often, as it's a bit easier to understand for most people. ## Summary @@ -711,12 +744,12 @@ A cheat sheet of array methods: - `pop()` -- extracts an item from the end, - `shift()` -- extracts an item from the beginning, - `unshift(...items)` -- adds items to the beginning. - - `splice(pos, deleteCount, ...items)` -- at index `pos` delete `deleteCount` elements and insert `items`. - - `slice(start, end)` -- creates a new array, copies elements from position `start` till `end` (not inclusive) into it. + - `splice(pos, deleteCount, ...items)` -- at index `pos` deletes `deleteCount` elements and inserts `items`. + - `slice(start, end)` -- creates a new array, copies elements from index `start` till `end` (not inclusive) into it. - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. - To search among elements: - - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found. + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - `findIndex` is like `find`, but returns the index instead of a value. @@ -729,26 +762,40 @@ A cheat sheet of array methods: - `sort(func)` -- sorts the array in-place, then returns it. - `reverse()` -- reverses the array in-place, then returns it. - `split/join` -- convert a string to array and back. - - `reduce(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. + - `reduce/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. - Additionally: - - `Array.isArray(arr)` checks `arr` for being an array. + - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`. Please note that methods `sort`, `reverse` and `splice` modify the array itself. These methods are the most used ones, they cover 99% of use cases. But there are few others: -- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) checks the array. +- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) check the array. The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`. + These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well. + + We can use `every` to compare arrays: + + ```js run + function arraysEqual(arr1, arr2) { + return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); + } + + alert( arraysEqual([1, 2], [1, 2])); // true + ``` + - [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`. - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copies its elements from position `start` till position `end` into *itself*, at position `target` (overwrites existing). +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) create a new flat array from a multidimensional array. + For the full list, see the [manual](mdn:js/Array). -From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier. +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods. diff --git a/1-js/05-data-types/05-array-methods/reduce.svg b/1-js/05-data-types/05-array-methods/reduce.svg index fcac711cb..180941dc2 100644 --- a/1-js/05-data-types/05-array-methods/reduce.svg +++ b/1-js/05-data-types/05-array-methods/reduce.svg @@ -1 +1 @@ -1sum 0 current 12sum 0+1 current 23sum 0+1+2 current 34sum 0+1+2+3 current 45sum 0+1+2+3+4 current 50+1+2+3+4+5 = 15 \ No newline at end of file +1sum 0 current 12sum 0+1 current 23sum 0+1+2 current 34sum 0+1+2+3 current 45sum 0+1+2+3+4 current 50+1+2+3+4+5 = 15 \ No newline at end of file diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 8a38516e1..524a5873c 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,12 +1,10 @@ +# Aufzählbare -# Iterables +_Aufzählbare_ Objekte sind eine Verallgemeinerung von Arrays. Das Konzept ermöglicht es uns, jedes Objekt in einer `for..of` -Schleife zu verwenden. -*Iterable* objects is a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. - -Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable. - -If an object isn't technically an array, but represents a collection (list, set) of something, then `for..of` is a great syntax to loop over it, so let's see how to make it work. +Gewiss, Arrays sind aufzählbar. Aber es gibt auch viele weitere integrierte Objekte, die aufzählbar sind. Zum Beispiel sind auch Strings aufzählbar. +Wenn ein Objekt technisch gesehen kein Array ist, sondern eine Sammlung (Liste, Menge) repräsentiert, dann ist "for..of" eine hervorragende Syntax, um sie zudurchlaufen. Sehen wir uns an, wie das funktioniert. ## Symbol.iterator @@ -19,36 +17,35 @@ Like a `range` object that represents an interval of numbers: ```js let range = { from: 1, - to: 5 + to: 5, }; // We want the for..of to work: // for(let num of range) ... num=1,2,3,4,5 ``` -To make the `range` iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). +To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). -1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. -2. Onward, `for..of` works *only with that returned object*. +1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an _iterator_ -- an object with the method `next`. +2. Onward, `for..of` works _only with that returned object_. 3. When `for..of` wants the next value, it calls `next()` on that object. -4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value. +4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value. Here's the full implementation for `range` with remarks: ```js run let range = { from: 1, - to: 5 + to: 5, }; // 1. call to for..of initially calls this -range[Symbol.iterator] = function() { - +range[Symbol.iterator] = function () { // ...it returns the iterator object: - // 2. Onward, for..of works only with this iterator, asking it for next values + // 2. Onward, for..of works only with the iterator object below, asking it for next values return { current: this.from, - last: this.to, + last: this.to, // 3. next() is called on each iteration by the for..of loop next() { @@ -58,7 +55,7 @@ range[Symbol.iterator] = function() { } else { return { done: true }; } - } + }, }; }; @@ -95,7 +92,7 @@ let range = { } else { return { done: true }; } - } + }, }; for (let num of range) { @@ -103,7 +100,7 @@ for (let num of range) { } ``` -Now `range[Symbol.iterator]()` returns the `range` object itself: it has the necessary `next()` method and remembers the current iteration progress in `this.current`. Shorter? Yes. And sometimes that's fine too. +Now `range[Symbol.iterator]()` returns the `range` object itself: it has the necessary `next()` method and remembers the current iteration progress in `this.current`. Shorter? Yes. And sometimes that's fine too. The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself. But two parallel for-ofs is a rare thing, even in async scenarios. @@ -115,7 +112,6 @@ There are no limitations on `next`, it can return more and more values, that's n Of course, the `for..of` loop over such an iterable would be endless. But we can always stop it using `break`. ``` - ## String is iterable Arrays and strings are most widely used built-in iterables. @@ -123,9 +119,9 @@ Arrays and strings are most widely used built-in iterables. For a string, `for..of` loops over its characters: ```js run -for (let char of "test") { +for (let char of 'test') { // triggers 4 times: once for each character - alert( char ); // t, then e, then s, then t + alert(char); // t, then e, then s, then t } ``` @@ -134,13 +130,13 @@ And it works correctly with surrogate pairs! ```js run let str = '𝒳😂'; for (let char of str) { - alert( char ); // 𝒳, and then 😂 + alert(char); // 𝒳, and then 😂 } ``` ## Calling an iterator explicitly -For deeper understanding let's see how to use an iterator explicitly. +For deeper understanding, let's see how to use an iterator explicitly. We'll iterate over a string in exactly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": @@ -165,16 +161,16 @@ That is rarely needed, but gives us more control over the process than `for..of` ## Iterables and array-likes [#array-like] -There are two official terms that look similar, but are very different. Please make sure you understand them well to avoid the confusion. +Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion. -- *Iterables* are objects that implement the `Symbol.iterator` method, as described above. -- *Array-likes* are objects that have indexes and `length`, so they look like arrays. +- _Iterables_ are objects that implement the `Symbol.iterator` method, as described above. +- _Array-likes_ are objects that have indexes and `length`, so they look like arrays. -When we use JavaScript for practical tasks in browser or other environments, we may meet objects that are iterables or array-likes, or both. +When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both. For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). -But an iterable may be not array-like. And vice versa an array-like may be not iterable. +But an iterable may not be array-like. And vice versa an array-like may not be iterable. For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`. @@ -193,7 +189,7 @@ for (let item of arrayLike) {} */!* ``` -Both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such an object and want to work with it as with an array. E.g. we would like to work with `range` using array methods. How to achieve that? +Both iterables and array-likes are usually _not arrays_, they don't have `push`, `pop` etc. That's rather inconvenient if we have such an object and want to work with it as with an array. E.g. we would like to work with `range` using array methods. How to achieve that? ## Array.from @@ -218,13 +214,14 @@ alert(arr.pop()); // World (method works) The same happens for an iterable: -```js +```js run // assuming that range is taken from the example above let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) ``` The full syntax for `Array.from` also allows us to provide an optional "mapping" function: + ```js Array.from(obj[, mapFn, thisArg]) ``` @@ -233,11 +230,11 @@ The optional second argument `mapFn` can be a function that will be applied to e For instance: -```js +```js run // assuming that range is taken from the example above // square each number -let arr = Array.from(range, num => num * num); +let arr = Array.from(range, (num) => num * num); alert(arr); // 1,4,9,16,25 ``` @@ -270,7 +267,7 @@ for (let char of str) { alert(chars); ``` -...But it is shorter. +...But it is shorter. We can even build surrogate-aware `slice` on it: @@ -281,27 +278,25 @@ function slice(str, start, end) { let str = '𝒳😂𩷶'; -alert( slice(str, 1, 3) ); // 😂𩷶 +alert(slice(str, 1, 3)); // 😂𩷶 // the native method does not support surrogate pairs -alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs) +alert(str.slice(1, 3)); // garbage (two pieces from different surrogate pairs) ``` - ## Summary -Objects that can be used in `for..of` are called *iterable*. +Objects that can be used in `for..of` are called _iterable_. - Technically, iterables must implement the method named `Symbol.iterator`. - - The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process. - - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. + - The result of `obj[Symbol.iterator]` is called an _iterator_. It handles the further iteration process. + - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. - The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. - Built-in iterables like strings or arrays, also implement `Symbol.iterator`. - String iterator knows about surrogate pairs. - -Objects that have indexed properties and `length` are called *array-like*. Such objects may also have other properties and methods, but lack the built-in methods of arrays. +Objects that have indexed properties and `length` are called _array-like_. Such objects may also have other properties and methods, but lack the built-in methods of arrays. If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract. -`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. diff --git a/1-js/05-data-types/07-map-set/01-array-unique-map/task.md b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md index d68030032..a33eb9873 100644 --- a/1-js/05-data-types/07-map-set/01-array-unique-map/task.md +++ b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# Filter unique array members +# Filtere eindeutige Array-Elemente -Let `arr` be an array. +Lass `arr` ein Array sein. -Create a function `unique(arr)` that should return an array with unique items of `arr`. +Erstelle eine Funktion `unique(arr)`, die ein Array mit den eindeutigen Elementen von `arr` zurückgeben sollte. -For instance: +Zum Beispiel: ```js function unique(arr) { - /* your code */ + /* Dein Code */ } let values = ["Hare", "Krishna", "Hare", "Krishna", @@ -22,6 +22,6 @@ let values = ["Hare", "Krishna", "Hare", "Krishna", alert( unique(values) ); // Hare, Krishna, :-O ``` -P.S. Here strings are used, but can be values of any type. +P.S. Hier werden Zeichenketten verwendet, es können aber Werte jeglichen Typs sein. -P.P.S. Use `Set` to store unique values. +P.P.S. Verwende `Set`, um eindeutige Werte zu speichern. diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md index 160675185..173e6e928 100644 --- a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md @@ -1,6 +1,6 @@ -To find all anagrams, let's split every word to letters and sort them. When letter-sorted, all anagrams are same. +Um alle Anagramme zu finden, lassen wir uns jede Zeichenkette in Buchstaben aufteilen und sortieren sie. Wenn sie buchstabensortiert sind, sind alle Anagramme gleich. -For instance: +Zum Beispiel: ``` nap, pan -> anp @@ -9,14 +9,14 @@ cheaters, hectares, teachers -> aceehrst ... ``` -We'll use the letter-sorted variants as map keys to store only one value per each key: +Wir verwenden die buchstabensortierten Varianten als Schlüssel in einer Map, um nur einen Wert pro Schlüssel zu speichern: ```js run function aclean(arr) { let map = new Map(); for (let word of arr) { - // split the word by letters, sort them and join back + // das Wort in Buchstaben aufteilen, sortieren und wieder zusammenfügen *!* let sorted = word.toLowerCase().split('').sort().join(''); // (*) */!* @@ -31,9 +31,9 @@ let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); ``` -Letter-sorting is done by the chain of calls in the line `(*)`. +Das Buchstabensortieren wird durch die Aufrufkette in der Zeile `(*)` durchgeführt. -For convenience let's split it into multiple lines: +Zur besseren Übersicht teilen wir es in mehrere Zeilen auf: ```js let sorted = word // PAN @@ -43,21 +43,21 @@ let sorted = word // PAN .join(''); // anp ``` -Two different words `'PAN'` and `'nap'` receive the same letter-sorted form `'anp'`. +Zwei unterschiedliche Wörter `'PAN'` und `'nap'` erhalten dieselbe buchstabensortierte Form `'anp'`. -The next line put the word into the map: +Die nächste Zeile fügt das Wort in die Map ein: ```js map.set(sorted, word); ``` -If we ever meet a word the same letter-sorted form again, then it would overwrite the previous value with the same key in the map. So we'll always have at maximum one word per letter-form. +Wenn wir erneut auf ein Wort mit derselben buchstabensortierten Form stoßen, wird der vorherige Wert mit demselben Schlüssel in der Map überschrieben. Daher haben wir immer maximal ein Wort pro Buchstabenform. -At the end `Array.from(map.values())` takes an iterable over map values (we don't need keys in the result) and returns an array of them. +Am Ende erzeugt `Array.from(map.values())` ein iterierbares über die Werte der Map (wir benötigen die Schlüssel im Ergebnis nicht) und gibt ein Array davon zurück. -Here we could also use a plain object instead of the `Map`, because keys are strings. +Hier könnten wir anstelle der `Map` auch ein einfaches Objekt verwenden, da die Schlüssel Zeichenketten sind. -That's how the solution can look: +So könnte die Lösung aussehen: ```js run demo function aclean(arr) { diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md index 731fd2c25..0964cc6f0 100644 --- a/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md @@ -1,12 +1,13 @@ +--- importance: 4 --- -# Filter anagrams +# Anagramme filtern -[Anagrams](https://en.wikipedia.org/wiki/Anagram) are words that have the same number of same letters, but in different order. +[Anagramme](https://de.wikipedia.org/wiki/Anagramm) sind Wörter, die die gleiche Anzahl von Buchstaben haben, aber in einer anderen Reihenfolge. -For instance: +Zum Beispiel: ``` nap - pan @@ -14,15 +15,14 @@ ear - are - era cheaters - hectares - teachers ``` -Write a function `aclean(arr)` that returns an array cleaned from anagrams. +Schreibe eine Funktion `aclean(arr)`, die ein Array zurückgibt, das von Anagrammen bereinigt ist. -For instance: +Zum Beispiel: ```js let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; -alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era" +alert( aclean(arr) ); // "nap,teachers,ear" oder "PAN,cheaters,era" ``` -From every anagram group should remain only one word, no matter which one. - +Von jeder Anagrammgruppe sollte nur ein Wort übrig bleiben, egal welches. diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md index 7310d1d36..d74ecc686 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md @@ -1,7 +1,6 @@ +Das liegt daran, dass `map.keys()` ein iterierbares Objekt zurückgibt, aber kein Array. -That's because `map.keys()` returns an iterable, but not an array. - -We can convert it into an array using `Array.from`: +Wir können es mit `Array.from` in ein Array umwandeln: ```js run diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md index 25c74bfc2..909158065 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Iterable keys +# Iterierbare Schlüssel -We'd like to get an array of `map.keys()` in a variable and then do apply array-specific methods to it, e.g. `.push`. +Wir möchten ein Array von `map.keys()` in einer Variablen haben und dann darauf array-spezifische Methoden anwenden, z.B. `.push`. -But that doesn't work: +Aber das funktioniert nicht: ```js run let map = new Map(); @@ -16,9 +16,10 @@ map.set("name", "John"); let keys = map.keys(); *!* -// Error: keys.push is not a function +// Fehler: keys.push ist keine Funktion keys.push("more"); */!* ``` -Why? How can we fix the code to make `keys.push` work? +Warum? Wie können wir den Code korrigieren, damit `keys.push` funktioniert? + diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index e08c84084..3aa91ab04 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,95 +1,96 @@ +# Map und Set -# Map and Set +Bisher haben wir folgende komplexe Datenstrukturen kennengelernt: -Now we've learned about the following complex data structures: +- Objekte werden für die Speicherung von Sammlungen mit Schlüsseln verwendet. +- Arrays werden für die Speicherung von geordneten Sammlungen verwendet. -- Objects for storing keyed collections. -- Arrays for storing ordered collections. - -But that's not enough for real life. That's why `Map` and `Set` also exist. +Aber das reicht für das echte Leben nicht aus. Deshalb gibt es auch `Map` und `Set`. ## Map -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) ist eine Sammlung von Daten-Elementen mit Schlüsseln, ähnlich wie ein `Object`. Der Hauptunterschied ist jedoch, dass `Map` Schlüssel jeglichen Typs zulässt. -Methods and properties are: +Methoden und Eigenschaften sind: -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- erstellt die Map. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- speichert den Wert unter dem Schlüssel. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- gibt den Wert zum Schlüssel zurück, `undefined` wenn der `key` nicht in der Map existiert. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- gibt `true` zurück, wenn der `key` existiert, andernfalls `false`. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- entfernt das Element (das Schlüssel/Wert-Paar) anhand des Schlüssels. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- entfernt alles aus der Map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- gibt die aktuelle Anzahl der Elemente zurück. -For instance: +Zum Beispiel: ```js run let map = new Map(); -map.set('1', 'str1'); // a string key -map.set(1, 'num1'); // a numeric key -map.set(true, 'bool1'); // a boolean key +map.set('1', 'str1'); // ein String als Schlüssel +map.set(1, 'num1'); // eine Zahl als Schlüssel +map.set(true, 'bool1'); // ein Boolean als Schlüssel -// remember the regular Object? it would convert keys to string -// Map keeps the type, so these two are different: +// erinnerst Du Dich an das reguläre Objekt? Es würde Schlüssel zu Strings konvertieren +// Map behält den Typ, daher sind diese beiden unterschiedlich: alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3 ``` -As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. +Wie wir sehen können, werden im Gegensatz zu Objekten die Schlüssel nicht in Strings umgewandelt. Jeder Schlüsseltyp ist möglich. -```smart header="`map[key]` isn't the right way to use a `Map`" -Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (no object keys and so on). +```smart header="`map[key]` ist nicht die korrekte Art eine `Map` zu verwenden" +Obwohl `map[key]` auch funktioniert, z.B. wir können `map[key] = 2` setzen, wird `map` dabei wie ein normales JavaScript-Objekt behandelt, daher gelten alle entsprechenden Einschränkungen (nur Strings/Symbol-Schlüssel und so weiter). -So we should use `map` methods: `set`, `get` and so on. +Deshalb sollten wir die `map`-Methoden verwenden: `set`, `get` usw. ``` -**Map can also use objects as keys.** +**Map kann auch Objekte als Schlüssel verwenden.** -For instance: +Zum Beispiel: ```js run let john = { name: "John" }; -// for every user, let's store their visits count +// für jeden Benutzer wollen wir die Anzahl der Besuche speichern let visitsCountMap = new Map(); -// john is the key for the map +// john ist der Schlüssel für die Map visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but not for object keys. +Objekte als Schlüssel zu verwenden ist eines der bemerkenswertesten und wichtigsten Merkmale von `Map`. Das Gleiche gilt nicht für `Object`. Strings als Schlüssel in `Object` ist in Ordnung, aber wir können kein anderes `Object` als Schlüssel in einem `Object` verwenden. -Let's try: +Lass es uns versuchen: ```js run let john = { name: "John" }; +let ben = { name: "Ben" }; -let visitsCountObj = {}; // try to use an object +let visitsCountObj = {}; // versuche ein Objekt zu verwenden -visitsCountObj[john] = 123; // try to use john object as the key +visitsCountObj[ben] = 234; // versuche Objekt ben als Schlüssel zu verwenden +visitsCountObj[john] = 123; // versuche Objekt john als Schlüssel zu verwenden, Objekt ben wird ersetzt *!* -// That's what got written! -alert( visitsCountObj["[object Object]"] ); // 123 +// Das wurde geschrieben! +alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` -As `visitsCountObj` is an object, it converts all keys, such as `john` to strings, so we've got the string key `"[object Object]"`. Definitely not what we want. +Da `visitsCountObj` ein Objekt ist, konvertiert es alle `Object`-Schlüssel, wie `john` und `ben` oben, zum gleichen String `"[object Object]"`. Definitiv nicht was wir wollen. -```smart header="How `Map` compares keys" -To test keys for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. +```smart header="Wie `Map` Schlüssel vergleicht" +Um Schlüssel auf Gleichheit zu testen, verwendet `Map` den Algorithmus [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). Das ist ungefähr das Gleiche wie die strikte Gleichheit `===`, aber der Unterschied liegt darin, dass `NaN` als gleich zu `NaN` angesehen wird. So kann `NaN` auch als Schlüssel verwendet werden. -This algorithm can't be changed or customized. +Dieser Algorithmus kann nicht verändert oder angepasst werden. ``` -````smart header="Chaining" -Every `map.set` call returns the map itself, so we can "chain" the calls: +````smart header="Verkettung" +Jeder Aufruf von `map.set` gibt die Map selbst zurück, sodass wir die Aufrufe "verketten" können: ```js map.set('1', 'str1') @@ -98,16 +99,15 @@ map.set('1', 'str1') ``` ```` +## Iteration über Map -## Iteration over Map - -For looping over a `map`, there are 3 methods: +Um über eine `map` zu iterieren, gibt es 3 Methoden: -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- gibt ein iterierbares Objekt für Schlüssel zurück, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- gibt ein iterierbares Objekt für Werte zurück, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- gibt ein iterierbares Objekt für Einträge `[key, value]` zurück, wird standardmäßig in `for..of` genutzt. -For instance: +Zum Beispiel: ```js run let recipeMap = new Map([ @@ -116,41 +116,41 @@ let recipeMap = new Map([ ['onion', 50] ]); -// iterate over keys (vegetables) +// iteriere über Schlüssel (Gemüse) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } -// iterate over values (amounts) +// iteriere über Werte (Mengen) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } -// iterate over [key, value] entries -for (let entry of recipeMap) { // the same as of recipeMap.entries() - alert(entry); // cucumber,500 (and so on) +// iteriere über [key, value] Einträge +for (let entry of recipeMap) { // das gleiche wie bei recipeMap.entries() + alert(entry); // cucumber,500 (usw.) } ``` -```smart header="The insertion order is used" -The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`. +```smart header="Die Einfügereihenfolge wird verwendet" +Die Iteration erfolgt in der gleichen Reihenfolge, in der die Werte eingefügt wurden. `Map` bewahrt diese Reihenfolge, anders als ein normales `Object`. ``` -Besides that, `Map` has a built-in `forEach` method, similar to `Array`: +Darüber hinaus verfügt `Map` über eine eingebaute `forEach`-Methode, ähnlich wie `Array`: ```js -// runs the function for each (key, value) pair +// führt die Funktion für jedes (key, value) Paar aus recipeMap.forEach( (value, key, map) => { - alert(`${key}: ${value}`); // cucumber: 500 etc + alert(`${key}: ${value}`); // cucumber: 500 usw }); ``` -## Object.entries: Map from Object +## Object.entries: Map aus Object -When a `Map` is created, we can pass an array (or another iterable) with key/value pairs for initialization, like this: +Wenn eine `Map` erstellt wird, können wir ein Array (oder ein anderes iterierbares Objekt) mit Schlüssel/Wert-Paaren zur Initialisierung übergeben, so wie hier: ```js run -// array of [key, value] pairs +// Array von [key, value] Paaren let map = new Map([ ['1', 'str1'], [1, 'num1'], @@ -160,9 +160,9 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +Wenn wir ein einfaches Objekt haben und wir daraus eine `Map` erstellen wollen, dann können wir die eingebaute Methode [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) verwenden, die ein Array von Schlüssel/Wert-Paaren für ein Objekt genau in diesem Format zurückgibt. -So we can create a map from an object like this: +So können wir also eine Map aus einem Objekt erstellen: ```js run let obj = { @@ -177,14 +177,14 @@ let map = new Map(Object.entries(obj)); alert( map.get('name') ); // John ``` -Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. +Hier gibt `Object.entries` das Array von Schlüssel/Wert-Paaren zurück: `[ ["name","John"], ["age", 30] ]`. Genau das braucht `Map`. -## Object.fromEntries: Object from Map +## Object.fromEntries: Object aus Map -We've just seen how to create `Map` from a plain object with `Object.entries(obj)`. +Wir haben gerade gesehen, wie man `Map` aus einem einfachen Objekt mit `Object.entries(obj)` erstellt. -There's `Object.fromEntries` method that does the reverse: given an array of `[key, value]` pairs, it creates an object from them: +Es gibt die Methode `Object.fromEntries`, die das Gegenteil macht: Gegeben ein Array von `[key, value]` Paaren erstellt sie daraus ein Objekt: ```js run let prices = Object.fromEntries([ @@ -193,16 +193,16 @@ let prices = Object.fromEntries([ ['meat', 4] ]); -// now prices = { banana: 1, orange: 2, meat: 4 } +// jetzt ist prices = { banana: 1, orange: 2, meat: 4 } alert(prices.orange); // 2 ``` -We can use `Object.fromEntries` to get a plain object from `Map`. +Wir können `Object.fromEntries` verwenden, um ein einfaches Objekt aus `Map` zu erstellen. -E.g. we store the data in a `Map`, but we need to pass it to a 3rd-party code that expects a plain object. +Z.B. wir speichern die Daten in einer `Map`, aber wir müssen sie an einen Drittanbieter-Code übergeben, der ein normales Objekt erwartet. -Here we go: +So wird dies erreicht: ```js run let map = new Map(); @@ -211,42 +211,42 @@ map.set('orange', 2); map.set('meat', 4); *!* -let obj = Object.fromEntries(map.entries()); // make a plain object (*) +let obj = Object.fromEntries(map.entries()); // erstelle ein einfaches Objekt (*) */!* -// done! +// fertig! // obj = { banana: 1, orange: 2, meat: 4 } alert(obj.orange); // 2 ``` -A call to `map.entries()` returns an iterable of key/value pairs, exactly in the right format for `Object.fromEntries`. +Ein Aufruf von `map.entries()` gibt ein iterierbares Objekt von Schlüssel/Wert-Paaren zurück, genau im richtigen Format für `Object.fromEntries`. -We could also make line `(*)` shorter: +Wir könnten auch die Zeile `(*)` kürzer machen: ```js -let obj = Object.fromEntries(map); // omit .entries() +let obj = Object.fromEntries(map); // weglassen von .entries() ``` -That's the same, because `Object.fromEntries` expects an iterable object as the argument. Not necessarily an array. And the standard iteration for `map` returns same key/value pairs as `map.entries()`. So we get a plain object with same key/values as the `map`. +Das kommt auf das Gleiche heraus, weil `Object.fromEntries` ein iterierbares Objekt als Argument erwartet (nicht unbedingt ein Array). Und die Standarditeration für `map` gibt die gleichen Schlüssel/Wert-Paare zurück wie `map.entries()`. So erhalten wir ein normales Objekt mit den gleichen Schlüsseln/Werten wie die `map`. ## Set -A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. +Ein [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) ist eine spezielle Art Sammlung - "Set von Werten" (ohne Schlüssel), in dem jeder Wert nur einmal vorkommen darf. -Its main methods are: +Seine Hauptmethoden sind: -- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- erstellt das Set, und wenn ein `iterable`-Objekt übergeben wird (in der Regel ein Array), kopiert es die Werte daraus in das Set. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- fügt einen Wert hinzu, gibt das Set selbst zurück. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- entfernt den Wert, gibt `true` zurück wenn der `value` zum Zeitpunkt des Aufrufs existierte, sonst `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- gibt `true` zurück, wenn der Wert im Set existiert, sonst `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- entfernt alles aus dem Set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- ist die Anzahl der Elemente. -The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. +Das Hauptmerkmal ist, dass wiederholte Aufrufe von `set.add(value)` mit demselben Wert nichts bewirken. Deshalb erscheint jeder Wert in einem `Set` nur einmal. -For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once. +Zum Beispiel haben wir Besucher, die kommen, und wir möchten uns an jeden erinnern. Aber wiederholte Besuche sollten nicht zu Duplikaten führen. Ein Besucher muss nur einmal "gezählt" werden. -`Set` is just the right thing for that: +`Set` ist genau das Richtige dafür: ```js run let set = new Set(); @@ -255,76 +255,76 @@ let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -// visits, some users come multiple times +// Besuche, einige Benutzer kommen mehrere Male set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); -// set keeps only unique values +// das Set behält nur einzigartige Werte alert( set.size ); // 3 for (let user of set) { - alert(user.name); // John (then Pete and Mary) + alert(user.name); // John (dann Pete und Mary) } ``` -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +Die Alternative zu `Set` könnte ein Array von Benutzern sein, und der Code zum Überprüfen auf Duplikate bei jeder Einfügung unter Verwendung von [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). Aber die Leistung wäre viel schlechter, weil diese Methode das gesamte Array durchläuft, um jeden Element zu überprüfen. `Set` ist intern viel besser für Einzigartigkeitsprüfungen optimiert. -## Iteration over Set +## Iteration über Set -We can loop over a set either with `for..of` or using `forEach`: +Wir können entweder mit `for..of` oder `forEach` über ein Set iterieren: ```js run let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); -// the same with forEach: +// das Gleiche mit forEach: set.forEach((value, valueAgain, set) => { alert(value); }); ``` -Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. +Beachte eine merkwürdige Sache. Die Callback-Funktion, die `forEach` übergeben wurde, hat drei Argumente: einen `value`, dann *den gleichen Wert* `valueAgain` und dann das Zielobjekt. Tatsächlich erscheint der gleiche Wert doppelt in den Argumenten. -That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +Das ist der Kompatibilität mit `Map` geschuldet, wo die Callback-Funktion für `forEach` drei Argumente hat. Sieht sicher ein bisschen seltsam aus, aber dies kann helfen, `Map` mit `Set` in bestimmten Fällen problemlos zu ersetzen, und umgekehrt. -The same methods `Map` has for iterators are also supported: +Die gleichen Methoden, die `Map` für Iteratoren hat, werden auch unterstützt: -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys()`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- gibt ein iterierbares Objekt für Werte zurück, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- das Gleiche wie `set.keys()`, für die Kompatibilität mit `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- gibt ein iterierbares Objekt für Einträge `[value, value]` zurück, existiert für die Kompatibilität mit `Map`. -## Summary +## Zusammenfassung -`Map` -- is a collection of keyed values. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- ist eine Sammlung von wertigen Daten mit Schlüsseln. -Methods and properties: +Methoden und Eigenschaften: -- `new Map([iterable])` -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- erstellt die Map, optional mit `iterable` (z.B. Array) von `[key,value]`-Paaren zur Initialisierung. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- speichert den Wert unter dem Schlüssel, gibt die Map selbst zurück. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- gibt den Wert zum Schlüssel zurück, `undefined` wenn der `key` nicht in der Map existiert. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- gibt `true` zurück, wenn der `key` existiert, sonst `false`. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- entfernt das Element anhand des Schlüssels, gibt `true` zurück, wenn der `key` zum Zeitpunkt des Aufrufs vorhanden war, sonst `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- entfernt alles aus der Map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- gibt die aktuelle Anzahl an Elementen zurück. -The differences from a regular `Object`: +Die Unterschiede zu einem regulären `Object`: -- Any keys, objects can be keys. -- Additional convenient methods, the `size` property. +- Beliebige Schlüssel, auch Objekte können Schlüssel sein. +- Zusätzliche praktische Methoden, die `size`-Eigenschaft. -`Set` -- is a collection of unique values. +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- ist eine Sammlung von einzigartigen Werten. -Methods and properties: +Methoden und Eigenschaften: -- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization. -- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- erstellt das Set, optional mit `iterable` (z.B. Array) von Werten zur Initialisierung. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- fügt einen Wert hinzu (macht nichts, wenn `value` bereits vorhanden ist), gibt das Set selbst zurück. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- entfernt den Wert, gibt `true` zurück, wenn der Wert im Moment des Aufrufs vorhanden war, sonst `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- gibt `true` zurück, wenn der Wert im Set vorhanden ist, sonst `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- entfernt alles aus dem Set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- ist die Anzahl der Elemente. -Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. +Die Iteration über `Map` und `Set` erfolgt immer in der Reihenfolge des Einfügens, daher können wir nicht sagen, dass diese Sammlungen ungeordnet sind, aber wir können die Elemente nicht umsortieren oder direkt ein Element anhand seiner Nummer abrufen. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index 6a4c20baf..c5b71ecfb 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -1,43 +1,43 @@ -Let's store read messages in `WeakSet`: +Lass uns gelesene Nachrichten in `WeakSet` speichern: ```js run let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hallo", from: "John"}, + {text: "Wie läuft's?", from: "John"}, + {text: "Bis bald", from: "Alice"} ]; let readMessages = new WeakSet(); -// two messages have been read +// Zwei Nachrichten wurden gelesen readMessages.add(messages[0]); readMessages.add(messages[1]); -// readMessages has 2 elements +// readMessages hat 2 Elemente -// ...let's read the first message again! +// ...lass uns die erste Nachricht nochmal lesen! readMessages.add(messages[0]); -// readMessages still has 2 unique elements +// readMessages hat immer noch 2 einzigartige Elemente -// answer: was the message[0] read? -alert("Read message 0: " + readMessages.has(messages[0])); // true +// Antwort: Wurde die Nachricht[0] gelesen? +alert("Gelesene Nachricht 0: " + readMessages.has(messages[0])); // true messages.shift(); -// now readMessages has 1 element (technically memory may be cleaned later) +// jetzt hat readMessages 1 Element (technisch gesehen, könnte der Speicher später bereinigt werden) ``` -The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. +Das `WeakSet` ermöglicht es, eine Menge von Nachrichten zu speichern und einfach zu überprüfen, ob eine Nachricht darin existiert. -It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set. +Es bereinigt sich automatisch. Der Kompromiss ist, dass wir nicht darüber iterieren können, d.h. wir können nicht "alle gelesenen Nachrichten" direkt erhalten. Aber wir können dies erreichen, indem wir über alle Nachrichten iterieren und diejenigen aussortieren, die nicht im Set sind. -Another, different solution could be to add a property like `message.isRead=true` to a message after it's read. As messages objects are managed by another code, that's generally discouraged, but we can use a symbolic property to avoid conflicts. +Eine andere, unterschiedliche Lösung könnte sein, einer Nachricht eine Eigenschaft wie `message.isRead=true` hinzuzufügen, nachdem sie gelesen wurde. Da Nachrichtenobjekte von anderem Code verwaltet werden, ist das allgemein nicht empfohlen, aber wir können eine symbolische Eigenschaft verwenden, um Konflikte zu vermeiden. -Like this: +So wie hier: ```js -// the symbolic property is only known to our code +// die symbolische Eigenschaft ist nur unserem Code bekannt let isRead = Symbol("isRead"); messages[0][isRead] = true; ``` -Now third-party code probably won't see our extra property. +Jetzt wird Code von dritten unsere zusätzliche Eigenschaft wahrscheinlich nicht sehen. -Although symbols allow to lower the probability of problems, using `WeakSet` is better from the architectural point of view. +Obwohl Symbole die Wahrscheinlichkeit von Problemen verringern, ist die Verwendung von `WeakSet` aus architektonischer Sicht besser. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md index fd31a891b..8bf5f3715 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md @@ -2,22 +2,22 @@ importance: 5 --- -# Store "unread" flags +# Speicherung von "ungelesen"-Markierungen -There's an array of messages: +Gegeben sei ein Array von Nachrichten: ```js let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hallo", from: "John"}, + {text: "Wie läuft's?", from: "John"}, + {text: "Bis bald", from: "Alice"} ]; ``` -Your code can access it, but the messages are managed by someone else's code. New messages are added, old ones are removed regularly by that code, and you don't know the exact moments when it happens. +Dein Code kann darauf zugreifen, aber die Nachrichten werden von anderem Code verwaltet. Neue Nachrichten werden hinzugefügt, alte regelmäßig entfernt, und du weißt nicht genau, in welchen Momenten das passiert. -Now, which data structure could you use to store information about whether the message "has been read"? The structure must be well-suited to give the answer "was it read?" for the given message object. +Welche Datenstruktur könntest du nun verwenden, um Informationen darüber zu speichern, ob die Nachricht "gelesen wurde"? Die Struktur muss gut geeignet sein, um die Frage "wurde es gelesen?" für das gegebene Nachrichtenobjekt zu beantworten. -P.S. When a message is removed from `messages`, it should disappear from your structure as well. +P.S. Wenn eine Nachricht aus `messages` entfernt wird, sollte sie auch aus deiner Struktur verschwinden. -P.P.S. We shouldn't modify message objects, add our properties to them. As they are managed by someone else's code, that may lead to bad consequences. +P.P.S. Wir sollten die Nachrichtenobjekte nicht modifizieren oder unsere Eigenschaften hinzufügen. Da sie von anderem Code verwaltet werden, könnte das zu schlechten Konsequenzen führen. diff --git a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md index 2af0547c1..f03b2aaf0 100644 --- a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md @@ -1,15 +1,14 @@ - -To store a date, we can use `WeakMap`: +Um ein Datum zu speichern, können wir `WeakMap` verwenden: ```js let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hallo", from: "John"}, + {text: "Wie läuft's?", from: "John"}, + {text: "Bis bald", from: "Alice"} ]; let readMap = new WeakMap(); readMap.set(messages[0], new Date(2017, 1, 1)); -// Date object we'll study later +// Date-Objekt, das wir später betrachten werden ``` diff --git a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md index 8e341c184..c23556602 100644 --- a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md @@ -2,20 +2,20 @@ importance: 5 --- -# Store read dates +# Lesezeitpunkte speichern -There's an array of messages as in the [previous task](info:task/recipients-read). The situation is similar. +Es gibt ein Array von Nachrichten, wie in der [vorherigen Aufgabe](info:task/recipients-read). Die Situation ist ähnlich. ```js let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} + {text: "Hallo", from: "John"}, + {text: "Wie läuft's?", from: "John"}, + {text: "Bis bald", from: "Alice"} ]; ``` -The question now is: which data structure you'd suggest to store the information: "when the message was read?". +Die Frage lautet nun: Welche Datenstruktur würdest Du vorschlagen, um die Information zu speichern: "Wann wurde die Nachricht gelesen?". -In the previous task we only needed to store the "yes/no" fact. Now we need to store the date, and it should only remain in memory until the message is garbage collected. +In der vorherigen Aufgabe mussten wir nur die Tatsache "ja/nein" speichern. Jetzt müssen wir das Datum speichern, und es sollte nur so lange im Speicher bleiben, bis die Nachricht vom Garbage Collector gelöscht wird. -P.S. Dates can be stored as objects of built-in `Date` class, that we'll cover later. +P.S. Daten können als Objekte der eingebauten `Date`-Klasse gespeichert werden, die wir später behandeln werden. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index bcc5e5e6f..734d05195 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,43 +1,45 @@ -# WeakMap and WeakSet +# WeakMap und WeakSet -As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). +Wie wir aus dem Kapitel wissen, verwaltet die JavaScript-Engine einen Wert im Speicher, solange er "erreichbar" ist und potenziell verwendet werden könnte. + +Zum Beispiel: -For instance: ```js let john = { name: "John" }; -// the object can be accessed, john is the reference to it +// Das Objekt kann aufgerufen werden, john ist die Referenz darauf -// overwrite the reference +// Überschreibe die Referenz john = null; *!* -// the object will be removed from memory +// Das Objekt wird aus dem Speicher entfernt */!* ``` -Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory. +Normalerweise werden Eigenschaften eines Objekts oder Elemente eines Arrays bzw. einer anderen Datenstruktur als erreichbar betrachtet und bleiben im Speicher solange diese Datenstruktur im Speicher ist. -For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it. +Beispielsweise, wenn wir ein Objekt in ein Array stecken, dann wird das Objekt so lange existieren, wie das Array existiert, auch wenn es keine weiteren Referenzen darauf gibt. -Like this: +So wie hier: ```js let john = { name: "John" }; let array = [ john ]; -john = null; // overwrite the reference +john = null; // Überschreibe die Referenz *!* -// john is stored inside the array, so it won't be garbage-collected -// we can get it as array[0] +// Das zuvor von john referenzierte Objekt wird innerhalb des Arrays gespeichert +// deshalb wird es nicht vom Garbage-Collector entfernt +// wir können es als array[0] abrufen */!* ``` -Similar to that, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected. +Ähnlich verhält es sich, wenn wir ein Objekt als Schlüssel in einer regulären `Map` nutzen, dann existiert das Objekt so lange wie die `Map`. Es belegt Speicher und wird möglicherweise nicht vom Garbage-Collector entfernt. -For instance: +Zum Beispiel: ```js let john = { name: "John" }; @@ -45,36 +47,36 @@ let john = { name: "John" }; let map = new Map(); map.set(john, "..."); -john = null; // overwrite the reference +john = null; // Überschreibe die Referenz *!* -// john is stored inside the map, -// we can get it by using map.keys() +// Das Objekt john wird innerhalb der Map gespeichert, +// wir können es erhalten, indem wir map.keys() verwenden */!* ``` -`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) ist in diesem Aspekt grundsätzlich anders. Es verhindert nicht die Garbage-Collection von Schlüsselobjekten. -Let's see what it means on examples. +Lass uns anhand von Beispielen anschauen, was das bedeutet. ## WeakMap -The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: +Der erste Unterschied zwischen [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) und [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) ist, dass Schlüssel Objekte sein müssen, keine primitiven Werte: ```js run let weakMap = new WeakMap(); let obj = {}; -weakMap.set(obj, "ok"); // works fine (object key) +weakMap.set(obj, "ok"); // funktioniert (Objekt als Schlüssel) *!* -// can't use a string as the key -weakMap.set("test", "Whoops"); // Error, because "test" is not an object +// kann keine Zeichenkette als Schlüssel verwenden +weakMap.set("test", "Hoppla"); // Fehler, weil "test" kein Objekt ist */!* ``` -Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. +Wenn wir nun ein Objekt als Schlüssel darin verwenden und es keine anderen Referenzen auf dieses Objekt gibt, wird es automatisch aus dem Speicher (und aus der Map) entfernt. ```js let john = { name: "John" }; @@ -82,131 +84,132 @@ let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); -john = null; // overwrite the reference +john = null; // Überschreibe die Referenz -// john is removed from memory! +// john wird aus dem Speicher entfernt! ``` -Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it will be automatically deleted from the map (and memory). +Vergleich das mit dem regulären `Map`-Beispiel oben. Wenn `john` jetzt nur als Schlüssel einer `WeakMap` existiert -- wird es automatisch aus der Map (und dem Speicher) gelöscht. -`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it. +`WeakMap` unterstützt keine Iteration und die Methoden `keys()`, `values()`, `entries()`, es gibt also keine Möglichkeit, alle Schlüssel oder Werte daraus zu holen. -`WeakMap` has only the following methods: +`WeakMap` hat nur die folgenden Methoden: -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) -Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. +Warum solche Einschränkungen? Das liegt an technischen Gründen. Wenn ein Objekt alle anderen Referenzen verloren hat (wie `john` im Code oben), dann soll es automatisch vom Garbage-Collector entfernt werden. Aber technisch ist nicht genau festgelegt *wann die Bereinigung stattfindet*. -The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. +Die JavaScript-Engine entscheidet darüber. Sie kann wählen, die Speicherbereinigung sofort durchzuführen oder zu warten und die Reinigung später durchzuführen, wenn mehr Löschvorgänge stattfinden. Daher ist die aktuelle Elementanzahl einer `WeakMap` nicht bekannt. Die Engine könnte sie bereits bereinigt haben oder nicht, oder nur teilweise. Aus diesem Grund werden Methoden, die auf alle Schlüssel/Werte zugreifen, nicht unterstützt. -Now where do we need such data structure? +Nun, wo brauchen wir eine solche Datenstruktur? -## Use case: additional data +## Anwendungsfall: zusätzliche Daten -The main area of application for `WeakMap` is an *additional data storage*. +Das Hauptanwendungsgebiet für `WeakMap` ist ein *zusätzlicher Datenspeicher*. -If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is exactly what's needed. +Wenn wir mit einem Objekt arbeiten, das zu einem anderen Code "gehört", vielleicht sogar zu einer Drittanbieter-Bibliothek, und wir möchten einige Daten speichern, die damit verbunden sind, die aber nur existieren sollen, so lange das Objekt lebt - dann ist `WeakMap` genau das, was wir brauchen. -We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well. +Wir legen die Daten in eine `WeakMap`, indem wir das Objekt als Schlüssel verwenden, und wenn das Objekt vom Garbage Collector abgerufen wird, verschwinden diese Daten ebenfalls automatisch. ```js -weakMap.set(john, "secret documents"); -// if john dies, secret documents will be destroyed automatically +weakMap.set(john, "geheime Dokumente"); +// wenn john eliminiert wird, werden die geheimen Dokumente automatisch zerstört ``` -Let's look at an example. +Schauen wir uns ein Beispiel an. -For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don't want to store their visit count anymore. +Zum Beispiel haben wir Code, der die Besucherzahl für Benutzer zählt. Die Informationen werden in einer Map gespeichert: Ein Benutzerobjekt ist der Schlüssel und die Besucherzahl ist der Wert. Wenn ein Benutzer die Seite verlässt (sein Objekt wird vom Garbage Collector entfernt), möchten wir seine Besucherzahl nicht mehr speichern. -Here's an example of a counting function with `Map`: +Hier ein Beispiel für eine Zählfunktion mit `Map`: ```js // 📁 visitsCount.js -let visitsCountMap = new Map(); // map: user => visits count +let visitsCountMap = new Map(); // map: Benutzer => Besucherzählung -// increase the visits count +// erhöhe die Besucherzahl function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -And here's another part of the code, maybe another file using it: +Und hier ist ein anderer Teil des Codes, vielleicht eine andere Datei, die ihn verwendet: ```js // 📁 main.js let john = { name: "John" }; -countUser(john); // count his visits +countUser(john); // zähle seine Besuche -// later john leaves us +// später verlässt uns john john = null; ``` -Now `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +Jetzt sollte das `john` Objekt vom Garbage Collector entfernt werden, aber bleibt im Speicher, da es ein Schlüssel in `visitsCountMap` ist. -We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures. +Wir müssen `visitsCountMap` aufräumen, wenn wir Benutzer entfernen, sonst wächst die Map im Speicher unendlich. Eine solche Reinigung kann zu einer mühsamen Aufgabe in komplexen Architekturen werden. -We can avoid it by switching to `WeakMap` instead: +Wir können dies vermeiden, indem wir auf `WeakMap` umsteigen: ```js // 📁 visitsCount.js -let visitsCountMap = new WeakMap(); // weakmap: user => visits count +let visitsCountMap = new WeakMap(); // weakmap: Benutzer => Besucherzählung -// increase the visits count +// erhöhe die Besucherzahl function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +Jetzt müssen wir `visitsCountMap` nicht aufräumen. Nachdem das `john` Objekt unerreichbar wird, auf alle Weisen außer als Schlüssel der `WeakMap`, wird es zusammen mit den Informationen zu diesem Schlüssel aus der `WeakMap` aus dem Speicher entfernt. -## Use case: caching +## Anwendungsfall: Caching -Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it. +Ein weiteres häufiges Beispiel ist Caching. Wir können Ergebnisse einer Funktion speichern ("cachen"), so dass spätere Aufrufe für dasselbe Objekt es wiederverwenden können. -We can use `Map` to store results, like this: +Um das zu erreichen, könnten wir `Map` verwenden (kein optimales Szenario): ```js run // 📁 cache.js let cache = new Map(); -// calculate and remember the result +// Berechne und merke das Ergebnis function process(obj) { if (!cache.has(obj)) { - let result = /* calculations of the result for */ obj; + let result = /* Berechnungen des Ergebnisses für */ obj; cache.set(obj, result); + return result; } return cache.get(obj); } *!* -// Now we use process() in another file: +// Jetzt verwenden wir process() in einer anderen Datei: */!* // 📁 main.js -let obj = {/* let's say we have an object */}; +let obj = {/* sagen wir, wir haben ein Objekt */}; -let result1 = process(obj); // calculated +let result1 = process(obj); // berechnet -// ...later, from another place of the code... -let result2 = process(obj); // remembered result taken from cache +// ...später, von einem anderen Teil des Codes... +let result2 = process(obj); // Ergebnis aus dem Cache genommen -// ...later, when the object is not needed any more: +// ...später, wenn das Objekt nicht mehr benötigt wird: obj = null; -alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) +alert(cache.size); // 1 (Autsch! Das Objekt ist immer noch im Cache und verbraucht Speicher!) ``` -For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. +Für mehrere Aufrufe von `process(obj)` mit demselben Objekt berechnet es das Ergebnis nur das erste Mal und nimmt es dann aus dem `cache`. Der Nachteil ist, dass wir `cache` aufräumen müssen, wenn das Objekt nicht mehr benötigt wird. -If we replace `Map` with `WeakMap`, then this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected. +Wenn wir `Map` durch `WeakMap` ersetzen, verschwindet dieses Problem. Das gecachte Ergebnis wird automatisch aus dem Speicher entfernt, nachdem das Objekt vom Garbage Collector entfernt wird. ```js run // 📁 cache.js @@ -214,42 +217,43 @@ If we replace `Map` with `WeakMap`, then this problem disappears: the cached res let cache = new WeakMap(); */!* -// calculate and remember the result +// Berechne und merke das Ergebnis function process(obj) { if (!cache.has(obj)) { - let result = /* calculate the result for */ obj; + let result = /* Berechnungen des Ergebnisses für */ obj; cache.set(obj, result); + return result; } return cache.get(obj); } // 📁 main.js -let obj = {/* some object */}; +let obj = {/* irgendein Objekt */}; let result1 = process(obj); let result2 = process(obj); -// ...later, when the object is not needed any more: +// ...später, wenn das Objekt nicht mehr benötigt wird: obj = null; -// Can't get cache.size, as it's a WeakMap, -// but it's 0 or soon be 0 -// When obj gets garbage collected, cached data will be removed as well +// Wir können cache.size nicht erhalten, da es sich um eine WeakMap handelt, +// aber es ist 0 oder wird bald 0 sein +// Wenn obj vom Garbage Collector abgerufen wird, werden die gecachten Daten ebenfalls entfernt ``` ## WeakSet -`WeakSet` behaves similarly: +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) verhält sich ähnlich: -- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). -- An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. +- Es ist analog zu `Set`, aber wir können nur Objekte zu `WeakSet` hinzufügen (keine Primitiven). +- Ein Objekt existiert im Set, solange es von irgendwo anders aus erreichbar ist. +- Wie bei `Set`, unterstützt es [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) und [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), aber nicht `size`, `keys()` und keine Iterationen. -Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +Als "schwach" dient es ebenfalls als zusätzlicher Speicher. Aber nicht für beliebige Daten, sondern für "Ja/Nein"-Fakten. Die Mitgliedschaft in einem `WeakSet` könnte etwas über das Objekt aussagen. -For instance, we can add users to `WeakSet` to keep track of those who visited our site: +Zum Beispiel können wir Benutzer zu `WeakSet` hinzufügen, um diejenigen zu verfolgen, die unsere Seite besucht haben: ```js run let visitedSet = new WeakSet(); @@ -258,31 +262,33 @@ let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -visitedSet.add(john); // John visited us -visitedSet.add(pete); // Then Pete -visitedSet.add(john); // John again +visitedSet.add(john); // John hat uns besucht +visitedSet.add(pete); // Dann Pete +visitedSet.add(john); // John noch einmal -// visitedSet has 2 users now +// visitedSet hat jetzt 2 Benutzer -// check if John visited? -alert(visitedSet.has(john)); // true +// überprüfen, ob John besucht hat? +alert(visitedSet.has(john)); // wahr -// check if Mary visited? -alert(visitedSet.has(mary)); // false +// überprüfen, ob Mary besucht hat? +alert(visitedSet.has(mary)); // falsch john = null; -// visitedSet will be cleaned automatically +// visitedSet wird automatisch bereinigt ``` -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +Die auffälligste Einschränkung von `WeakMap` und `WeakSet` ist das Fehlen von Iterationen und die Unfähigkeit, alle aktuellen Inhalte zu erhalten. Das mag unpraktisch erscheinen, verhindert aber nicht, dass `WeakMap/WeakSet` ihre Hauptaufgabe erfüllen -- ein "zusätzlicher" Speicher von Daten für Objekte, die an anderer Stelle gespeichert/verwaltet werden. + +## Zusammenfassung -## Summary +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) ist eine `Map`-ähnliche Sammlung, die nur Objekte als Schlüssel erlaubt und diese zusammen mit dem zugehörigen Wert entfernt, sobald sie anderweitig unzugänglich werden. -`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) ist eine `Set`-ähnliche Sammlung, die nur Objekte speichert und diese entfernt, sobald sie anderweitig unzugänglich werden. -`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. +Ihre Hauptvorteile sind, dass sie eine schwache Referenz zu Objekten haben, sodass diese leicht vom Garbage Collector entfernt werden können. -Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed. +Das geht einher mit dem Nachteil, dass sie keine Unterstützung haben für `clear`, `size`, `keys`, `values`... -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +`WeakMap` und `WeakSet` werden als "sekundäre" Datenstrukturen in Verbindung mit der "primären" Objektspeicherung verwendet. Sobald das Objekt aus dem primären Speicher entfernt wird, und es nur als Schlüssel einer `WeakMap` oder in einem `WeakSet` gefunden wird, wird es automatisch aufgeräumt. diff --git a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md index 27a7b418a..9780b5d11 100644 --- a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md +++ b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md @@ -17,12 +17,12 @@ let salaries = { alert( sumSalaries(salaries) ); // 650 ``` -Or, optionally, we could also get the sum using `Object.values` and `reduce`: +Oder optional könnten wir die Summe auch mit `Object.values` und `reduce` erhalten: ```js -// reduce loops over array of salaries, -// adding them up -// and returns the result +// reduce iteriert über das Array von Gehältern, +// addiert diese +// und gibt das Ergebnis zurück function sumSalaries(salaries) { return Object.values(salaries).reduce((a, b) => a + b, 0) // 650 } diff --git a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md index 211357d03..89d9ca6be 100644 --- a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md +++ b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md @@ -2,15 +2,15 @@ importance: 5 --- -# Sum the properties +# Summe der Eigenschaften -There is a `salaries` object with arbitrary number of salaries. +Es gibt ein `salaries` Objekt mit einer beliebigen Anzahl von Gehältern. -Write the function `sumSalaries(salaries)` that returns the sum of all salaries using `Object.values` and the `for..of` loop. +Schreibe die Funktion `sumSalaries(salaries)`, die die Summe aller Gehälter zurückgibt, indem sie `Object.values` und die Schleife `for..of` verwendet. -If `salaries` is empty, then the result must be `0`. +Wenn `salaries` leer ist, dann muss das Ergebnis `0` sein. -For instance: +Zum Beispiel: ```js let salaries = { @@ -21,4 +21,3 @@ let salaries = { alert( sumSalaries(salaries) ); // 650 ``` - diff --git a/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md index d7aebb1fa..7d7ee3359 100644 --- a/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md +++ b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Count properties +# Zähle Eigenschaften -Write a function `count(obj)` that returns the number of properties in the object: +Schreibe eine Funktion `count(obj)`, die die Anzahl der Eigenschaften in einem Objekt zurückgibt: ```js let user = { @@ -15,7 +15,6 @@ let user = { alert( count(user) ); // 2 ``` -Try to make the code as short as possible. - -P.S. Ignore symbolic properties, count only "regular" ones. +Versuche, den Code so kurz wie möglich zu halten. +P.S. Ignoriere symbolische Eigenschaften, zähle nur die "normalen". diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index b633dc274..3f0b5a572 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -1,42 +1,41 @@ - # Object.keys, values, entries -Let's step away from the individual data structures and talk about the iterations over them. +Lass uns einen Schritt zurücktreten von den einzelnen Datenstrukturen und über deren Iteration sprechen. -In the previous chapter we saw methods `map.keys()`, `map.values()`, `map.entries()`. +Im letzten Kapitel haben wir die Methoden `map.keys()`, `map.values()`, `map.entries()` gesehen. -These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too. +Diese Methoden sind generisch, es ist allgemein üblich, sie für Datenstrukturen zu verwenden. Wenn wir jemals eine eigene Datenstruktur erstellen, sollten wir sie ebenfalls implementieren. -They are supported for: +Sie werden unterstützt für: - `Map` - `Set` - `Array` -Plain objects also support similar methods, but the syntax is a bit different. +Einfache Objekte unterstützen auch ähnliche Methoden, aber die Syntax ist ein wenig anders. ## Object.keys, values, entries -For plain objects, the following methods are available: +Für einfache Objekte sind die folgenden Methoden verfügbar: -- [Object.keys(obj)](mdn:js/Object/keys) -- returns an array of keys. -- [Object.values(obj)](mdn:js/Object/values) -- returns an array of values. -- [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of `[key, value]` pairs. +- [Object.keys(obj)](mdn:js/Object/keys) -- gibt ein Array von Schlüsseln zurück. +- [Object.values(obj)](mdn:js/Object/values) -- gibt ein Array von Werten zurück. +- [Object.entries(obj)](mdn:js/Object/entries) -- gibt ein Array von `[key, value]`-Paaren zurück. -Please note the distinctions (compared to map for example): +Bitte beachte die Unterschiede (zun Beispiel im Vergleich zu Map): | | Map | Object | |-------------|------------------|--------------| -| Call syntax | `map.keys()` | `Object.keys(obj)`, but not `obj.keys()` | -| Returns | iterable | "real" Array | +| Aufrufsyntax | `map.keys()` | `Object.keys(obj)`, aber nicht `obj.keys()` | +| Rückgabe | iterable | "echtes" Array | -The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`. +Der erste Unterschied ist, dass wir `Object.keys(obj)` aufrufen müssen, und nicht `obj.keys()`. -Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `data` that implements its own `data.values()` method. And we still can call `Object.values(data)` on it. +Warum ist das so? Der Hauptgrund ist Flexibilität. Erinnere dich, Objekte sind die Basis aller komplexen Strukturen in JavaScript. Wir können also ein eigenes Objekt wie `data` haben, das seine eigene Methode `data.values()` implementiert. Und wir können immer noch `Object.values(data)` darauf anwenden. -The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons. +Der zweite Unterschied ist, dass die `Object.*` Methoden "echte" Array-Objekte zurückgeben, nicht nur ein Iterable. Das liegt hauptsächlich an historischen Gründen. -For instance: +Zum Beispiel: ```js let user = { @@ -49,7 +48,7 @@ let user = { - `Object.values(user) = ["John", 30]` - `Object.entries(user) = [ ["name","John"], ["age",30] ]` -Here's an example of using `Object.values` to loop over property values: +Hier ist ein Beispiel für die Verwendung von `Object.values` zum Durchlaufen von Eigenschaftswerten: ```js run let user = { @@ -57,30 +56,30 @@ let user = { age: 30 }; -// loop over values +// Schleife über Werte for (let value of Object.values(user)) { - alert(value); // John, then 30 + alert(value); // John, dann 30 } ``` -```warn header="Object.keys/values/entries ignore symbolic properties" -Just like a `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys. +```warn header="Object.keys/values/entries ignorieren symbolische Eigenschaften" +Wie bei einer `for..in`-Schleife, ignorieren diese Methoden Eigenschaften, die `Symbol(...)` als Schlüssel verwenden. -Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys. +Normalerweise ist das praktisch. Aber wenn wir auch symbolische Schlüssel wollen, dann gibt es eine separate Methode [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols), die ein Array von nur symbolischen Schlüsseln zurückgibt. Außerdem gibt es die Methode [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys), die *alle* Schlüssel zurückgibt. ``` -## Transforming objects +## Transformation von Objekten -Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others. +Objekten fehlen viele Methoden, die für Arrays existieren, z.B. `map`, `filter` und andere. -If we'd like to apply them, then we can use `Object.entries` followed by `Object.fromEntries`: +Wenn wir sie anwenden möchten, dann können wir `Object.entries` gefolgt von `Object.fromEntries` verwenden: -1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. -2. Use array methods on that array, e.g. `map`. -3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. +1. Verwende `Object.entries(obj)`, um ein Array von Schlüssel/Wert-Paaren von `obj` zu erhalten. +2. Verwende Array-Methoden auf diesem Array, z.B. `map`, um diese Schlüssel/Wert-Paare zu transformieren. +3. Verwende `Object.fromEntries(array)` auf dem resultierenden Array, um es wieder in ein Objekt umzuwandeln. -For example, we have an object with prices, and would like to double them: +Zum Beispiel könnten wir ein Objekt mit Preisen haben und möchten diese verdoppeln: ```js run let prices = { @@ -91,12 +90,13 @@ let prices = { *!* let doublePrices = Object.fromEntries( - // convert to array, map, and then fromEntries gives back the object - Object.entries(prices).map(([key, value]) => [key, value * 2]) + // Preise in ein Array konvertieren, jedes Schlüssel/Wert-Paar in ein anderes Paar umwandeln + // und dann gibt fromEntries das Objekt zurück + Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) ); */!* alert(doublePrices.meat); // 8 -``` +``` -It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +Es mag auf den ersten Blick schwierig erscheinen, wird aber leicht verständlich, nachdem Du es ein- oder zweimal verwendet hast. Auf diese Weise können wir leistungsstarke Transformationsketten erstellen. diff --git a/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md index cc226e7c5..1fb55c2a3 100644 --- a/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md +++ b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md @@ -1,4 +1,3 @@ - ```js run let user = { name: "John", @@ -10,4 +9,4 @@ let {name, years: age, isAdmin = false} = user; alert( name ); // John alert( age ); // 30 alert( isAdmin ); // false -``` \ No newline at end of file +``` diff --git a/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md index b68db5c59..4ed055c37 100644 --- a/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md +++ b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Destructuring assignment +# Zuweisung mit Destrukturierung -We have an object: +Wir haben ein Objekt: ```js let user = { @@ -13,18 +13,18 @@ let user = { }; ``` -Write the destructuring assignment that reads: +Schreibe die Zuweisung mit Destrukturierung, die folgendes liest: -- `name` property into the variable `name`. -- `years` property into the variable `age`. -- `isAdmin` property into the variable `isAdmin` (false, if no such property) +- `name` Eigenschaft in die Variable `name`. +- `years` Eigenschaft in die Variable `age`. +- `isAdmin` Eigenschaft in die Variable `isAdmin` (false, falls keine solche Eigenschaft existiert) -Here's an example of the values after your assignment: +Hier ist ein Beispiel von den Werten nach deiner Zuweisung: ```js let user = { name: "John", years: 30 }; -// your code to the left side: +// dein Code auf der linken Seite: // ... = user alert( name ); // John diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js index f4bd5c761..6538af42b 100644 --- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js @@ -1,16 +1,14 @@ function topSalary(salaries) { - let max = 0; + let maxSalary = 0; let maxName = null; for(const [name, salary] of Object.entries(salaries)) { - if (max < salary) { - max = salary; + if (maxSalary < salary) { + maxSalary = salary; maxName = name; } } return maxName; -} - - +} \ No newline at end of file diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md index 9f33de089..86a3cb704 100644 --- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# The maximal salary +# Das maximale Gehalt -There is a `salaries` object: +Es gibt ein `salaries` Objekt: ```js let salaries = { @@ -14,9 +14,9 @@ let salaries = { }; ``` -Create the function `topSalary(salaries)` that returns the name of the top-paid person. +Erstelle die Funktion `topSalary(salaries)`, die den Namen der Person mit dem höchsten Gehalt zurückgibt. -- If `salaries` is empty, it should return `null`. -- If there are multiple top-paid persons, return any of them. +- Wenn `salaries` leer ist, sollte es `null` zurückgeben. +- Wenn es mehrere Personen mit dem höchsten Gehalt gibt, gib eine beliebige davon zurück. -P.S. Use `Object.entries` and destructuring to iterate over key/value pairs. +P.S. Verwende `Object.entries` und Destructuring, um über Schlüssel-Wert-Paare zu iterieren. diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index 46aa760a9..ec8a19b04 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -1,44 +1,51 @@ -# Destructuring assignment +# Destrukturierende Zuweisung -The two most used data structures in JavaScript are `Object` and `Array`. +Die beiden am häufigsten verwendeten Datenstrukturen in JavaScript sind `Object` und `Array`. -Objects allow us to create a single entity that stores data items by key, and arrays allow us to gather data items into an ordered collection. +- Objekte ermöglichen es uns, eine einzelne Entität zu erstellen, die Datenpunkte anhand von Schlüsseln speichert. +- Arrays erlauben es uns, Datenpunkte in einer geordneten Liste zusammenzufassen. -But when we pass those to a function, it may need not an object/array as a whole, but rather individual pieces. +Dennoch, wenn wir diese an eine Funktion übergeben, wird möglicherweise nicht das komplette Objekt/Array als Ganzes benötigt. Sie könnte einzelne Teile benötigen. -*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. +Die *destrukturierende Zuweisung* ist eine spezielle Syntax, die es uns ermöglicht, Arrays oder Objekte in eine Reihe von Variablen "auszupacken", da dies manchmal bequemer ist. -## Array destructuring +Destrukturierung funktioniert auch hervorragend bei komplexen Funktionen mit vielen Parametern, Default-Werten und so weiter. Gleich werden wir das sehen. -An example of how the array is destructured into variables: +## Array-Destrukturierung + +Hier ist ein Beispiel, wie ein Array in Variablen destrukturiert wird: ```js -// we have an array with the name and surname -let arr = ["Ilya", "Kantor"] +// wir haben ein Array mit dem Namen und dem Nachnamen +let arr = ["John", "Smith"] *!* -// destructuring assignment -// sets firstName = arr[0] -// and surname = arr[1] +// destrukturierende Zuweisung +// setzt firstName = arr[0] +// und surname = arr[1] let [firstName, surname] = arr; */!* -alert(firstName); // Ilya -alert(surname); // Kantor +alert(firstName); // John +alert(surname); // Smith ``` -Now we can work with variables instead of array members. +Nun können wir mit Variablen statt mit Array-Elementen arbeiten. -It looks great when combined with `split` or other array-returning methods: +Es sieht großartig aus, wenn es mit `split` oder anderen Array-Rückgabemethoden kombiniert wird: -```js -let [firstName, surname] = "Ilya Kantor".split(' '); +```js run +let [firstName, surname] = "John Smith".split(' '); +alert(firstName); // John +alert(surname); // Smith ``` -````smart header="\"Destructuring\" does not mean \"destructive\"." -It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified. +Wie man sieht, ist die Syntax einfach. Es gibt jedoch einige Besonderheiten. Lass uns weitere Beispiele ansehen, um es besser zu verstehen. -It's just a shorter way to write: +````smart header="\"Destrukturierung\" bedeutet nicht \"destruktiv\"." +Sie wird "destrukturierende Zuweisung" genannt, weil sie "destrukturiert", indem sie Elemente in Variablen kopiert. Aber das Array selbst wird nicht verändert. + +Sie ist einfach eine kürzere Art zu schreiben: ```js // let [firstName, surname] = arr; let firstName = arr[0]; @@ -46,52 +53,50 @@ let surname = arr[1]; ``` ```` -````smart header="Ignore elements using commas" -Unwanted elements of the array can also be thrown away via an extra comma: +````smart header="Elemente ignorieren mit Kommas" +Unerwünschte Elemente des Arrays können auch über ein zusätzliches Komma verworfen werden: ```js run *!* -// second element is not needed -let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// das zweite Element wird nicht benötigt +let [firstName, , title] = ["Julius", "Caesar", "Konsul", "der Römischen Republik"]; */!* -alert( title ); // Consul +alert( title ); // Konsul ``` -In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them). +Im obigen Code wird das zweite Element des Arrays übersprungen, das dritte wird `title` zugewiesen und die restlichen Array-Elemente werden ebenfalls übersprungen (da es keine Variablen für sie gibt). ```` -````smart header="Works with any iterable on the right-side" +````smart header="Funktioniert mit jedem Iterierbaren auf der rechten Seite" -...Actually, we can use it with any iterable, not only arrays: +...Tatsächlich können wir sie mit jedem iterierbaren Objekt verwenden, nicht nur Arrays: ```js let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); ``` - +Das funktioniert, weil intern eine Zuweisung über Destrukturierung durch Iterieren über den rechten Wert arbeitet. Es ist eine Art syntaktischer Zucker für den Aufruf von `for..of` über den Wert rechts von `=` und die Zuweisung der Werte. ```` +````smart header="Zuweisen an alles auf der linken Seite" +Wir können jede "zuweisbare" Sache auf der linken Seite verwenden. -````smart header="Assign to anything at the left-side" - -We can use any "assignables" at the left side. - -For instance, an object property: +Zum Beispiel, eine Objekteigenschaft: ```js run let user = {}; -[user.name, user.surname] = "Ilya Kantor".split(' '); +[user.name, user.surname] = "John Smith".split(' '); -alert(user.name); // Ilya +alert(user.name); // John +alert(user.surname); // Smith ``` ```` -````smart header="Looping with .entries()" - -In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method. +````smart header="Schleifen mit .entries()" +Im vorherigen Kapitel haben wir die Methode [Object.entries(obj)](mdn:js/Object/entries) gesehen. -We can use it with destructuring to loop over keys-and-values of an object: +Wir können sie mit Destrukturierung verwenden, um über Schlüssel-Wert-Paare eines Objekts zu iterieren: ```js run let user = { @@ -99,15 +104,15 @@ let user = { age: 30 }; -// loop over keys-and-values +// Schleife über Schlüssel-Wert-Paare *!* for (let [key, value] of Object.entries(user)) { */!* - alert(`${key}:${value}`); // name:John, then age:30 + alert(`${key}:${value}`); // name:John, dann age:30 } ``` -...And the same for a map: +Der ähnliche Code für eine `Map` ist einfacher, da sie iterierbar ist: ```js run let user = new Map(); @@ -115,54 +120,73 @@ user.set("name", "John"); user.set("age", "30"); *!* +// Map iteriert als [key, value]-Paare, sehr praktisch für Destrukturierung for (let [key, value] of user) { */!* - alert(`${key}:${value}`); // name:John, then age:30 + alert(`${key}:${value}`); // name:John, dann age:30 } ``` ```` -```smart header="Swap variables trick" -A well-known trick for swapping values of two variables: +````smart header="Trick zum Tauschen von Variablen" +Es gibt einen bekannten Trick, um die Werte zweier Variablen mithilfe einer Zuweisung über Destrukturierung zu tauschen: ```js run let guest = "Jane"; let admin = "Pete"; -// Swap values: make guest=Pete, admin=Jane +// Lassen Sie uns die Werte tauschen: make guest=Pete, admin=Jane +*!* [guest, admin] = [admin, guest]; +*/!* -alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!) +alert(`${guest} ${admin}`); // Pete Jane (erfolgreich getauscht!) ``` -Here we create a temporary array of two variables and immediately destructure it in swapped order. +Hier erstellen wir ein temporäres Array aus zwei Variablen und destrukturieren es sofort in vertauschter Reihenfolge. -We can swap more than two variables this way. +Wir können auf diese Weise mehr als zwei Variablen tauschen. +```` +### Der Rest '...' -### The rest '...' +Normalerweise, wenn das Array länger ist als die Liste auf der linken Seite, werden die "zusätzlichen" Elemente weggelassen. -If we want not just to get first values, but also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: +Zum Beispiel werden hier nur zwei Elemente genommen, und der Rest wird einfach ignoriert: ```js run -let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; +let [name1, name2] = ["Julius", "Caesar", "Konsul", "der Römischen Republik"]; alert(name1); // Julius alert(name2); // Caesar +// Weitere Elemente werden nirgendwo zugewiesen +``` + +Wenn wir auch alles sammeln möchten, was folgt – können wir einen weiteren Parameter hinzufügen, der "den Rest" bekommt, indem wir drei Punkte `"..."` verwenden: + +```js run +let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Konsul", "der Römischen Republik"*/!*]; *!* -// Note that type of `rest` is Array. -alert(rest[0]); // Consul -alert(rest[1]); // of the Roman Republic +// rest ist ein Array von Elementen, beginnend mit dem dritten +alert(rest[0]); // Konsul +alert(rest[1]); // der Römischen Republik alert(rest.length); // 2 */!* ``` -The value of `rest` is the array of the remaining array elements. We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment. +Der Wert von `rest` ist das Array der verbleibenden Array-Elemente. + +Wir können jeden anderen Variablennamen anstelle von `rest` verwenden, achte nur darauf, dass ihm drei Punkte vorangestellt sind und dass er am Ende der destrukturierenden Zuweisung steht. -### Default values +```js run +let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Konsul", "der Römischen Republik"]; +// nun ist titles = ["Konsul", "der Römischen Republik"] +``` + +### Default-Werte -If there are fewer values in the array than variables in the assignment, there will be no error. Absent values are considered undefined: +Wenn das Array kürzer ist als die Liste der Variablen auf der linken Seite, gibt es keine Fehler. Fehlende Werte werden als undefined angesehen: ```js run *!* @@ -173,49 +197,49 @@ alert(firstName); // undefined alert(surname); // undefined ``` -If we want a "default" value to replace the missing one, we can provide it using `=`: +Wenn wir einen "Default-Wert" als Ersatz für den fehlenden Wert möchten, können wir ihn mit `=` angeben: ```js run *!* -// default values -let [name = "Guest", surname = "Anonymous"] = ["Julius"]; +// Default-Werte +let [name = "Gast", surname = "Anonym"] = ["Julius"]; */!* -alert(name); // Julius (from array) -alert(surname); // Anonymous (default used) +alert(name); // Julius (aus dem Array) +alert(surname); // Anonym (Default-Wert verwendet) ``` -Default values can be more complex expressions or even function calls. They are evaluated only if the value is not provided. +Default-Werte können komplexere Ausdrücke oder sogar Funktionsaufrufe sein. Sie werden nur ausgewertet, wenn der Wert nicht bereitgestellt wird. -For instance, here we use the `prompt` function for two defaults. But it will run only for the missing one: +Zum Beispiel verwenden wir hier die `prompt`-Funktion für zwei Defaults: ```js run -// runs only prompt for surname +// prompt wird nur für surname ausgeführt let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; -alert(name); // Julius (from array) -alert(surname); // whatever prompt gets +alert(name); // Julius (aus dem Array) +alert(surname); // was auch immer prompt erhält ``` +Bitte beachte: Der `prompt` wird nur für den fehlenden Wert (`surname`) ausgeführt. +## Objekt-Destrukturierung -## Object destructuring +Die destrukturierende Zuweisung funktioniert auch mit Objekten. -The destructuring assignment also works with objects. - -The basic syntax is: +Die grundlegende Syntax ist: ```js let {var1, var2} = {var1:…, var2:…} ``` -We have an existing object at the right side, that we want to split into variables. The left side contains a "pattern" for corresponding properties. In the simple case, that's a list of variable names in `{...}`. +Wir sollten ein bestehendes Objekt auf der rechten Seite haben, das wir in Variablen aufteilen möchten. Die linke Seite enthält ein objektähnliches "Muster" für entsprechende Eigenschaften. Im einfachsten Fall ist das eine Liste von Variablennamen in `{...}`. -For instance: +Zum Beispiel: ```js run let options = { - title: "Menu", + title: "Menü", width: 100, height: 200 }; @@ -224,31 +248,33 @@ let options = { let {title, width, height} = options; */!* -alert(title); // Menu +alert(title); // Menü alert(width); // 100 alert(height); // 200 ``` -Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. The order does not matter. This works too: +Die Eigenschaften `options.title`, `options.width` und `options.height` sind den entsprechenden Variablen zugewiesen. + +Die Reihenfolge spielt keine Rolle. Das funktioniert auch: ```js -// changed the order in let {...} -let {height, width, title} = { title: "Menu", height: 200, width: 100 } +// geänderte Reihenfolge in let {...} +let {height, width, title} = { title: "Menü", height: 200, width: 100 } ``` -The pattern on the left side may be more complex and specify the mapping between properties and variables. +Das Muster auf der linken Seite kann komplexer sein und die Zuordnung zwischen Eigenschaften und Variablen festlegen. -If we want to assign a property to a variable with another name, for instance, `options.width` to go into the variable named `w`, then we can set it using a colon: +Wenn wir beispielsweise eine Eigenschaft einem Variablennamen mit einem anderen Namen zuweisen möchten, zum Beispiel `options.width` in die Variable `w`, dann können wir den Variablennamen mit einem Doppelpunkt festlegen: ```js run let options = { - title: "Menu", + title: "Menü", width: 100, height: 200 }; *!* -// { sourceProperty: targetVariable } +// { Quelleigenschaft: Zielvariable } let {width: w, height: h, title} = options; */!* @@ -256,144 +282,144 @@ let {width: w, height: h, title} = options; // height -> h // title -> title -alert(title); // Menu +alert(title); // Menü alert(w); // 100 alert(h); // 200 ``` -The colon shows "what : goes where". In the example above the property `width` goes to `w`, property `height` goes to `h`, and `title` is assigned to the same name. +Der Doppelpunkt zeigt "was : wohin geht". Im obigen Beispiel geht die Eigenschaft `width` zu `w`, die Eigenschaft `height` zu `h` und `title` wird dem gleichen Namen zugewiesen. -For potentially missing properties we can set default values using `"="`, like this: +Für potenziell fehlende Eigenschaften können wir Default-Werte mit `"="` festlegen, wie hier: ```js run let options = { - title: "Menu" + title: "Menü" }; *!* let {width = 100, height = 200, title} = options; */!* -alert(title); // Menu +alert(title); // Menü alert(width); // 100 alert(height); // 200 ``` -Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided. +Genau wie bei Arrays oder Funktionsparametern können Default-Werte beliebige Ausdrücke oder sogar Funktionsaufrufe sein. Sie werden ausgewertet, wenn der Wert nicht bereitgestellt wird. -In the code below `prompt` asks for `width`, but not for `title`: +Im nachfolgenden Code fragt `prompt` nach `width`, aber nicht nach `title`: ```js run let options = { - title: "Menu" + title: "Menü" }; *!* -let {width = prompt("width?"), title = prompt("title?")} = options; +let {width = prompt("Breite?"), title = prompt("Titel?")} = options; */!* -alert(title); // Menu -alert(width); // (whatever the result of prompt is) +alert(title); // Menü +alert(width); // (was auch immer das Ergebnis von prompt ist) ``` -We also can combine both the colon and equality: +Wir können auch sowohl den Doppelpunkt als auch die Gleichheit kombinieren: ```js run let options = { - title: "Menu" + title: "Menü" }; *!* let {width: w = 100, height: h = 200, title} = options; */!* -alert(title); // Menu +alert(title); // Menü alert(w); // 100 alert(h); // 200 ``` -If we have a complex object with many properties, we can extract only what we need: +Wenn wir ein komplexes Objekt mit vielen Eigenschaften haben, können wir nur das extrahieren, was wir brauchen: ```js run let options = { - title: "Menu", + title: "Menü", width: 100, height: 200 }; -// only extract title as a variable +// Nur den Titel als Variable extrahieren let { title } = options; -alert(title); // Menu +alert(title); // Menü ``` -### The rest pattern "..." +### Das Rest-Pattern "..." -What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere? +Was ist, wenn das Objekt mehr Eigenschaften hat, als wir Variablen? Können wir einige nehmen und dann den "Rest" irgendwo zuweisen? -We can use the rest pattern, just like we did with arrays. It's not supported by some older browsers (IE, use Babel to polyfill it), but works in modern ones. +Wir können das Rest-Pattern verwenden, genau wie bei Arrays. Es wird von einigen älteren Browsern nicht unterstützt (IE, hier kann Babel für ein Polyfill verwendet werden), aber es funktioniert in modernen Browsern. -It looks like this: +Es sieht folgendermaßen aus: ```js run let options = { - title: "Menu", + title: "Menü", height: 200, width: 100 }; *!* -// title = property named title -// rest = object with the rest of properties +// title = Eigenschaft namens title +// rest = Objekt mit dem Rest der Eigenschaften let {title, ...rest} = options; */!* -// now title="Menu", rest={height: 200, width: 100} +// jetzt ist title="Menü", rest={height: 200, width: 100} alert(rest.height); // 200 alert(rest.width); // 100 ``` -````smart header="Gotcha if there's no `let`" -In the examples above variables were declared right in the assignment: `let {…} = {…}`. Of course, we could use existing variables too, without `let`. But there's a catch. +````smart header="Aufgepasst, wenn kein `let` vorhanden ist" +In den obigen Beispielen wurden Variablen direkt in der Zuweisung deklariert: `let {…} = {…}`. Natürlich könnten wir auch vorhandene Variablen ohne `let` verwenden. Es gibt aber einen Haken. -This won't work: +Das wird nicht funktionieren: ```js run let title, width, height; -// error in this line -{title, width, height} = {title: "Menu", width: 200, height: 100}; +// Fehler in dieser Zeile +{title, width, height} = {title: "Menü", width: 200, height: 100}; ``` -The problem is that JavaScript treats `{...}` in the main code flow (not inside another expression) as a code block. Such code blocks can be used to group statements, like this: +Das Problem ist, dass JavaScript `{...}` im Hauptcodefluss (nicht innerhalb eines anderen Ausdrucks) als Codeblock behandelt. Solche Codeblöcke können verwendet werden, um Anweisungen zu gruppieren, wie hier: ```js run { - // a code block - let message = "Hello"; + // ein Codeblock + let message = "Hallo"; // ... alert( message ); } ``` -So here JavaScript assumes that we have a code block, that's why there's an error. We want destructuring instead. +Hier nimmt JavaScript also an, dass wir einen Codeblock haben, deshalb gibt es einen Fehler. Wir wollen stattdessen Destrukturierung. -To show JavaScript that it's not a code block, we can wrap the expression in parentheses `(...)`: +Um JavaScript zu zeigen, dass es kein Codeblock ist, können wir den Ausdruck in Klammern `(...)` einpacken: ```js run let title, width, height; -// okay now -*!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; +// jetzt ist es okay +*!*(*/!*{title, width, height} = {title: "Menü", width: 200, height: 100}*!*)*/!*; -alert( title ); // Menu +alert( title ); // Menü ``` ```` -## Nested destructuring +## Geschachtelte Destrukturierung -If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +Wenn ein Objekt oder ein Array andere eingebettete Objekte und Arrays enthält, können wir komplexere Muster auf der linken Seite verwenden, um tiefer liegende Teile zu extrahieren. -In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure to extract values from them: +Im nachfolgenden Code enthält `options` ein weiteres Objekt in der Eigenschaft `size` und ein Array in der Eigenschaft `items`. Das Muster auf der linken Seite der Zuweisung hat die gleiche Struktur, um Werte daraus zu extrahieren: ```js run let options = { @@ -401,40 +427,40 @@ let options = { width: 100, height: 200 }, - items: ["Cake", "Donut"], - extra: true + items: ["Kuchen", "Donut"], + extra: true }; -// destructuring assignment split in multiple lines for clarity +// Zuweisung über Destrukturierung in mehreren Zeilen für bessere Übersicht aufgeteilt let { - size: { // put size here + size: { // Größe hier einfügen width, height }, - items: [item1, item2], // assign items here - title = "Menu" // not present in the object (default value is used) + items: [item1, item2], // Gegenstände hier zuweisen + title = "Menü" // nicht im Objekt vorhanden (Default-Wert verwendet) } = options; -alert(title); // Menu +alert(title); // Menü alert(width); // 100 alert(height); // 200 -alert(item1); // Cake +alert(item1); // Kuchen alert(item2); // Donut ``` -All properties of `options` object except `extra` that is absent in the left part, are assigned to corresponding variables: +Alle Eigenschaften des `options` Objekts außer `extra`, die im linken Teil nicht vorhanden sind, werden den entsprechenden Variablen zugewiesen: ![](destructuring-complex.svg) -Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value. +Letztendlich haben wir `width`, `height`, `item1`, `item2` und `title` aus dem Default-Wert. -Note that there are no variables for `size` and `items`, as we take their content instead. +Zu beachten ist, dass es keine Variablen für `size` und `items` gibt, da wir stattdessen deren Inhalt nehmen. -## Smart function parameters +## Intelligente Funktionenparameter -There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on. +Es gibt Zeiten, in denen eine Funktion viele Parameter hat, von denen die meisten optional sind. Das trifft besonders auf Benutzeroberflächen zu. Stelle Dir eine Funktion vor, die ein Menü erstellt. Sie könnte eine Breite, eine Höhe, einen Titel, eine Liste von Elementen und so weiter haben. -Here's a bad way to write such function: +Hier ist eine schlechte Möglichkeit, eine solche Funktion zu schreiben: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -442,32 +468,32 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +Im wirklichen Leben ist das Problem, sich die Reihenfolge der Argumente zu merken. In der Regel versuchen IDEs uns zu helfen, besonders wenn der Code gut dokumentiert ist, aber trotzdem... Ein weiteres Problem ist, wie eine Funktion aufgerufen werden soll, wenn die meisten Parameter per Default in Ordnung sind. -Like this? +Zum Beispiel so? ```js -// undefined where default values are fine +// undefined, wo Standardwerte passen showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` -That's ugly. And becomes unreadable when we deal with more parameters. +Das ist hässlich. Und es wird unlesbar, wenn wir es mit mehreren Parametern zu tun haben. -Destructuring comes to the rescue! +Destrukturierung kommt zur Rettung! -We can pass parameters as an object, and the function immediately destructurizes them into variables: +Wir können Parameter als Objekt übergeben, und die Funktion destrukturiert sie sofort in Variablen: ```js run -// we pass object to function +// wir übergeben ein Objekt an die Funktion let options = { title: "My menu", items: ["Item1", "Item2"] }; -// ...and it immediately expands it to variables +// ...und es entfaltet sich sofort in Variablen function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { - // title, items – taken from options, - // width, height – defaults used + // title, items – entnommen aus options, + // width, height – Standardwerte verwendet alert( `${title} ${width} ${height}` ); // My Menu 200 100 alert( items ); // Item1, Item2 } @@ -475,7 +501,7 @@ function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []} showMenu(options); ``` -We can also use more complex destructuring with nested objects and colon mappings: +Wir können auch komplexere Destrukturierung mit verschachtelten Objekten und Zuordnungen mit Doppelpunkt verwenden: ```js run let options = { @@ -486,9 +512,9 @@ let options = { *!* function showMenu({ title = "Untitled", - width: w = 100, // width goes to w - height: h = 200, // height goes to h - items: [item1, item2] // items first element goes to item1, second to item2 + width: w = 100, // Breite geht zu w + height: h = 200, // Höhe geht zu h + items: [item1, item2] // erstes Element von items geht zu item1, zweites zu item2 }) { */!* alert( `${title} ${w} ${h}` ); // My Menu 100 200 @@ -499,7 +525,7 @@ function showMenu({ showMenu(options); ``` -The full syntax is the same as for a destructuring assignment: +Die vollständige Syntax entspricht einer destrukturierenden Zuweisung: ```js function({ incomingProperty: varName = defaultValue @@ -507,17 +533,17 @@ function({ }) ``` -Then, for an object of parameters, there will be a variable `varName` for property `incomingProperty`, with `defaultValue` by default. +Dann gibt es für ein Objekt an Parametern eine Variable `varName` für die Eigenschaft `incomingProperty`, per Default mit `defaultValue`. -Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object: +Bitte beachte, dass eine solche Destrukturierung voraussetzt, dass `showMenu()` ein Argument hat. Wenn wir alle Werte als Default möchten, dann sollten wir ein leeres Objekt übergeben: ```js -showMenu({}); // ok, all values are default +showMenu({}); // ok, alle Werte sind Default -showMenu(); // this would give an error +showMenu(); // das würde einen Fehler verursachen ``` -We can fix this by making `{}` the default value for the whole object of parameters: +Wir können dies beheben, indem wir `{}` als Default-Wert für das gesamte Objekt an Parametern machen: ```js run function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { @@ -527,26 +553,26 @@ function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { showMenu(); // Menu 100 200 ``` -In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize. +Im obigen Code ist das ganze Argumentobjekt per Default `{}`, so dass es immer etwas zum Destrukturieren gibt. -## Summary +## Zusammenfassung -- Destructuring assignment allows for instantly mapping an object or array onto many variables. -- The full object syntax: +- Die destrukturierende Zuweisung ermöglicht eine sofortige Zuordnung eines Objekts oder Arrays auf viele Variablen. +- Die vollständige Objektsyntax: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` - This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used. + Das bedeutet, dass die Eigenschaft `prop` in die Variable `varName` gehen sollte, und wenn eine solche Eigenschaft nicht existiert, dann sollte der Wert `default` verwendet werden. - Object properties that have no mapping are copied to the `rest` object. + Eigenschaften des Objekts, die nicht zugeordnet sind, werden auf das Objekt `rest` kopiert. -- The full array syntax: +- Die vollständige Array-Syntax: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = standard, item2, ...rest] = array ``` - The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`. + Das erste Element geht zu `item1`; das zweite zu `item2`, alle weiteren bilden das Array `rest`. -- It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one. +- Es ist möglich, Daten aus verschachtelten Arrays/Objekten zu extrahieren, dafür muss die linke Seite dieselbe Struktur wie die rechte haben. diff --git a/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg b/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg index cb496bf66..8a1ff1a93 100644 --- a/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg +++ b/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md index 9bb1d749c..817f011f9 100644 --- a/1-js/05-data-types/11-date/1-new-date/solution.md +++ b/1-js/05-data-types/11-date/1-new-date/solution.md @@ -1,8 +1,18 @@ -The `new Date` constructor uses the local time zone. So the only important thing to remember is that months start from zero. +Der `new Date` Konstruktor verwendet die lokale Zeitzone. Das Einzige, woran du dich erinnern musst, ist, dass die Zählung der Monate bei Null beginnt. -So February has number 1. +Also hat der Februar die Nummer 1. + +Hier ist ein Beispiel mit Zahlen als Datumskomponenten: + +```js run +// new Date(Jahr, Monat, Tag, Stunde, Minute, Sekunde, Millisekunde) +let d1 = new Date(2012, 1, 20, 3, 12); +alert( d1 ); +``` +Wir könnten auch ein Datum aus einem String erstellen, so wie hier: ```js run -let d = new Date(2012, 1, 20, 3, 12); -alert( d ); +//new Date(Datenstring) +let d2 = new Date("2012-02-20T03:12"); +alert( d2 ); ``` diff --git a/1-js/05-data-types/11-date/1-new-date/task.md b/1-js/05-data-types/11-date/1-new-date/task.md index 1b40d5ac0..99b1dc587 100644 --- a/1-js/05-data-types/11-date/1-new-date/task.md +++ b/1-js/05-data-types/11-date/1-new-date/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Create a date +# Erstelle ein Datum -Create a `Date` object for the date: Feb 20, 2012, 3:12am. The time zone is local. +Erstelle ein `Date`-Objekt für das Datum: 20. Feb. 2012, 3:12 Uhr morgens. Die Zeitzone ist lokal. -Show it using `alert`. +Zeige es mit `alert` an. diff --git a/1-js/05-data-types/11-date/2-get-week-day/solution.md b/1-js/05-data-types/11-date/2-get-week-day/solution.md index 58d75c1c3..fe8af0dd1 100644 --- a/1-js/05-data-types/11-date/2-get-week-day/solution.md +++ b/1-js/05-data-types/11-date/2-get-week-day/solution.md @@ -1,14 +1,14 @@ -The method `date.getDay()` returns the number of the weekday, starting from sunday. +Die Methode `date.getDay()` gibt die Zahl des Wochentags zurück, beginnend mit Sonntag. -Let's make an array of weekdays, so that we can get the proper day name by its number: +Lass uns ein Array von Wochentagen erstellen, damit wir den richtigen Namen des Tages anhand seiner Nummer erhalten können: ```js run demo function getWeekDay(date) { - let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; + let days = ['SO', 'MO', 'DI', 'MI', 'DO', 'FR', 'SA']; return days[date.getDay()]; } -let date = new Date(2014, 0, 3); // 3 Jan 2014 +let date = new Date(2014, 0, 3); // 3. Jan 2014 alert( getWeekDay(date) ); // FR ``` diff --git a/1-js/05-data-types/11-date/2-get-week-day/task.md b/1-js/05-data-types/11-date/2-get-week-day/task.md index 5cf31565d..48b6ece8d 100644 --- a/1-js/05-data-types/11-date/2-get-week-day/task.md +++ b/1-js/05-data-types/11-date/2-get-week-day/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Show a weekday +# Zeige einen Wochentag -Write a function `getWeekDay(date)` to show the weekday in short format: 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'. +Schreibe eine Funktion `getWeekDay(date)`, um den Wochentag in Kurzform anzuzeigen: 'MO', 'DI', 'MI', 'DO', 'FR', 'SA', 'SO'. -For instance: +Zum Beispiel: ```js no-beautify -let date = new Date(2012, 0, 3); // 3 Jan 2012 -alert( getWeekDay(date) ); // should output "TU" +let date = new Date(2012, 0, 3); // 3. Jan 2012 +alert( getWeekDay(date) ); // sollte "DI" ausgeben ``` diff --git a/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js index fb9e3d2a4..79ae6250b 100644 --- a/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js +++ b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js @@ -2,7 +2,7 @@ function getLocalDay(date) { let day = date.getDay(); - if (day == 0) { // weekday 0 (sunday) is 7 in european + if (day == 0) { // Wochentag 0 (Sonntag) ist 7 im europäischen Format day = 7; } diff --git a/1-js/05-data-types/11-date/3-weekday/task.md b/1-js/05-data-types/11-date/3-weekday/task.md index ba62790cf..87a0421b8 100644 --- a/1-js/05-data-types/11-date/3-weekday/task.md +++ b/1-js/05-data-types/11-date/3-weekday/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# European weekday +# Europäischer Wochentag -European countries have days of week starting with Monday (number 1), then Tuesday (number 2) and till Sunday (number 7). Write a function `getLocalDay(date)` that returns the "European" day of week for `date`. +Europäische Länder beginnen die Woche mit Montag (Nummer 1), gefolgt von Dienstag (Nummer 2) bis hin zum Sonntag (Nummer 7). Schreibe eine Funktion `getLocalDay(date)`, die den "europäischen" Wochentag für `date` zurückgibt. ```js no-beautify -let date = new Date(2012, 0, 3); // 3 Jan 2012 -alert( getLocalDay(date) ); // tuesday, should show 2 +let date = new Date(2012, 0, 3); // 3. Jan 2012 +alert( getLocalDay(date) ); // Dienstag, sollte 2 anzeigen ``` diff --git a/1-js/05-data-types/11-date/4-get-date-ago/solution.md b/1-js/05-data-types/11-date/4-get-date-ago/solution.md index 5c394c100..e63427843 100644 --- a/1-js/05-data-types/11-date/4-get-date-ago/solution.md +++ b/1-js/05-data-types/11-date/4-get-date-ago/solution.md @@ -1,4 +1,4 @@ -The idea is simple: to substract given number of days from `date`: +Die Idee ist einfach: eine gegebene Anzahl von Tagen von `date` abzuziehen: ```js function getDateAgo(date, days) { @@ -7,9 +7,9 @@ function getDateAgo(date, days) { } ``` -...But the function should not change `date`. That's an important thing, because the outer code which gives us the date does not expect it to change. +...Aber die Funktion sollte `date` nicht ändern. Das ist eine wichtige Sache, denn der äußere Code, der uns das Datum bereitstellt, erwartet nicht, dass es sich ändert. -To implement it let's clone the date, like this: +Um das zu implementieren, sollten wir das Datum klonen, so wie hier: ```js run demo function getDateAgo(date, days) { @@ -21,7 +21,7 @@ function getDateAgo(date, days) { let date = new Date(2015, 0, 2); -alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) -alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) -alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) +alert( getDateAgo(date, 1) ); // 1, (1. Jan 2015) +alert( getDateAgo(date, 2) ); // 31, (31. Dez 2014) +alert( getDateAgo(date, 365) ); // 2, (2. Jan 2014) ``` diff --git a/1-js/05-data-types/11-date/4-get-date-ago/task.md b/1-js/05-data-types/11-date/4-get-date-ago/task.md index 058d39c7e..6a190c8b4 100644 --- a/1-js/05-data-types/11-date/4-get-date-ago/task.md +++ b/1-js/05-data-types/11-date/4-get-date-ago/task.md @@ -2,20 +2,20 @@ importance: 4 --- -# Which day of month was many days ago? +# Welcher Tag des Monats war vor vielen Tagen? -Create a function `getDateAgo(date, days)` to return the day of month `days` ago from the `date`. +Erstelle eine Funktion `getDateAgo(date, days)`, um den Tag des Monats vor `days` Tagen ab dem Datum `date` zu ermitteln. -For instance, if today is 20th, then `getDateAgo(new Date(), 1)` should be 19th and `getDateAgo(new Date(), 2)` should be 18th. +Beispiel: Wenn heute der 20. ist, dann sollte `getDateAgo(new Date(), 1)` den 19. zurückgeben und `getDateAgo(new Date(), 2)` den 18. -Should work reliably for `days=365` or more: +Die Funktion sollte auch zuverlässig für `days=365` oder mehr funktionieren: ```js let date = new Date(2015, 0, 2); -alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) -alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) -alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) +alert( getDateAgo(date, 1) ); // 1, (1. Jan 2015) +alert( getDateAgo(date, 2) ); // 31, (31. Dez 2014) +alert( getDateAgo(date, 365) ); // 2, (2. Jan 2014) ``` -P.S. The function should not modify the given `date`. +P.S. Die Funktion sollte das übergebene `date` nicht verändern. diff --git a/1-js/05-data-types/11-date/5-last-day-of-month/solution.md b/1-js/05-data-types/11-date/5-last-day-of-month/solution.md index 4f642536e..463fd6b3e 100644 --- a/1-js/05-data-types/11-date/5-last-day-of-month/solution.md +++ b/1-js/05-data-types/11-date/5-last-day-of-month/solution.md @@ -1,4 +1,4 @@ -Let's create a date using the next month, but pass zero as the day: +Lass uns ein Datum erstellen, indem wir den nächsten Monat nehmen, aber als Tag eine Null übergeben: ```js run demo function getLastDayOfMonth(year, month) { let date = new Date(year, month + 1, 0); @@ -10,4 +10,4 @@ alert( getLastDayOfMonth(2012, 1) ); // 29 alert( getLastDayOfMonth(2013, 1) ); // 28 ``` -Normally, dates start from 1, but technically we can pass any number, the date will autoadjust itself. So when we pass 0, then it means "one day before 1st day of the month", in other words: "the last day of the previous month". +Normalerweise beginnen Daten ab der Zahl 1, technisch gesehen können wir aber jede Zahl übergeben, das Datum wird sich selbst automatisch anpassen. Also, wenn wir 0 übergeben, dann bedeutet das "ein Tag vor dem ersten Tag des Monats", anders ausgedrückt: "der letzte Tag des vorherigen Monats". diff --git a/1-js/05-data-types/11-date/5-last-day-of-month/task.md b/1-js/05-data-types/11-date/5-last-day-of-month/task.md index 10dfb7a7a..c2d148510 100644 --- a/1-js/05-data-types/11-date/5-last-day-of-month/task.md +++ b/1-js/05-data-types/11-date/5-last-day-of-month/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Last day of month? +# Letzter Tag des Monats? -Write a function `getLastDayOfMonth(year, month)` that returns the last day of month. Sometimes it is 30th, 31st or even 28/29th for Feb. +Schreibe eine Funktion `getLastDayOfMonth(year, month)`, die den letzten Tag des Monats zurückgibt. Manchmal ist es der 30., der 31. oder sogar der 28./29. für Februar. -Parameters: +Parameter: -- `year` -- four-digits year, for instance 2012. -- `month` -- month, from 0 to 11. +- `year` -- vierstelliges Jahr, zum Beispiel 2012. +- `month` -- Monat, von 0 bis 11. -For instance, `getLastDayOfMonth(2012, 1) = 29` (leap year, Feb). +Zum Beispiel `getLastDayOfMonth(2012, 1) = 29` (Schaltjahr, Feb). diff --git a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md index 8f8e52b68..4a1f9ac00 100644 --- a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md +++ b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md @@ -1,22 +1,22 @@ -To get the number of seconds, we can generate a date using the current day and time 00:00:00, then substract it from "now". +Um die Anzahl der Sekunden zu ermitteln, können wir ein Datum unter Verwendung des aktuellen Tages und der Uhrzeit 00:00:00 generieren und dieses von "jetzt" subtrahieren. -The difference is the number of milliseconds from the beginning of the day, that we should divide by 1000 to get seconds: +Die Differenz ist die Anzahl der Millisekunden seit Beginn des Tages, die wir durch 1000 teilen sollten, um Sekunden zu erhalten: ```js run function getSecondsToday() { let now = new Date(); - // create an object using the current day/month/year + // erstelle ein Objekt mit dem aktuellen Tag/Monat/Jahr let today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - let diff = now - today; // ms difference - return Math.round(diff / 1000); // make seconds + let diff = now - today; // ms Differenz + return Math.round(diff / 1000); // mache Sekunden } alert( getSecondsToday() ); ``` -An alternative solution would be to get hours/minutes/seconds and convert them to seconds: +Eine alternative Lösung wäre, Stunden/Minuten/Sekunden zu ermitteln und diese in Sekunden umzurechnen: ```js run function getSecondsToday() { diff --git a/1-js/05-data-types/11-date/6-get-seconds-today/task.md b/1-js/05-data-types/11-date/6-get-seconds-today/task.md index 456790928..beee601d6 100644 --- a/1-js/05-data-types/11-date/6-get-seconds-today/task.md +++ b/1-js/05-data-types/11-date/6-get-seconds-today/task.md @@ -2,14 +2,14 @@ importance: 5 --- -# How many seconds have passed today? +# Wie viele Sekunden sind heute bereits vergangen? -Write a function `getSecondsToday()` that returns the number of seconds from the beginning of today. +Schreibe eine Funktion `getSecondsToday()`, die die Anzahl der Sekunden seit Beginn des heutigen Tages zurückgibt. -For instance, if now were `10:00 am`, and there was no daylight savings shift, then: +Wenn es zum Beispiel jetzt `10:00 Uhr vormittags` wäre und es keine Umstellung auf Sommerzeit gäbe, dann: ```js getSecondsToday() == 36000 // (3600 * 10) ``` -The function should work in any day. That is, it should not have a hard-coded value of "today". +Die Funktion sollte an jedem Tag funktionieren. Das heißt, sie sollte keinen fest einprogrammierten Wert von "heute" haben. diff --git a/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md index c337d1199..cf1270cae 100644 --- a/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md +++ b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md @@ -1,20 +1,20 @@ -To get the number of milliseconds till tomorrow, we can from "tomorrow 00:00:00" substract the current date. +Um die Anzahl der Millisekunden bis zum morgigen Tag zu berechnen, können wir vom "morgigen Tag, 00:00:00" das aktuelle Datum subtrahieren. -First, we generate that "tomorrow", and then do it: +Zuerst erzeugen wir das "morgige" Datum, und dann tun wir Folgendes: ```js run function getSecondsToTomorrow() { let now = new Date(); - // tomorrow date + // morgiges Datum let tomorrow = new Date(now.getFullYear(), now.getMonth(), *!*now.getDate()+1*/!*); - let diff = tomorrow - now; // difference in ms - return Math.round(diff / 1000); // convert to seconds + let diff = tomorrow - now; // Differenz in ms + return Math.round(diff / 1000); // umrechnen in Sekunden } ``` -Alternative solution: +Alternative Lösung: ```js run function getSecondsToTomorrow() { @@ -29,4 +29,4 @@ function getSecondsToTomorrow() { } ``` -Please note that many countries have Daylight Savings Time (DST), so there may be days with 23 or 25 hours. We may want to treat such days separately. +Bitte beachte, dass viele Länder die Sommerzeit (DST) einführen, daher kann es Tage mit 23 oder 25 Stunden geben. Wir möchten solche Tage möglicherweise gesondert behandeln. diff --git a/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md index e05903026..af94a3384 100644 --- a/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md +++ b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md @@ -2,14 +2,14 @@ importance: 5 --- -# How many seconds till tomorrow? +# Wie viele Sekunden bis morgen? -Create a function `getSecondsToTomorrow()` that returns the number of seconds till tomorrow. +Erstelle eine Funktion `getSecondsToTomorrow()`, die die Anzahl der Sekunden bis morgen zurückgibt. -For instance, if now is `23:00`, then: +Zum Beispiel, wenn es jetzt `23:00` ist, dann: ```js getSecondsToTomorrow() == 3600 ``` -P.S. The function should work at any day, the "today" is not hardcoded. +P.S. Die Funktion sollte an jedem Tag funktionieren, das "heute" ist nicht fest einprogrammiert. diff --git a/1-js/05-data-types/11-date/8-format-date-relative/solution.md b/1-js/05-data-types/11-date/8-format-date-relative/solution.md index 372485685..1a1d150e9 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/solution.md +++ b/1-js/05-data-types/11-date/8-format-date-relative/solution.md @@ -1,26 +1,26 @@ -To get the time from `date` till now -- let's substract the dates. +Um die Zeit von `date` bis jetzt zu bekommen -- lass uns die Daten subtrahieren. ```js run demo function formatDate(date) { - let diff = new Date() - date; // the difference in milliseconds + let diff = new Date() - date; // der Unterschied in Millisekunden - if (diff < 1000) { // less than 1 second + if (diff < 1000) { // weniger als 1 Sekunde return 'right now'; } - let sec = Math.floor(diff / 1000); // convert diff to seconds + let sec = Math.floor(diff / 1000); // diff in Sekunden umwandeln if (sec < 60) { return sec + ' sec. ago'; } - let min = Math.floor(diff / 60000); // convert diff to minutes + let min = Math.floor(diff / 60000); // diff in Minuten umwandeln if (min < 60) { return min + ' min. ago'; } - // format the date - // add leading zeroes to single-digit day/month/hours/minutes + // das Datum formatieren + // führende Nullen zu einstelligen Tagen/Monaten/Stunden/Minuten hinzufügen let d = date; d = [ '0' + d.getDate(), @@ -28,9 +28,9 @@ function formatDate(date) { '' + d.getFullYear(), '0' + d.getHours(), '0' + d.getMinutes() - ].map(component => component.slice(-2)); // take last 2 digits of every component + ].map(component => component.slice(-2)); // die letzten 2 Ziffern jeder Komponente nehmen - // join the components into date + // die Komponenten zu einem Datum zusammenfügen return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); } @@ -40,11 +40,11 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" -// yesterday's date like 31.12.2016 20:00 +// das gestrige Datum wie 31.12.2016 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` -Alternative solution: +Alternative Lösung: ```js run function formatDate(date) { @@ -58,7 +58,7 @@ function formatDate(date) { let diffMin = diffSec / 60; let diffHour = diffMin / 60; - // formatting + // Formatierung year = year.toString().slice(-2); month = month < 10 ? '0' + month : month; dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; @@ -76,3 +76,4 @@ function formatDate(date) { } } ``` + diff --git a/1-js/05-data-types/11-date/8-format-date-relative/task.md b/1-js/05-data-types/11-date/8-format-date-relative/task.md index 9651b305f..ee9290f1a 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/task.md +++ b/1-js/05-data-types/11-date/8-format-date-relative/task.md @@ -2,16 +2,16 @@ importance: 4 --- -# Format the relative date +# Das relative Datum formatieren -Write a function `formatDate(date)` that should format `date` as follows: +Schreibe eine Funktion `formatDate(date)`, die das Datum `date` wie folgt formatieren soll: -- If since `date` passed less than 1 second, then `"right now"`. -- Otherwise, if since `date` passed less than 1 minute, then `"n sec. ago"`. -- Otherwise, if less than an hour, then `"m min. ago"`. -- Otherwise, the full date in the format `"DD.MM.YY HH:mm"`. That is: `"day.month.year hours:minutes"`, all in 2-digit format, e.g. `31.12.16 10:00`. +- Wenn seit `date` weniger als 1 Sekunde vergangen ist, dann `"right now"`. +- Ansonsten, wenn seit `date` weniger als 1 Minute vergangen ist, dann `"n sec. ago"`. +- Ansonsten, wenn weniger als eine Stunde vergangen ist, dann `"m min. ago"`. +- Andernfalls das vollständige Datum im Format `"TT.MM.JJ HH:mm"`. Das heißt: `"Tag.Monat.Jahr Stunden:Minuten"`, alles im 2-Ziffern-Format, z.B. `31.12.16 10:00`. -For instance: +Zum Beispiel: ```js alert( formatDate(new Date(new Date - 1)) ); // "right now" @@ -20,6 +20,6 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" -// yesterday's date like 31.12.16 20:00 +// Das Datum von gestern wie 31.12.16 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index a2de63ae4..2e5ac559c 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -1,141 +1,141 @@ -# Date and time +# Datum und Uhrzeit -Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date, time and provides methods for date/time management. +Lass uns ein neues eingebautes Objekt kennenlernen: [Date](mdn:js/Date). Es speichert das Datum, die Uhrzeit und bietet Methoden zur Verwaltung von Datum/Uhrzeit. -For instance, we can use it to store creation/modification times, to measure time, or just to print out the current date. +Zum Beispiel können wir es verwenden, um Erstellungs-/Änderungszeiten zu speichern, die Zeit zu messen oder einfach nur das aktuelle Datum auszugeben. -## Creation +## Erstellung -To create a new `Date` object call `new Date()` with one of the following arguments: +Um ein neues `Date`-Objekt zu erstellen, rufe `new Date()` mit einem der folgenden Argumente auf: `new Date()` -: Without arguments -- create a `Date` object for the current date and time: +: Ohne Argumente – erstellt ein `Date`-Objekt für das aktuelle Datum und die aktuelle Uhrzeit: ```js run let now = new Date(); - alert( now ); // shows current date/time + alert( now ); // zeigt aktuelles Datum/Uhrzeit ``` `new Date(milliseconds)` -: Create a `Date` object with the time equal to number of milliseconds (1/1000 of a second) passed after the Jan 1st of 1970 UTC+0. +: Erstelle ein `Date`-Objekt mit der Zeit gleich der Anzahl von Millisekunden (1/1000 einer Sekunde), die seit dem 1. Januar 1970 UTC+0 vergangen sind. ```js run - // 0 means 01.01.1970 UTC+0 + // 0 bedeutet 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); - // now add 24 hours, get 02.01.1970 UTC+0 + // nun 24 Stunden hinzufügen, ergibt 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 ); ``` - An integer number representing the number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*. + Eine ganze Zahl, die die Anzahl der Millisekunden darstellt, die seit Beginn des Jahres 1970 vergangen sind, wird als *Zeitstempel* bezeichnet. - It's a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp using the `date.getTime()` method (see below). + Es ist eine leichtgewichtige numerische Darstellung eines Datums. Wir können immer ein Datum aus einem Zeitstempel erstellen mit `new Date(timestamp)` und das bestehende `Date`-Objekt in einen Zeitstempel umwandeln mit der Methode `date.getTime()` (siehe unten). - Dates before 01.01.1970 have negative timestamps, e.g.: + Daten vor dem 01.01.1970 haben negative Zeitstempel, zum Beispiel: ```js run - // 31 Dec 1969 + // 31. Dez 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 ); ``` `new Date(datestring)` -: If there is a single argument, and it's a string, then it is parsed automatically. The algorithm is the same as `Date.parse` uses, we'll cover it later. +: Wenn es ein einzelnes Argument gibt und es ist ein String, dann wird es automatisch geparst. Der Algorithmus ist derselbe, den `Date.parse` verwendet, was wir später behandeln werden. ```js run let date = new Date("2017-01-26"); alert(date); - // The time is not set, so it's assumed to be midnight GMT and - // is adjusted according to the timezone the code is run in - // So the result could be - // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) - // or - // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) + // Die Zeit ist nicht gesetzt, daher wird angenommen, dass es Mitternacht GMT ist + // und sie wird entsprechend der Zeitzone angepasst, in der der Code ausgeführt wird + // Das Ergebnis könnte also sein + // Do., 26. Jan. 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) + // oder + // Mi., 25. Jan. 2017 16:00:00 GMT-0800 (Pacific Standard Time) ``` `new Date(year, month, date, hours, minutes, seconds, ms)` -: Create the date with the given components in the local time zone. Only the first two arguments are obligatory. +: Erstelle das Datum mit den gegebenen Komponenten in der lokalen Zeitzone. Nur die ersten beiden Argumente sind obligatorisch. - - The `year` must have 4 digits: `2013` is okay, `98` is not. - - The `month` count starts with `0` (Jan), up to `11` (Dec). - - The `date` parameter is actually the day of month, if absent then `1` is assumed. - - If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`. + - `year` sollte 4 Ziffern haben. Aus Kompatibilitätsgründen werden auch 2 Ziffern akzeptiert und als `19xx` betrachtet, zum Beispiel entspricht `98` dem Wert `1998`, aber die Verwendung von 4 Ziffern wird dringend empfohlen. + - `month` zählt ab `0` (Januar), bis `11` (Dezember). + - Der `date`-Parameter ist tatsächlich der Tag des Monats, wenn weggelassen, dann wird `1` angenommen. + - Wenn `hours/minutes/seconds/ms` weggelassen werden, wird angenommen, dass diese `0` sind. - For instance: + Beispiel: ```js - new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00 - new Date(2011, 0, 1); // the same, hours etc are 0 by default + new Date(2011, 0, 1, 0, 0, 0, 0); // 1. Jan 2011, 00:00:00 + new Date(2011, 0, 1); // das Gleiche, Stunden usw. sind standardmäßig 0 ``` - The minimal precision is 1 ms (1/1000 sec): + Die größtmögliche Genauigkeit ist 1 ms (1/1000 Sekunde): ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567 ``` -## Access date components +## Zugriff auf Datumskomponenten -There are methods to access the year, month and so on from the `Date` object: +Es gibt Methoden, um auf das Jahr, den Monat usw. des `Date`-Objekts zuzugreifen: [getFullYear()](mdn:js/Date/getFullYear) -: Get the year (4 digits) +: Zugriff auf das Jahr (4 Ziffern) [getMonth()](mdn:js/Date/getMonth) -: Get the month, **from 0 to 11**. +: Zugriff auf den Monat, **von 0 bis 11**. [getDate()](mdn:js/Date/getDate) -: Get the day of month, from 1 to 31, the name of the method does look a little bit strange. +: Zugriff auf den Tag des Monats, von 1 bis 31, der Name der Methode wirkt etwas seltsam. [getHours()](mdn:js/Date/getHours), [getMinutes()](mdn:js/Date/getMinutes), [getSeconds()](mdn:js/Date/getSeconds), [getMilliseconds()](mdn:js/Date/getMilliseconds) -: Get the corresponding time components. +: Zugriff auf die entsprechenden Zeitkomponenten. -```warn header="Not `getYear()`, but `getFullYear()`" -Many JavaScript engines implement a non-standard method `getYear()`. This method is deprecated. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for the year. +```warn header="Nicht `getYear()`, sondern `getFullYear()`" +Viele JavaScript-Engines implementieren eine nicht-standardisierte Methode `getYear()`. Diese Methode ist veraltet. Sie gibt manchmal ein 2-stelliges Jahr zurück. Bitte verwende sie niemals. Es gibt `getFullYear()` für das Jahr. ``` -Additionally, we can get a day of week: +Zusätzlich können wir den Wochentag bekommen: [getDay()](mdn:js/Date/getDay) -: Get the day of week, from `0` (Sunday) to `6` (Saturday). The first day is always Sunday, in some countries that's not so, but can't be changed. +: Zugriff auf den Wochentag, von `0` (Sonntag) bis `6` (Samstag). Der erste Tag ist immer Sonntag, in einigen Ländern ist das nicht so, aber das kann nicht geändert werden. -**All the methods above return the components relative to the local time zone.** +**Alle oben genannten Methoden geben die Komponenten relativ zur lokalen Zeitzone zurück.** -There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay). Just insert the `"UTC"` right after `"get"`. +Es gibt auch ihre UTC-Gegenstücke, die Tag, Monat, Jahr usw. für die Zeitzone UTC+0 zurückgeben: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay). Füge einfach nach `"get"` das `"UTC"` ein. -If your local time zone is shifted relative to UTC, then the code below shows different hours: +Wenn deine lokale Zeitzone relativ zu UTC verschoben ist, dann zeigt der untenstehende Code unterschiedliche Stunden an: ```js run -// current date +// aktuelles Datum let date = new Date(); -// the hour in your current time zone +// die Stunde in Ihrer aktuellen Zeitzone alert( date.getHours() ); -// the hour in UTC+0 time zone (London time without daylight savings) +// die Stunde in der Zeitzone UTC+0 (Londoner Zeit ohne Sommerzeit) alert( date.getUTCHours() ); ``` -Besides the given methods, there are two special ones that do not have a UTC-variant: +Neben den gegebenen Methoden gibt es zwei spezielle, die keine UTC-Variante haben: [getTime()](mdn:js/Date/getTime) -: Returns the timestamp for the date -- a number of milliseconds passed from the January 1st of 1970 UTC+0. +: Gibt den Zeitstempel für das Datum zurück -- eine Anzahl von Millisekunden seit dem 1. Januar 1970 UTC+0. [getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) -: Returns the difference between UTC and the local time zone, in minutes: +: Gibt den Unterschied zwischen UTC und der lokalen Zeitzone in Minuten zurück: ```js run - // if you are in timezone UTC-1, outputs 60 - // if you are in timezone UTC+3, outputs -180 + // Wenn du dich in der Zeitzone UTC-1 befindest, gibt es 60 aus + // Wenn du dich in der Zeitzone UTC+3 befindest, gibt es -180 aus alert( new Date().getTimezoneOffset() ); ``` -## Setting date components +## Datumskomponenten einstellen -The following methods allow to set date/time components: +Die folgenden Methoden erlauben es, Datum/Zeit-Komponenten festzulegen: - [`setFullYear(year, [month], [date])`](mdn:js/Date/setFullYear) - [`setMonth(month, [date])`](mdn:js/Date/setMonth) @@ -144,38 +144,38 @@ The following methods allow to set date/time components: - [`setMinutes(min, [sec], [ms])`](mdn:js/Date/setMinutes) - [`setSeconds(sec, [ms])`](mdn:js/Date/setSeconds) - [`setMilliseconds(ms)`](mdn:js/Date/setMilliseconds) -- [`setTime(milliseconds)`](mdn:js/Date/setTime) (sets the whole date by milliseconds since 01.01.1970 UTC) +- [`setTime(milliseconds)`](mdn:js/Date/setTime) (setzt das ganze Datum auf Millisekunden seit dem 01.01.1970 UTC) -Every one of them except `setTime()` has a UTC-variant, for instance: `setUTCHours()`. +Jede dieser Methoden außer `setTime()` hat ein UTC-Gegenstück, wie zum Beispiel: `setUTCHours()`. -As we can see, some methods can set multiple components at once, for example `setHours`. The components that are not mentioned are not modified. +Wie wir sehen können, können einige Methoden gleichzeitig mehrere Komponenten anpassen, zum Beispiel `setHours`. Die Komponenten, die nicht erwähnt werden, werden nicht geändert. -For instance: +Beispiel: ```js run -let today = new Date(); +let heute = new Date(); -today.setHours(0); -alert(today); // still today, but the hour is changed to 0 +heute.setHours(0); +alert(heute); // immer noch heute, aber die Stunde ist auf 0 geändert -today.setHours(0, 0, 0, 0); -alert(today); // still today, now 00:00:00 sharp. +heute.setHours(0, 0, 0, 0); +alert(heute); // immer noch heute, jetzt 00:00:00 genau. ``` -## Autocorrection +## Autokorrektur -The *autocorrection* is a very handy feature of `Date` objects. We can set out-of-range values, and it will auto-adjust itself. +Die *Autokorrektur* ist eine sehr praktische Funktion von `Date`-Objekten. Wir können Werte außerhalb des Bereichs verwenden, und sie wird diese automatisch anpassen. -For instance: +Beispiel: ```js run -let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? -alert(date); // ...is 1st Feb 2013! +let date = new Date(2013, 0, *!*32*/!*); // 32. Jan 2013 ?!? +alert(date); // ...ist der 1. Feb 2013! ``` -Out-of-range date components are distributed automatically. +Datumskomponenten außerhalb des Bereichs werden automatisch verteilt. -Let's say we need to increase the date "28 Feb 2016" by 2 days. It may be "2 Mar" or "1 Mar" in case of a leap-year. We don't need to think about it. Just add 2 days. The `Date` object will do the rest: +Angenommen, wir müssen das Datum "28. Feb 2016" um 2 Tage erhöhen. Es könnte der "2. März" oder der "1. März" im Falle eines Schaltjahres sein. Wir müssen nicht darüber nachdenken. Einfach 2 Tage hinzufügen. Das `Date`-Objekt erledigt den Rest: ```js run let date = new Date(2016, 1, 28); @@ -183,112 +183,112 @@ let date = new Date(2016, 1, 28); date.setDate(date.getDate() + 2); */!* -alert( date ); // 1 Mar 2016 +alert( date ); // 1. Mär 2016 ``` -That feature is often used to get the date after the given period of time. For instance, let's get the date for "70 seconds after now": +Diese Funktion wird oft verwendet, um das Datum nach einer bestimmten Zeitperiode zu erhalten. Zum Beispiel, lass uns das Datum für "70 Sekunden ab jetzt" erhalten: ```js run let date = new Date(); date.setSeconds(date.getSeconds() + 70); -alert( date ); // shows the correct date +alert( date ); // zeigt das korrekte Datum ``` -We can also set zero or even negative values. For example: +Wir können auch null oder sogar negative Werte verwenden. Zum Beispiel: ```js run -let date = new Date(2016, 0, 2); // 2 Jan 2016 +let date = new Date(2016, 0, 2); // 2. Jan 2016 -date.setDate(1); // set day 1 of month +date.setDate(1); // lege Tag 1 des Monats fest alert( date ); -date.setDate(0); // min day is 1, so the last day of the previous month is assumed -alert( date ); // 31 Dec 2015 +date.setDate(0); // minimaler Tag ist 1, also wird der letzte Tag des Vormonats angenommen +alert( date ); // 31. Dez 2015 ``` -## Date to number, date diff +## Datum in Zahl, Datumsunterschied -When a `Date` object is converted to number, it becomes the timestamp same as `date.getTime()`: +Wenn ein `Date`-Objekt in eine Zahl umgewandelt wird, wird es zum Zeitstempel genauso wie `date.getTime()`: ```js run let date = new Date(); -alert(+date); // the number of milliseconds, same as date.getTime() +alert(+date); // die Anzahl der Millisekunden, genau wie date.getTime() ``` -The important side effect: dates can be subtracted, the result is their difference in ms. +Die wichtige Seiteneffekt: Daten können subtrahiert werden, das Ergebnis ist ihr Unterschied in Millisekunden. -That can be used for time measurements: +Das kann für Zeitmessungen verwendet werden: ```js run -let start = new Date(); // start measuring time +let start = new Date(); // beginnen mit der Zeitmessung -// do the job +// verrichte Arbeit for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } -let end = new Date(); // end measuring time +let end = new Date(); // beenden der Zeitmessung -alert( `The loop took ${end - start} ms` ); +alert( `Die Schleife dauerte ${end - start} ms` ); ``` ## Date.now() -If we only want to measure time, we don't need the `Date` object. +Wenn wir nur die Zeit messen wollen, benötigen wir kein `Date`-Objekt. -There's a special method `Date.now()` that returns the current timestamp. +Es gibt eine spezielle Methode `Date.now()`, die den aktuellen Zeitstempel zurückgibt. -It is semantically equivalent to `new Date().getTime()`, but it doesn't create an intermediate `Date` object. So it's faster and doesn't put pressure on garbage collection. +Sie ist semantisch gleichbedeutend mit `new Date().getTime()`, aber sie erstellt kein Zwischen-`Date`-Objekt. Daher ist sie schneller und belastet die Garbage-Kollektion nicht. -It is used mostly for convenience or when performance matters, like in games in JavaScript or other specialized applications. +Sie wird meistens aus Bequemlichkeit oder wenn Performance wichtig ist verwendet, wie in JavaScript-Spielen oder anderen spezialisierten Anwendungen. -So this is probably better: +Also ist das vermutlich besser: ```js run *!* -let start = Date.now(); // milliseconds count from 1 Jan 1970 +let start = Date.now(); // Millisekunden seit 1. Jan 1970 */!* -// do the job +// verrichte Arbeit for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } *!* -let end = Date.now(); // done +let end = Date.now(); // fertig */!* -alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates +alert( `Die Schleife dauerte ${end - start} ms` ); // subtrahiere Zahlen, nicht Daten ``` ## Benchmarking -If we want a reliable benchmark of CPU-hungry function, we should be careful. +Wenn wir einen verlässlichen Benchmark für eine CPU-intensive Funktion haben wollen, sollten wir vorsichtig sein. -For instance, let's measure two functions that calculate the difference between two dates: which one is faster? +Zum Beispiel, lass uns zwei Funktionen messen, die den Unterschied zwischen zwei Daten berechnen: welche ist schneller? -Such performance measurements are often called "benchmarks". +Solche Leistungsmessungen werden oft "Benchmarks" genannt. ```js -// we have date1 and date2, which function faster returns their difference in ms? +// wir haben date1 und date2, welche Funktion gibt schneller deren Unterschied in ms zurück? function diffSubtract(date1, date2) { return date2 - date1; } -// or +// oder function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } ``` -These two do exactly the same thing, but one of them uses an explicit `date.getTime()` to get the date in ms, and the other one relies on a date-to-number transform. Their result is always the same. +Diese beiden machen genau dasselbe, aber eine benutzt ein explizites `date.getTime()`, um das Datum in Millisekunden zu erhalten, und die andere verlässt sich auf eine Datum-zu-Zahl-Umwandlung. Das Ergebnis ist immer das Gleiche. -So, which one is faster? +Also, welche ist schneller? -The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it at least 100000 times. +Die erste Idee könnte sein, sie viele Male hintereinander auszuführen und den Zeitunterschied zu messen. In unserem Fall sind die Funktionen sehr einfach, also müssen wir das mindestens 100000 Mal machen. -Let's measure: +Lass uns messen: ```js run function diffSubtract(date1, date2) { @@ -308,23 +308,23 @@ function bench(f) { return Date.now() - start; } -alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' ); -alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' ); +alert( 'Zeit für diffSubtract: ' + bench(diffSubtract) + 'ms' ); +alert( 'Zeit für diffGetTime: ' + bench(diffGetTime) + 'ms' ); ``` -Wow! Using `getTime()` is so much faster! That's because there's no type conversion, it is much easier for engines to optimize. +Wow! Die Verwendung von `getTime()` ist so viel schneller! Das liegt daran, dass keine Typumwandlung stattfindet und es für die Engines viel einfacher ist, zu optimieren. -Okay, we have something. But that's not a good benchmark yet. +Okay, wir haben etwas. Aber das ist noch kein guter Benchmark. -Imagine that at the time of running `bench(diffSubtract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` that work has finished. +Stell dir vor, dass zur Zeit der Ausführung von `bench(diffSubtract)` die CPU parallel etwas anderes gemacht hat und Ressourcen verbraucht wurden. Und zum Zeitpunkt der Ausführung von `bench(diffGetTime)` war diese Arbeit beendet. -A pretty real scenario for a modern multi-process OS. +Ein ziemlich reales Szenario für ein modernes Multi-Prozess-Betriebssystem. -As a result, the first benchmark will have less CPU resources than the second. That may lead to wrong results. +Als Ergebnis könnte der erste Benchmark weniger CPU-Ressourcen als der zweite haben. Das könnte zu falschen Ergebnissen führen. -**For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.** +**Für zuverlässigeres Benchmarking sollte das gesamte Paket an Benchmarks mehrmals wiederholt werden.** -For example, like this: +Zum Beispiel so: ```js run function diffSubtract(date1, date2) { @@ -348,86 +348,86 @@ let time1 = 0; let time2 = 0; *!* -// run bench(upperSlice) and bench(upperLoop) each 10 times alternating +// führe bench(diffSubtract) und bench(diffGetTime) jeweils 10-mal abwechselnd aus for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } */!* -alert( 'Total time for diffSubtract: ' + time1 ); -alert( 'Total time for diffGetTime: ' + time2 ); +alert( 'Gesamtzeit für diffSubtract: ' + time1 ); +alert( 'Gesamtzeit für diffGetTime: ' + time2 ); ``` -Modern JavaScript engines start applying advanced optimizations only to "hot code" that executes many times (no need to optimize rarely executed things). So, in the example above, first executions are not well-optimized. We may want to add a heat-up run: +Moderne JavaScript-Engines beginnen damit, fortgeschrittene Optimierungen nur auf "heißen Code" anzuwenden, der viele Male ausgeführt wird (keine Notwendigkeit, selten ausgeführte Dinge zu optimieren). Deshalb sind in dem oben genannten Beispiel die ersten Ausführungen nicht gut optimiert. Wir möchten vielleicht einen Aufwärm-Durchlauf hinzufügen: ```js -// added for "heating up" prior to the main loop +// Hinzugefügt zum "Aufwärmen" vor der Hauptschleife bench(diffSubtract); bench(diffGetTime); -// now benchmark +// jetzt Benchmarking for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } ``` -```warn header="Be careful doing microbenchmarking" -Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. +```warn header="Sei vorsichtig bei Mikrobenchmarking" +Moderne JavaScript-Engines führen viele Optimierungen durch. Du kannst die Ergebnisse von "künstlichen Tests" im Vergleich zur "normalen Nutzung" verändern, insbesondere wenn wir etwas sehr Kleines benchmarken, wie die Funktionsweise eines Operators oder einer eingebauten Funktion. Wenn du also die Performance ernsthaft verstehen möchtest, dann studiere bitte, wie die JavaScript-Engine funktioniert. Und dann wirst du wahrscheinlich überhaupt keine Mikrobenchmarks brauchen. -The great pack of articles about V8 can be found at . +Eine großartige Sammlung von Artikeln über V8 findest Du unter . ``` -## Date.parse from a string +## Date.parse aus einem String -The method [Date.parse(str)](mdn:js/Date/parse) can read a date from a string. +Die Methode [Date.parse(str)](mdn:js/Date/parse) kann ein Datum aus einem String auslesen. -The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where: +Das String-Format sollte sein: `YYYY-MM-DDTHH:mm:ss.sssZ`, wobei: -- `YYYY-MM-DD` -- is the date: year-month-day. -- The character `"T"` is used as the delimiter. -- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds. -- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` that would mean UTC+0. +- `YYYY-MM-DD` -- das Datum ist: Jahr-Monat-Tag. +- Das Zeichen `"T"` wird als Trennzeichen verwendet. +- `HH:mm:ss.sss` -- ist die Zeit: Stunden, Minuten, Sekunden und Millisekunden. +- Der optionale Teil `'Z'` kennzeichnet die Zeitzone im Format `+-hh:mm`. Ein einzelner Buchstabe `Z` würde UTC+0 bedeuten. -Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`. +Auch kürzere Varianten sind möglich, wie `YYYY-MM-DD` oder `YYYY-MM` oder sogar `YYYY`. -The call to `Date.parse(str)` parses the string in the given format and returns the timestamp (number of milliseconds from 1 Jan 1970 UTC+0). If the format is invalid, returns `NaN`. +Der Aufruf von `Date.parse(str)` parst den String im gegebenen Format und gibt den Zeitstempel zurück (Anzahl der Millisekunden ab dem 1. Januar 1970 UTC+0). Wenn das Format ungültig ist, gibt er `NaN` zurück. -For instance: +Zum Beispiel: ```js run let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); -alert(ms); // 1327611110417 (timestamp) +alert(ms); // 1327611110417 (Zeitstempel) ``` -We can instantly create a `new Date` object from the timestamp: +Wir können sofort ein `new Date` Objekt aus dem Zeitstempel erstellen: ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); -alert(date); +alert(date); ``` -## Summary +## Zusammenfassung -- Date and time in JavaScript are represented with the [Date](mdn:js/Date) object. We can't create "only date" or "only time": `Date` objects always carry both. -- Months are counted from zero (yes, January is a zero month). -- Days of week in `getDay()` are also counted from zero (that's Sunday). -- `Date` auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours. -- Dates can be subtracted, giving their difference in milliseconds. That's because a `Date` becomes the timestamp when converted to a number. -- Use `Date.now()` to get the current timestamp fast. +- Datum und Uhrzeit in JavaScript werden mit dem [Date](mdn:js/Date)-Objekt dargestellt. Wir können nicht "nur Datum" oder "nur Zeit" erstellen: `Date`-Objekte tragen immer beides. +- Monate werden von Null gezählt (ja, Januar ist der Null-Monat). +- Wochentage in `getDay()` werden auch ab Null gezählt (das ist Sonntag). +- `Date` korrigiert sich selbst, wenn Komponenten außerhalb des gültigen Bereichs gesetzt werden. Gut für das Hinzufügen/Subtrahieren von Tagen/Monaten/Stunden. +- Daten können subtrahiert werden, was ihre Differenz in Millisekunden ergibt. Das liegt daran, dass ein `Date`, wenn es in eine Zahl umgewandelt wird, zum Zeitstempel wird. +- Verwende `Date.now()`, um schnell den aktuellen Zeitstempel zu erhalten. -Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds. +Beachte, dass anders als in vielen anderen Systemen, Zeitstempel in JavaScript in Millisekunden und nicht in Sekunden sind. -Sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point): +Manchmal benötigen wir genauere Zeitmessungen. JavaScript selbst hat keine Möglichkeit, Zeit in Mikrosekunden zu messen (1 Millionstel einer Sekunde), aber die meisten Umgebungen bieten dies an. Zum Beispiel hat der Browser [performance.now()](mdn:api/Performance/now), was die Anzahl der Millisekunden seit dem Start des Seitenladens mit Mikrosekunden-Präzision ergibt (3 Ziffern nach dem Punkt): ```js run -alert(`Loading started ${performance.now()}ms ago`); -// Something like: "Loading started 34731.26000000001ms ago" -// .26 is microseconds (260 microseconds) -// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct +alert(`Das Laden hat vor ${performance.now()}ms begonnen`); +// Etwas wie: "Das Laden hat vor 34731.26000000001ms begonnen" +// .26 sind Mikrosekunden (260 Mikrosekunden) +// mehr als 3 Ziffern nach dem Dezimalpunkt sind Präzisionsfehler, nur die ersten 3 sind korrekt ``` -Node.js has `microtime` module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in `Date`. +Node.js hat das `microtime` Modul und andere Weisen. Technisch gesehen, ermöglicht fast jedes Gerät und jede Umgebung eine genauere Präzision, sie ist nur nicht in `Date`. diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index a5f2974af..133ffb353 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -27,7 +27,7 @@ Luckily, there's no need to write the code to handle all this. The task has been ## JSON.stringify -The [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](http://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. +The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. JavaScript provides methods: @@ -41,7 +41,7 @@ let student = { age: 30, isAdmin: false, courses: ['html', 'css', 'js'], - wife: null + spouse: null }; *!* @@ -58,7 +58,7 @@ alert(json); "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], - "wife": null + "spouse": null } */ */!* @@ -105,7 +105,7 @@ JSON is data-only language-independent specification, so some JavaScript-specifi Namely: - Function properties (methods). -- Symbolic properties. +- Symbolic keys and values. - Properties that store `undefined`. ```js run @@ -276,6 +276,7 @@ name: John name: Alice place: [object Object] number: 23 +occupiedBy: [object Object] */ ``` @@ -328,6 +329,8 @@ alert(JSON.stringify(user, null, 2)); */ ``` +The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces. + The `space` parameter is used solely for logging and nice-output purposes. ## Custom "toJSON" @@ -402,7 +405,7 @@ To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/p The syntax: ```js -let value = JSON.parse(str, [reviver]); +let value = JSON.parse(str[, reviver]); ``` str @@ -448,7 +451,7 @@ let json = `{ Besides, JSON does not support comments. Adding a comment to JSON makes it invalid. -There's another format named [JSON5](http://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm. diff --git a/1-js/05-data-types/12-json/json-meetup.svg b/1-js/05-data-types/12-json/json-meetup.svg index e44674904..3fa32a261 100644 --- a/1-js/05-data-types/12-json/json-meetup.svg +++ b/1-js/05-data-types/12-json/json-meetup.svg @@ -1 +1 @@ -number: 23title: "Conference"...placeoccupiedByparticipants \ No newline at end of file +number: 23title: "Conference"...placeoccupiedByparticipants \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 3a281ef3f..11667f940 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. -P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md index 59040a2b7..09e511db5 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md @@ -1,4 +1,4 @@ -By definition, a factorial is `n!` can be written as `n * (n-1)!`. +By definition, a factorial `n!` can be written as `n * (n-1)!`. In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`. diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg index 59e6a52c4..1313837f3 100644 --- a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg @@ -1 +1 @@ -fib ( 5 )fib(4)fib(3)fib(3)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1) \ No newline at end of file +fib ( 5 )fib(4)fib(3)fib(3)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1) \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md index 4357ff208..0eb76ea1c 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md @@ -33,7 +33,7 @@ printReverseList(list); # Using a loop -The loop variant is also a little bit more complicated then the direct output. +The loop variant is also a little bit more complicated than the direct output. There is no way to get the last value in our `list`. We also can't "go back". diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 320de62f0..5ae894474 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -61,7 +61,7 @@ When `pow(x, n)` is called, the execution splits into two branches: if n==1 = x / pow(x, n) = - \ + \ else = x * pow(x, n - 1) ``` @@ -132,7 +132,7 @@ We can sketch it as: -That's when the function starts to execute. The condition `n == 1` is false, so the flow continues into the second branch of `if`: +That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`: ```js run function pow(x, n) { @@ -188,7 +188,7 @@ The new current execution context is on top (and bold), and previous remembered When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped. ```smart -Here in the picture we use the word "line", as our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. +Here in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. So it would be more precise to say that the execution resumes "immediately after the subcall". ``` @@ -285,7 +285,7 @@ The iterative `pow` uses a single context changing `i` and `result` in the proce **Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.** -...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used. @@ -535,7 +535,7 @@ Terms: list = { value, next -> list } ``` - Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches. + Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they have branches and every branch can have other branches. Recursive functions can be used to walk them as we've seen in the `sumSalary` example. diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-0.svg b/1-js/06-advanced-functions/01-recursion/linked-list-0.svg index f18c6ffb6..5d23c7a4c 100644 --- a/1-js/06-advanced-functions/01-recursion/linked-list-0.svg +++ b/1-js/06-advanced-functions/01-recursion/linked-list-0.svg @@ -1 +1 @@ -value1nextvalue"new item"nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file +value1nextvalue"new item"nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg index edec23912..2f37449c4 100644 --- a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg +++ b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg @@ -1 +1 @@ -value"new item"nextvalue1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file +value"new item"nextvalue1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-split.svg b/1-js/06-advanced-functions/01-recursion/linked-list-split.svg index cba81e648..6c3072130 100644 --- a/1-js/06-advanced-functions/01-recursion/linked-list-split.svg +++ b/1-js/06-advanced-functions/01-recursion/linked-list-split.svg @@ -1 +1 @@ -value1nextvalue2nextvalue3nextvalue4nextnullnullsecondListlist \ No newline at end of file +value1nextvalue2nextvalue3nextvalue4nextnullnullsecondListlist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list.svg b/1-js/06-advanced-functions/01-recursion/linked-list.svg index 63a070fd2..c02744f39 100644 --- a/1-js/06-advanced-functions/01-recursion/linked-list.svg +++ b/1-js/06-advanced-functions/01-recursion/linked-list.svg @@ -1 +1 @@ -value1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file +value1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow.svg b/1-js/06-advanced-functions/01-recursion/recursion-pow.svg index 8bd4a43fe..2b970a04a 100644 --- a/1-js/06-advanced-functions/01-recursion/recursion-pow.svg +++ b/1-js/06-advanced-functions/01-recursion/recursion-pow.svg @@ -1 +1 @@ -pow(x,n)xx * pow(x, n-1)n == 1 ?YesNorecursive call until n==1 \ No newline at end of file +pow(x,n)xx * pow(x, n-1)n == 1 ?YesNorecursive call until n==1 \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg b/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg index f47f0668b..61f32dbc0 100644 --- a/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg +++ b/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index 1f139d7a4..dbdfbd6c0 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -23,7 +23,7 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` -There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted. +There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`. The rest of the parameters can be included in the function definition by using three dots `...` followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array". @@ -225,7 +225,7 @@ But there's a subtle difference between `Array.from(obj)` and `[...obj]`: So, for the task of turning something into an array, `Array.from` tends to be more universal. -## Get a new copy of an array/object +## Copy an array/object Remember when we talked about `Object.assign()` [in the past](info:object-copy#cloning-and-merging-object-assign)? @@ -233,8 +233,11 @@ It is possible to do the same thing with the spread syntax. ```js run let arr = [1, 2, 3]; + +*!* let arrCopy = [...arr]; // spread the array into a list of parameters // then put the result into a new array +*/!* // do the arrays have the same contents? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true @@ -252,8 +255,11 @@ Note that it is possible to do the same thing to make a copy of an object: ```js run let obj = { a: 1, b: 2, c: 3 }; + +*!* let objCopy = { ...obj }; // spread the object into a list of parameters // then return the result in a new object +*/!* // do the objects have the same contents? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true @@ -267,7 +273,7 @@ alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} ``` -This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj);` or for an array `let arrCopy = Object.assign([], arr);` so we prefer to use it whenever we can. +This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj)` or for an array `let arrCopy = Object.assign([], arr)` so we prefer to use it whenever we can. ## Summary diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg new file mode 100644 index 000000000..f8c7bd6ac --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg @@ -0,0 +1 @@ +outer<empty>makeArmy() LexicalEnvironmentwhile iteration LexicalEnvironment<empty><empty><empty>i: 10 \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg new file mode 100644 index 000000000..7611d0ef8 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg @@ -0,0 +1 @@ +outermakeArmy() LexicalEnvironmentfor iteration LexicalEnvironmenti: 0i: 1i: 2i: 10... \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg new file mode 100644 index 000000000..d83ecbe76 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg @@ -0,0 +1 @@ +outerj: 0j: 1j: 2j: 10...makeArmy() LexicalEnvironmentwhile iteration LexicalEnvironment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg deleted file mode 100644 index c0a312ec7..000000000 --- a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg +++ /dev/null @@ -1 +0,0 @@ -outeri: 0i: 1i: 2i: 10...makeArmy() LexicalEnvironmentfor block LexicalEnvironment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md index 0fb0b4a49..9d99aa717 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -1,12 +1,12 @@ -Let's examine what's done inside `makeArmy`, and the solution will become obvious. +Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious. 1. It creates an empty array `shooters`: ```js let shooters = []; ``` -2. Fills it in the loop via `shooters.push(function...)`. +2. Fills it with functions via `shooters.push(function)` in the loop. Every element is a function, so the resulting array looks like this: @@ -26,95 +26,104 @@ Let's examine what's done inside `makeArmy`, and the solution will become obviou ``` 3. The array is returned from the function. + + Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it. + + Now why do all such functions show the same value, `10`? + + That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment. + + Then, what will be the value of `i`? + + If we look at the source: + + ```js + function makeArmy() { + ... + let i = 0; + while (i < 10) { + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); // add function to the array + i++; + } + ... + } + ``` + + We can see that all `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`). + + As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`. + + ![](lexenv-makearmy-empty.svg) + + As you can see above, on each iteration of a `while {...}` block, a new lexical environment is created. So, to fix this, we can copy the value of `i` into a variable within the `while {...}` block, like this: + + ```js run + function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + *!* + let j = i; + */!* + let shooter = function() { // shooter function + alert( *!*j*/!* ); // should show its number + }; + shooters.push(shooter); + i++; + } + + return shooters; + } + + let army = makeArmy(); + + // Now the code works correctly + army[0](); // 0 + army[5](); // 5 + ``` + + Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration. + + The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration: + + ![](lexenv-makearmy-while-fixed.svg) + + Such a problem could also be avoided if we used `for` in the beginning, like this: + + ```js run demo + function makeArmy() { + + let shooters = []; + + *!* + for(let i = 0; i < 10; i++) { + */!* + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); + } + + return shooters; + } + + let army = makeArmy(); + + army[0](); // 0 + army[5](); // 5 + ``` + + That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration. + + ![](lexenv-makearmy-for-fixed.svg) -Then, later, the call to `army[5]()` will get the element `army[5]` from the array (it will be a function) and call it. - -Now why all such functions show the same? - -That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment. - -What will be the value of `i`? - -If we look at the source: - -```js -function makeArmy() { - ... - let i = 0; - while (i < 10) { - let shooter = function() { // shooter function - alert( i ); // should show its number - }; - ... - } - ... -} -``` - -...We can see that it lives in the lexical environment associated with the current `makeArmy()` run. But when `army[5]()` is called, `makeArmy` has already finished its job, and `i` has the last value: `10` (the end of `while`). - -As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`. - -We can fix it by moving the variable definition into the loop: - -```js run demo -function makeArmy() { - - let shooters = []; - -*!* - for(let i = 0; i < 10; i++) { -*/!* - let shooter = function() { // shooter function - alert( i ); // should show its number - }; - shooters.push(shooter); - } - - return shooters; -} - -let army = makeArmy(); - -army[0](); // 0 -army[5](); // 5 -``` - -Now it works correctly, because every time the code block in `for (let i=0...) {...}` is executed, a new Lexical Environment is created for it, with the corresponding variable `i`. - -So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. That's why now it works. - -![](lexenv-makearmy.svg) - -Here we rewrote `while` into `for`. - -Another trick could be possible, let's see it for better understanding of the subject: - -```js run -function makeArmy() { - let shooters = []; - - let i = 0; - while (i < 10) { -*!* - let j = i; -*/!* - let shooter = function() { // shooter function - alert( *!*j*/!* ); // should show its number - }; - shooters.push(shooter); - i++; - } - - return shooters; -} - -let army = makeArmy(); +Now, as you've put so much effort into reading this, and the final recipe is so simple - just use `for`, you may wonder -- was it worth that? -army[0](); // 0 -army[5](); // 5 -``` +Well, if you could easily answer the question, you wouldn't read the solution. So, hopefully this task must have helped you to understand things a bit better. -The `while` loop, just like `for`, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a `shooter`. +Besides, there are indeed cases when one prefers `while` to `for`, and other scenarios, where such problems are real. -We copy `let j = i`. This makes a loop body local `j` and copies the value of `i` to it. Primitives are copied "by value", so we actually get a complete independent copy of `i`, belonging to the current loop iteration. diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/task.md b/1-js/06-advanced-functions/03-closure/10-make-army/task.md index 93e64f2d0..f50c7dc20 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/task.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md @@ -14,22 +14,28 @@ function makeArmy() { let i = 0; while (i < 10) { - let shooter = function() { // shooter function - alert( i ); // should show its number + let shooter = function() { // create a shooter function, + alert( i ); // that should show its number }; - shooters.push(shooter); + shooters.push(shooter); // and add it to the array i++; } + // ...and return the array of shooters return shooters; } let army = makeArmy(); -army[0](); // the shooter number 0 shows 10 -army[5](); // and number 5 also outputs 10... -// ... all shooters show 10 instead of their 0, 1, 2, 3... +*!* +// all shooters show 10 instead of their numbers 0, 1, 2, 3... +army[0](); // 10 from the shooter number 0 +army[1](); // 10 from the shooter number 1 +army[2](); // 10 ...and so on. +*/!* ``` -Why do all of the shooters show the same value? Fix the code so that they work as intended. +Why do all of the shooters show the same value? + +Fix the code so that they work as intended. diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg index 5cdf7f1a4..8dfd8bd63 100644 --- a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg @@ -1 +1 @@ -makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file +makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md index d02c53b99..4e386eec5 100644 --- a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md +++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md @@ -1,4 +1,6 @@ +importance: 5 +--- # Function in if Look at the code. What will be the result of the call at the last line? diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md index 404bae80b..b16b35290 100644 --- a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -15,7 +15,7 @@ function func() { func(); ``` -In this example we can observe the peculiar difference between a "non-existing" and "unitialized" variable. +In this example we can observe the peculiar difference between a "non-existing" and "uninitialized" variable. As you may have read in the article [](info:closure), a variable starts in the "uninitialized" state from the moment when the execution enters a code block (or a function). And it stays uninitalized until the corresponding `let` statement. @@ -27,7 +27,7 @@ The code above demonstrates it. function func() { *!* // the local variable x is known to the engine from the beginning of the function, - // but "unitialized" (unusable) until let ("dead zone") + // but "uninitialized" (unusable) until let ("dead zone") // hence the error */!* diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js index e3c335e03..802f28c4d 100644 --- a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js @@ -23,7 +23,7 @@ describe("byField", function(){ { name: "John", age: 20, surname: "Johnson"}, ]; let ageSortedAnswer = users.sort(byField("age")); - assert.deepEqual(ageSortedKey, ageSortedKey); + assert.deepEqual(ageSortedKey, ageSortedAnswer); }); it("sorts users by surname", function(){ diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 90d1d735e..cb43a7968 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,11 +1,15 @@ -# Variable scope +# Variable scope, closure -JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, passed as an argument to another function and called from a totally different place of code later. +JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at any moment, passed as an argument to another function, and then called from a totally different place of code later. -We already know that a function can access variables outside of it. +We already know that a function can access variables outside of it ("outer" variables). -Now let's expand our knowledge to include more complex scenarios. +But what happens if outer variables change since a function is created? Will the function get newer values or the old ones? + +And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place? + +Let's expand our knowledge to understand these scenarios and more complex ones. ```smart header="We'll talk about `let/const` variables here" In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the modern ones), and `var` (the remnant of the past). @@ -142,7 +146,7 @@ Despite being simple, slightly modified variants of that code have practical use How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? -Undestanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. +Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. ## Lexical Environment @@ -310,7 +314,7 @@ When on an interview, a frontend developer gets a question about "what's a closu Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable. -...But if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. +However, if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive. @@ -329,7 +333,7 @@ let g = f(); // g.[[Environment]] stores a reference to the Lexical Environment // of the corresponding f() call ``` -Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below: +Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them: ```js function f() { @@ -367,7 +371,7 @@ As we've seen, in theory while a function is alive, all outer variables are also But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's obvious from the code that an outer variable is not used -- it is removed. -**An important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging.** +**An important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging.** Try running the example below in Chrome with the Developer Tools open. @@ -409,6 +413,6 @@ let g = f(); g(); ``` -This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it. +This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it. -That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You always can check for it by running the examples on this page. +That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page. diff --git a/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg index 97f76e569..3ef787875 100644 --- a/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg +++ b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg @@ -1 +1 @@ -outernullexecution startphrase: <uninitialized> say: function... \ No newline at end of file +outernullexecution startphrase: <uninitialized> say: function... \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg index b9060bc8a..f78441712 100644 --- a/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg @@ -1 +1 @@ -null[[Environment]]makeCounter: function counter: undefinedcount: 0outerouter \ No newline at end of file +null[[Environment]]makeCounter: function counter: undefinedcount: 0outerouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg index 3e4206ca6..3950a8faa 100644 --- a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg @@ -1 +1 @@ -count: 1<empty>nullouterouteroutermakeCounter: function counter: functionmodified here \ No newline at end of file +count: 1<empty>nullouterouteroutermakeCounter: function counter: functionmodified here \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg index e1bb8cc8f..24315bf21 100644 --- a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg @@ -1 +1 @@ -count: 0<empty>nullouterouteroutermakeCounter: function counter: function \ No newline at end of file +count: 0<empty>nullouterouteroutermakeCounter: function counter: function \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg index 2a1c4a729..2ca06455a 100644 --- a/1-js/06-advanced-functions/03-closure/closure-makecounter.svg +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg @@ -1 +1 @@ -makeCounter: function counter: undefinedcount: 0nullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() callouterouter \ No newline at end of file +makeCounter: function counter: undefinedcount: 0nullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() callouterouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg index 741c05448..b9bb12fff 100644 --- a/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg +++ b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg @@ -1 +1 @@ -phrase: "Bye"phrase: "Hello"phrase: undefinedphrase: <uninitialized>outernullexecution start \ No newline at end of file +phrase: "Bye"phrase: "Hello"phrase: undefinedphrase: <uninitialized>outernullexecution start \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-if.svg b/1-js/06-advanced-functions/03-closure/lexenv-if.svg index b644fe154..3d4d6d7cc 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-if.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-if.svg @@ -1 +1 @@ -phrase: "Hello"outerouternulluser: "John" \ No newline at end of file +phrase: "Hello"outerouternulluser: "John" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg index a14df7092..f15e77a88 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg @@ -1 +1 @@ -makeCounter: function[[Environment]]outernull \ No newline at end of file +makeCounter: function[[Environment]]outernull \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg index 66e5200f8..f37488537 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg @@ -1 +1 @@ -makeCounter: functioncounter: undefinedcount: 0outerouternullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() call \ No newline at end of file +makeCounter: functioncounter: undefinedcount: 0outerouternullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() call \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg index 28c526c4f..54f1d97ba 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg @@ -1 +1 @@ -makeCounter: functioncounter: undefinedcount: 0outerouternull[[Environment]] \ No newline at end of file +makeCounter: functioncounter: undefinedcount: 0outerouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg index acc1e8fb9..fb60a785f 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg @@ -1 +1 @@ -makeCounter: functioncounter: functioncount: 0outerouternull[[Environment]] \ No newline at end of file +makeCounter: functioncounter: functioncount: 0outerouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg index cf91c331d..79c440da7 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg @@ -1 +1 @@ -makeCounter: functioncounter: functioncount: 0<empty>outerouterouternull[[Environment]] \ No newline at end of file +makeCounter: functioncounter: functioncount: 0<empty>outerouterouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg index def542ceb..06d5b5060 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg @@ -1 +1 @@ -makeCounter: functioncounter: functioncount: 1outerouternull[[Environment]]modified here \ No newline at end of file +makeCounter: functioncounter: functioncount: 1outerouternull[[Environment]]modified here \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg index 2e956cbbf..b6e576f0c 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg @@ -1 +1 @@ -phrase: "Bye"phrase: "Hello"phrase: undefined<empty>outernullexecution start \ No newline at end of file +phrase: "Bye"phrase: "Hello"phrase: undefined<empty>outernullexecution start \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg index d0f4a8e64..1942a7e37 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg @@ -1 +1 @@ -say: function phrase: "Hello"say: functionouternullexecution start \ No newline at end of file +say: function phrase: "Hello"say: functionouternullexecution start \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg index 9620f0485..7bddc2230 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg @@ -1 +1 @@ -phrase: "Hello"outernullLexical Environment \ No newline at end of file +phrase: "Hello"outernullLexical Environment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg index ff0486ede..79501a5b0 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg @@ -1 +1 @@ -say: function phrase: "Hello"name: "John"outerouternull \ No newline at end of file +say: function phrase: "Hello"name: "John"outerouternull \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg index abd77fff9..dea6ac467 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg @@ -1 +1 @@ -say: function phrase: "Hello"name: "John"outerouternullLexical Environment of the call \ No newline at end of file +say: function phrase: "Hello"name: "John"outerouternullLexical Environment of the call \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-search-order.svg b/1-js/06-advanced-functions/03-closure/lexical-search-order.svg index 89a9d110a..d9884ec4f 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-search-order.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-search-order.svg @@ -1 +1 @@ -123 \ No newline at end of file +123 \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg index 674437196..f1f1d3b1d 100644 --- a/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg +++ b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg @@ -1 +1 @@ -functionUser(name){this.sayHi=function(){alert(name);};}letuser=newUser("John");user.sayHi(); \ No newline at end of file +functionUser(name){this.sayHi=function(){alert(name);};}letuser=newUser("John");user.sayHi(); \ No newline at end of file diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 1e8bb5bae..28d7a76ec 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -4,7 +4,7 @@ ```smart header="This article is for understanding old scripts" The information in this article is useful for understanding old scripts. -That's not how we write a new code. +That's not how we write new code. ``` In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: @@ -28,7 +28,7 @@ On the other hand, it's important to understand differences when migrating old s ## "var" has no block scope -Variables, declared with `var`, are either function-wide or global. They are visible through blocks. +Variables, declared with `var`, are either function-scoped or global-scoped. They are visible through blocks. For instance: @@ -52,19 +52,21 @@ if (true) { } *!* -alert(test); // Error: test is not defined +alert(test); // ReferenceError: test is not defined */!* ``` The same thing for loops: `var` cannot be block- or loop-local: -```js +```js run for (var i = 0; i < 10; i++) { + var one = 1; // ... } *!* -alert(i); // 10, "i" is visible after loop, it's a global variable +alert(i); // 10, "i" is visible after loop, it's a global variable +alert(one); // 1, "one" is visible after loop, it's a global variable */!* ``` @@ -80,10 +82,10 @@ function sayHi() { } sayHi(); -alert(phrase); // Error: phrase is not defined (Check the Developer Console) +alert(phrase); // ReferenceError: phrase is not defined ``` -As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that. +As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript, blocks had no Lexical Environments, and `var` is a remnant of that. ## "var" tolerates redeclarations @@ -168,7 +170,7 @@ That's best demonstrated with an example: ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Hello"; @@ -203,11 +205,11 @@ sayHi(); Because all `var` declarations are processed at the function start, we can reference them at any place. But variables are undefined until the assignments. -In both examples above `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. +In both examples above, `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. -### IIFE +## IIFE -As in the past there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE). +In the past, as there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE). That's not something we should use nowadays, but you can find them in old scripts. @@ -216,22 +218,22 @@ An IIFE looks like this: ```js run (function() { - let message = "Hello"; + var message = "Hello"; alert(message); // Hello })(); ``` -Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables. +Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables. -The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: +The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript engine encounters `"function"` in the main code, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: ```js run -// Try to declare and immediately call a function -function() { // <-- Error: Function statements require a function name +// Tries to declare and immediately call a function +function() { // <-- SyntaxError: Function statements require a function name - let message = "Hello"; + var message = "Hello"; alert(message); // Hello @@ -254,11 +256,11 @@ There exist other ways besides parentheses to tell JavaScript that we mean a Fun ```js run // Ways to create IIFE -(function() { +*!*(*/!*function() { alert("Parentheses around the function"); }*!*)*/!*(); -(function() { +*!*(*/!*function() { alert("Parentheses around the whole thing"); }()*!*)*/!*; @@ -277,7 +279,7 @@ In all the above cases we declare a Function Expression and run it immediately. There are two main differences of `var` compared to `let/const`: -1. `var` variables have no block scope, they are visible minimum at the function level. +1. `var` variables have no block scope, their visibility is scoped to current function, or global, if declared outside function. 2. `var` declarations are processed at function start (script start for globals). There's one more very minor difference related to the global object, that we'll cover in the next chapter. diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md index 3d195a978..0369cee72 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -1,87 +1,108 @@ -# Global object +# Globales Objekt -The global object provides variables and functions that are available anywhere. By default, those that are built into the language or the environment. +Das globale Objekt stellt Variablen und Funktionen bereit, die überall verfügbar sind. Standardmäßig diejenigen, die in die Sprache oder die Umgebung integriert sind. -In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name. +In einem Browser heißt es `window`, für Node.js ist es `global`, für andere Umgebungen kann es einen anderen Namen haben. -Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled. +<<<<<<< HEAD +Vor kurzem wurde der Sprache `globalThis` als standardisierter Name für ein globales Objekt hinzugefügt, das in allen Umgebungen unterstützt werden soll. In einigen Browsern, insbesondere Nicht-Chromium Edge, wird `globalThis` noch nicht unterstützt, kann aber leicht mit Polyfills umgangen werden. +======= +Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 -We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead. +Wir verwenden hier `window`, vorausgesetzt, unsere Umgebung ist ein Browser. Wenn dein Skript in anderen Umgebungen ausgeführt werden kann, ist es besser, stattdessen `globalThis` zu verwenden. -All properties of the global object can be accessed directly: +Auf alle Eigenschaften des globalen Objekts kann direkt zugegriffen werden: ```js run -alert("Hello"); -// is the same as -window.alert("Hello"); +alert("Hallo"); +// ist das selbe wie +window.alert("Halllo"); ``` -In a browser, global functions and variables declared with `var` (not `let/const`!) become the property of the global object: +In einem Browser werden mit `var` (nicht `let/const`!) deklarierte globale Funktionen und Variablen Eigentum des globalen Objekts: ```js run untrusted refresh var gVar = 5; -alert(window.gVar); // 5 (became a property of the global object) +alert(window.gVar); // 5 (wurde Eigentum des globalen Objekts) ``` -Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen. +<<<<<<< HEAD +<<<<<<< HEAD +Bitte nicht darauf verlassen! Dieses Verhalten existiert aus Kompatibilitätsgründen. Moderne Skripte verwenden [JavaScript-Module](info:modules), wo so etwas nicht passiert. +======= +The same effect have function declarations (statements with `function` keyword in the main code flow, not function expressions). +======= +Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions). +>>>>>>> d694e895efe89922a109702085b6ca1efeffea10 -If we used `let` instead, such thing wouldn't happen: +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 + +Wenn wir stattdessen `let` verwenden, würde so etwas nicht passieren: ```js run untrusted refresh let gLet = 5; -alert(window.gLet); // undefined (doesn't become a property of the global object) +alert(window.gLet); // undefined (wird nicht Eigentum des globalen Objekts) ``` -If a value is so important that you'd like to make it available globally, write it directly as a property: +Wenn ein Wert so wichtig ist, dass du ihn global verfügbar machen möchtest, schreibe ihn direkt als Eigenschaft: ```js run *!* -// make current user information global, to let all scripts access it +// aktuelle Benutzerinformationen global machen, damit alle Skripte darauf zugreifen können window.currentUser = { name: "John" }; */!* -// somewhere else in code +// woanders im Code alert(currentUser.name); // John -// or, if we have a local variable with the name "currentUser" -// get it from window explicitly (safe!) +// oder, wenn wir eine lokale Variable mit dem Namen "currentUser" haben +// Holen Sie es explizit aus dem Fenster (sicher!) alert(window.currentUser.name); // John ``` -That said, using global variables is generally discouraged. There should be as few global variables as possible. The code design where a function gets "input" variables and produces certain "outcome" is clearer, less prone to errors and easier to test than if it uses outer or global variables. +Von der Verwendung globaler Variablen wird jedoch im Allgemeinen abgeraten. Es sollten möglichst wenige globale Variablen vorhanden sein. Der Codeentwurf, bei dem eine Funktion "Eingabe"-Variablen erhält und bestimmte "Ergebnisse" erzeugt, ist klarer, weniger fehleranfällig und einfacher zu testen, als wenn du äußere oder globale Variablen verwendest. -## Using for polyfills +## Verwendung für Polyfills -We use the global object to test for support of modern language features. +Wir verwenden das globale Objekt, um die Unterstützung moderner Sprachfunktionen zu testen. -For instance, test if a built-in `Promise` object exists (it doesn't in really old browsers): +Testen Sie zum Beispiel, ob ein eingebautes `Promise`-Objekt existiert (in wirklich alten Browsern nicht): ```js run if (!window.Promise) { - alert("Your browser is really old!"); + alert("Dein Browser ist wirklich alt!"); } ``` -If there's none (say, we're in an old browser), we can create "polyfills": add functions that are not supported by the environment, but exist in the modern standard. +Wenn es keine gibt (sagen wir, wir befinden uns in einem alten Browser), können wir "Polyfills" erstellen: Funktionen hinzufügen, die von der Umgebung nicht unterstützt werden, aber im modernen Standard vorhanden sind. ```js run if (!window.Promise) { - window.Promise = ... // custom implementation of the modern language feature + window.Promise = ... // benutzerdefinierte Implementierung der modernen Sprachfunktion } ``` -## Summary +## Zusammenfassung -- The global object holds variables that should be available everywhere. +- Das globale Objekt enthält Variablen, die überall verfügbar sein sollten. - That includes JavaScript built-ins, such as `Array` and environment-specific values, such as `window.innerHeight` -- the window height in the browser. -- The global object has a universal name `globalThis`. + Dazu gehören JavaScript-Einbauten wie "Array" und umgebungsspezifische Werte wie `window.innerHeight` -- die Fensterhöhe im Browser. +- Das globale Objekt hat einen universellen Namen `globalThis`. - ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). As `globalThis` is a recent proposal, it's not supported in non-Chromium Edge (but can be polyfilled). +<<<<<<< HEAD + ...Aber häufiger wird mit umgebungsspezifischen Namen der "alten Schule" wie `window` (Browser) und `global` (Node.js) bezeichnet. Da `globalThis` ein neuer Vorschlag ist, wird es in Nicht-Chromium Edge nicht unterstützt (kann aber mit Polyfills umgangen werden). +- Wir sollten Werte im globalen Objekt nur speichern, wenn sie wirklich global für unser Projekt sind. Und halten Sie ihre Anzahl auf ein Minimum. +- Im Browser werden globale Funktionen und Variablen, die mit `var` deklariert sind, zu einer Eigenschaft des globalen Objekts, es sei denn, wir verwenden [modules](info:modules). +- Um unseren Code zukunftssicher und verständlicher zu machen, sollten wir direkt auf die Eigenschaften des globalen Objekts zugreifen, als `window.x`. +======= + ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). - We should store values in the global object only if they're truly global for our project. And keep their number at minimum. - In-browser, unless we're using [modules](info:modules), global functions and variables declared with `var` become a property of the global object. - To make our code future-proof and easier to understand, we should access properties of the global object directly, as `window.x`. +>>>>>>> a82915575863d33db6b892087975f84dea6cb425 diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md index 5c9326912..e97039f72 100644 --- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md @@ -5,7 +5,7 @@ Now the code: -```js run +```js demo run function sum(a) { let currentSum = a; @@ -52,4 +52,4 @@ function f(b) { } ``` -This `f` will be used in the next call, again return itself, so many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. +This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index ed848c0c5..c84f4e52f 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -326,7 +326,7 @@ welcome(); // Hello, Guest (nested call works) Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function. -The outer code still has it's variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally. +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to can call itself reliably. ```smart header="There's no such thing for Function Declaration" The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name. @@ -347,7 +347,7 @@ If the function is declared as a Function Expression (not in the main code flow) Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature. -They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. +They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. So, a function can do a useful job by itself and also carry a bunch of other functionality in properties. diff --git a/1-js/06-advanced-functions/07-new-function/article.md b/1-js/06-advanced-functions/07-new-function/article.md index 3214ba376..ffe264a4e 100644 --- a/1-js/06-advanced-functions/07-new-function/article.md +++ b/1-js/06-advanced-functions/07-new-function/article.md @@ -92,7 +92,7 @@ What if it could access the outer variables? The problem is that before JavaScript is published to production, it's compressed using a *minifier* -- a special program that shrinks code by removing extra comments, spaces and -- what's important, renames local variables into shorter ones. -For instance, if a function has `let userName`, minifier replaces it `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace. +For instance, if a function has `let userName`, minifier replaces it with `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace. So if `new Function` had access to outer variables, it would be unable to find renamed `userName`. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index 95fddea65..f96959988 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,7 @@ Usually, that's a function. For historical reasons, a string of code can be pass : The delay before run, in milliseconds (1000 ms = 1 second), by default 0. `arg1`, `arg2`... -: Arguments for the function (not supported in IE9-) +: Arguments for the function For instance, this code calls `sayHi()` after one second: @@ -102,7 +102,7 @@ As we can see from `alert` output, in a browser the timer identifier is a number Again, there is no universal specification for these methods, so that's fine. -For browsers, timers are described in the [timers section](https://www.w3.org/TR/html5/webappapis.html#timers) of HTML5 standard. +For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard. ## setInterval @@ -129,7 +129,7 @@ setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); ```smart header="Time goes on while `alert` is shown" In most browsers, including Chrome and Firefox the internal timer continues "ticking" while showing `alert/confirm/prompt`. -So if you run the code above and don't dismiss the `alert` window for some time, then in the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. +So if you run the code above and don't dismiss the `alert` window for some time, then the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. ``` ## Nested setTimeout @@ -232,7 +232,7 @@ setTimeout(function() {...}, 100); For `setInterval` the function stays in memory until `clearInterval` is called. -There's a side-effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small. +There's a side effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small. ```` ## Zero delay setTimeout @@ -256,7 +256,7 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter . ````smart header="Zero delay is in fact not zero (in a browser)" -In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". +In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see: @@ -281,7 +281,7 @@ The similar thing happens if we use `setInterval` instead of `setTimeout`: `setI That limitation comes from ancient times and many scripts rely on it, so it exists for historical reasons. -For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html) for Node.js. So this note is browser-specific. +For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific. ```` ## Summary @@ -290,13 +290,13 @@ For server-side JavaScript, that limitation does not exist, and there exist othe - To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`. - Nested `setTimeout` calls are a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely. - Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete". -- The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. +- The browser limits the minimal delay for five or more nested calls of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. Please note that all scheduling methods do not *guarantee* the exact delay. For example, the in-browser timer may slow down for a lot of reasons: - The CPU is overloaded. - The browser tab is in the background mode. -- The laptop is on battery. +- The laptop is on battery saving mode. All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg index 9a214c548..bce7d6a84 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg @@ -1 +1 @@ -func(1)func(2)func(3)100200300 \ No newline at end of file +func(1)func(2)func(3)100200300 \ No newline at end of file diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg index a559f6163..d6d233b2b 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg @@ -1 +1 @@ -func(1)func(2)func(3)100100 \ No newline at end of file +func(1)func(2)func(3)100100 \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg index 5896a5fa4..bd82f0245 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg @@ -1 +1 @@ -200ms1500ms1000ms0cf(a)f(b)f(c)500mstimecalls: after 1000ms \ No newline at end of file +200ms1500ms1000ms0cf(a)f(b)f(c)500mstimecalls: after 1000ms \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 550bf52da..5b0fcc5f8 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -6,6 +6,8 @@ importance: 5 The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. +In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). + For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. @@ -14,18 +16,17 @@ Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there a ...And it will get the arguments of the very last call, other calls are ignored. -Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce): +Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)): ```js let f = _.debounce(alert, 1000); -f("a"); +f("a"); setTimeout( () => f("b"), 200); -setTimeout( () => f("c"), 500); +setTimeout( () => f("c"), 500); // debounced function waits 1000ms after the last call and then runs: alert("c") ``` - Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished. There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result. @@ -43,9 +44,8 @@ See? The second input calls the debounced function, so its content is processed So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else. - It waits the given time after the last call, and then runs its function, that can process the result. The task is to implement `debounce` decorator. -Hint: that's just a few lines if you think about it :) \ No newline at end of file +Hint: that's just a few lines if you think about it :) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index cf851f771..6950664be 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -12,11 +12,10 @@ function throttle(func, ms) { savedThis = this; return; } + isThrottled = true; func.apply(this, arguments); // (1) - isThrottled = true; - setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 6cb664fdb..cbd473196 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -6,12 +6,14 @@ importance: 5 Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. -When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. +When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. -The difference with debounce is that it's completely different decorator: +Compared to the debounce decorator, the behavior is completely different: - `debounce` runs the function once after the "cooldown" period. Good for processing the final result. - `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often. +In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. + Let's check the real-life application to better understand that requirement and to see where it comes from. **For instance, we want to track mouse movements.** diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index d0dda4df1..c5d785493 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -36,11 +36,11 @@ function cachingDecorator(func) { slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached -alert( "Again: " + slow(1) ); // the same +alert( slow(1) ); // slow(1) is cached and the result returned +alert( "Again: " + slow(1) ); // slow(1) result returned from cache -alert( slow(2) ); // slow(2) is cached -alert( "Again: " + slow(2) ); // the same as the previous line +alert( slow(2) ); // slow(2) is cached and the result returned +alert( "Again: " + slow(2) ); // slow(2) result returned from cache ``` In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. @@ -301,18 +301,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l So these two calls are almost equivalent: ```js -func.call(context, ...args); // pass an array as list with spread syntax -func.apply(context, args); // is same as using call +func.call(context, ...args); +func.apply(context, args); ``` -There's only a subtle difference: +They perform the same call of `func` with given context and arguments. + +There's only a subtle difference regarding `args`: - The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. - The `apply` accepts only *array-like* `args`. -So, where we expect an iterable, `call` works, and where we expect an array-like, `apply` works. - -And for objects that are both iterable and array-like, like a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. Passing all arguments along with the context to another function is called *call forwarding*. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg index 258fcfdfc..9b63cb982 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg +++ b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg @@ -1 +1 @@ -wrapperaround the function \ No newline at end of file +wrapperaround the function \ No newline at end of file diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index 403107ca6..4a381c0b4 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,5 +1,5 @@ -The error occurs because `ask` gets functions `loginOk/loginFail` without the object. +The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object. When it calls them, they naturally assume `this=undefined`. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 787c7d68e..6d65e7dd1 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -167,7 +167,7 @@ sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! // even if the value of user changes within 1 second -// sayHi uses the pre-bound value +// sayHi uses the pre-bound value which is reference to the old user object user = { sayHi() { alert("Another user in setTimeout!"); } }; @@ -187,8 +187,8 @@ let user = { let say = user.say.bind(user); -say("Hello"); // Hello, John ("Hello" argument is passed to say) -say("Bye"); // Bye, John ("Bye" is passed to say) +say("Hello"); // Hello, John! ("Hello" argument is passed to say) +say("Bye"); // Bye, John! ("Bye" is passed to say) ``` ````smart header="Convenience method: `bindAll`" @@ -202,7 +202,7 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash. +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash. ```` ## Partial functions @@ -247,7 +247,7 @@ The call to `mul.bind(null, 2)` creates a new function `double` that passes call That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. -Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. +Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. The function `triple` in the code below triples the value: diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md index f5caeaece..8730277ad 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -52,7 +52,7 @@ let group = { *!* this.students.forEach(function(student) { // Error: Cannot read property 'title' of undefined - alert(this.title + ': ' + student) + alert(this.title + ': ' + student); }); */!* } @@ -87,7 +87,7 @@ For instance, `defer(f, ms)` gets a function and returns a wrapper around it tha ```js run function defer(f, ms) { return function() { - setTimeout(() => f.apply(this, arguments), ms) + setTimeout(() => f.apply(this, arguments), ms); }; } diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index 3593bffae..0a945b377 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -19,7 +19,7 @@ We didn't see them yet, because generally they do not show up. When we create a First, let's see how to get those flags. -The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. +The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. The syntax is: ```js @@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty). +To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). The syntax is: @@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. ```smart header="Errors appear only in strict mode" -In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. ``` Here's the same example, but the property is created from scratch: @@ -194,7 +194,7 @@ alert(Object.keys(user)); // name The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties. -A non-configurable property can not be deleted. +A non-configurable property can't be deleted, its attributes can't be modified. For instance, `Math.PI` is non-writable, non-enumerable and non-configurable: @@ -214,49 +214,67 @@ alert( JSON.stringify(descriptor, null, 2 ) ); So, a programmer is unable to change the value of `Math.PI` or overwrite it. ```js run -Math.PI = 3; // Error +Math.PI = 3; // Error, because it has writable: false // delete Math.PI won't work either ``` +We also can't change `Math.PI` to be `writable` again: + +```js run +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); +``` + +There's absolutely nothing we can do with `Math.PI`. + Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`. -To be precise, non-configurability imposes several restrictions on `defineProperty`: -1. Can't change `configurable` flag. -2. Can't change `enumerable` flag. -3. Can't change `writable: false` to `true` (the other way round works). -4. Can't change `get/set` for an accessor property (but can assign them if absent). +**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.** -Here we are making `user.name` a "forever sealed" constant: +Here `user.name` is non-configurable, but we can still change it (as it's writable): ```js run -let user = { }; +let user = { + name: "John" +}; + +Object.defineProperty(user, "name", { + configurable: false +}); + +user.name = "Pete"; // works fine +delete user.name; // Error +``` + +And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`: + +```js run +let user = { + name: "John" +}; Object.defineProperty(user, "name", { - value: "John", writable: false, configurable: false }); -*!* // won't be able to change user.name or its flags // all this won't work: -// user.name = "Pete" -// delete user.name -// defineProperty(user, "name", { value: "Pete" }) -Object.defineProperty(user, "name", {writable: true}); // Error -*/!* +user.name = "Pete"; +delete user.name; +Object.defineProperty(user, "name", { value: "Pete" }); ``` -```smart header="\"Non-configurable\" doesn't mean \"non-writable\"" -Notable exception: a value of non-configurable, but writable property can be changed. +```smart header="The only attribute change possible: writable true -> false" +There's a minor exception about changing flags. -The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value. +We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though. ``` ## Object.defineProperties -There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once. +There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once. The syntax is: @@ -282,7 +300,7 @@ So, we can set many properties at once. ## Object.getOwnPropertyDescriptors -To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). +To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors). Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object: @@ -300,7 +318,7 @@ for (let key in user) { ...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred. -Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones. +Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones. ## Sealing an object globally @@ -308,24 +326,24 @@ Property descriptors work at the level of individual properties. There are also methods that limit access to the *whole* object: -[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) +[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) : Forbids the addition of new properties to the object. -[Object.seal(obj)](mdn:js/Object/seal) +[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) : Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. -[Object.freeze(obj)](mdn:js/Object/freeze) +[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) : Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. And also there are tests for them: -[Object.isExtensible(obj)](mdn:js/Object/isExtensible) +[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) : Returns `false` if adding properties is forbidden, otherwise `true`. -[Object.isSealed(obj)](mdn:js/Object/isSealed) +[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) : Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`. -[Object.isFrozen(obj)](mdn:js/Object/isFrozen) +[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen) : Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`. These methods are rarely used in practice. diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md index 45b9e70ed..c2aa35d53 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -5,7 +5,7 @@ There are two kinds of object properties. The first kind is *data properties*. We already know how to work with them. All properties that we've been using until now were data properties. -The second type of properties is something new. It's *accessor properties*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code. +The second type of property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code. ## Getters and setters diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 710390f15..ef6c7ffeb 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,7 +12,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named ![prototype](object-prototype-empty.svg) -The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it. +When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. @@ -27,19 +27,11 @@ let rabbit = { }; *!* -rabbit.__proto__ = animal; +rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` -```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" -Please note that `__proto__` is *not the same* as `[[Prototype]]`. It's a getter/setter for it. - -It exists for historical reasons. In modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later. - -By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples. -``` - -If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`. +Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`. For instance: @@ -62,7 +54,7 @@ alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true ``` -Here the line `(*)` sets `animal` to be a prototype of `rabbit`. +Here the line `(*)` sets `animal` to be the prototype of `rabbit`. Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up): @@ -130,6 +122,8 @@ alert(longEar.jumps); // true (from rabbit) ![](proto-animal-rabbit-chain.svg) +Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`. + There are only two limitations: 1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. @@ -137,6 +131,18 @@ There are only two limitations: Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. +```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" +It's a common mistake of novice developers not to know the difference between these two. + +Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language. + +The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later. + +By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it. + +As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples. +``` + ## Writing doesn't use prototype The prototype is only used for reading properties. @@ -197,6 +203,9 @@ alert(admin.fullName); // John Smith (*) // setter triggers! admin.fullName = "Alice Cooper"; // (**) + +alert(admin.fullName); // Alice Cooper, state of admin modified +alert(user.fullName); // John Smith, state of user protected ``` Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called. @@ -277,7 +286,7 @@ for(let prop in rabbit) alert(prop); // jumps, then eats */!* ``` -If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. +If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. So we can filter out inherited properties (or do something else with them): diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg index da48a7ccd..eb79c19ff 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg @@ -1 +1 @@ -prototype objectobject[[Prototype]] \ No newline at end of file +prototype objectobject[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg index 520bf87ed..4bf580ae7 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg @@ -1 +1 @@ -eats: true walk: functionanimaljumps: truerabbit[[Prototype]]earLength: 10longEar[[Prototype]] \ No newline at end of file +eats: true walk: functionanimaljumps: truerabbit[[Prototype]]earLength: 10longEar[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg index 8b6573574..838c78395 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg @@ -1 +1 @@ -eats: true walk: functionanimalwalk: functionrabbit[[Prototype]] \ No newline at end of file +eats: true walk: functionanimalwalk: functionrabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg index 6e3b6f555..d791e5390 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg @@ -1 +1 @@ -walk: function sleep: functionanimalrabbit[[Prototype]]name: "White Rabbit" isSleeping: true \ No newline at end of file +walk: function sleep: functionanimalrabbit[[Prototype]]name: "White Rabbit" isSleeping: true \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg index b83933a87..b32471028 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg @@ -1 +1 @@ -eats: true walk: functionanimaljumps: truerabbit[[Prototype]] \ No newline at end of file +eats: true walk: functionanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg index 538f5afb3..4f3c1bc0e 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg @@ -1 +1 @@ -eats: trueanimaljumps: truerabbit[[Prototype]] \ No newline at end of file +eats: trueanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg index ed9fea4a0..bf0baf013 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg @@ -1 +1 @@ -name: "John" surname: "Smith" set fullName: functionisAdmin: true name: "Alice" surname: "Cooper"useradmin[[Prototype]] \ No newline at end of file +name: "John" surname: "Smith" set fullName: functionisAdmin: true name: "Alice" surname: "Cooper"useradmin[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg index 782a858bd..32a9858f8 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg @@ -1 +1 @@ -toString: function hasOwnProperty: function ...Object.prototypeanimal[[Prototype]][[Prototype]][[Prototype]]nulleats: truerabbitjumps: true \ No newline at end of file +toString: function hasOwnProperty: function ...Object.prototypeanimal[[Prototype]][[Prototype]][[Prototype]]nulleats: truerabbitjumps: true \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 0073e252e..372d50dd6 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -38,7 +38,12 @@ Why `user2.name` is `undefined`? Here's how `new user.constructor('Pete')` works: 1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). +3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. +4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all. +Finally, at the end, we have `let user2 = new Object('Pete')`. + +Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. + +(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg index 187b899e4..59d60b397 100644 --- a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg +++ b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg @@ -1 +1 @@ -Rabbitprototypeconstructordefault "prototype" \ No newline at end of file +Rabbitprototypeconstructordefault "prototype" \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg index a2c19d850..ede4e1227 100644 --- a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg +++ b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg @@ -1 +1 @@ -eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file +eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg index 4d6b10e30..54b3d7980 100644 --- a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg +++ b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg @@ -1 +1 @@ -default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file +default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index 378936c9a..bdfc86dd8 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -2,7 +2,7 @@ The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it. -First we'll see at the details, and then how to use it for adding new capabilities to built-in objects. +First we'll look at the details, and then how to use it for adding new capabilities to built-in objects. ## Object.prototype @@ -33,7 +33,9 @@ We can check it like this: let obj = {}; alert(obj.__proto__ === Object.prototype); // true -// obj.toString === obj.__proto__.toString == Object.prototype.toString + +alert(obj.toString === obj.__proto__.toString); //true +alert(obj.toString === Object.prototype.toString); //true ``` Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg index 187b899e4..59d60b397 100644 --- a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg +++ b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg @@ -1 +1 @@ -Rabbitprototypeconstructordefault "prototype" \ No newline at end of file +Rabbitprototypeconstructordefault "prototype" \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg index 8475560b3..ebb4f3205 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg @@ -1 +1 @@ -toString: function ...Array.prototypetoString: function ...Object.prototype[[Prototype]][[Prototype]][1, 2, 3] \ No newline at end of file +toString: function ...Array.prototypetoString: function ...Object.prototype[[Prototype]][[Prototype]][1, 2, 3] \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg index 36cc81cd9..4d6129e0a 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg @@ -1 +1 @@ -toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file +toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg index c111e0725..9630e68e2 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg @@ -1 +1 @@ -constructor: Object toString: function ...Object.prototypeObjectobj = new Object()[[Prototype]]prototype \ No newline at end of file +constructor: Object toString: function ...Object.prototypeObjectobj = new Object()[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg index 8b802eb44..9ccb34229 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg @@ -1 +1 @@ -obj[[Prototype]]null \ No newline at end of file +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg index b5014f9f0..024dd3021 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg @@ -1 +1 @@ -constructor: Object toString: function ...Object.prototypeObjectprototype \ No newline at end of file +constructor: Object toString: function ...Object.prototypeObjectprototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg deleted file mode 100644 index a2c19d850..000000000 --- a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg +++ /dev/null @@ -1 +0,0 @@ -eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg index 4d6b10e30..54b3d7980 100644 --- a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg +++ b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg @@ -1 +1 @@ -default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file +default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index a92e17900..f3c9cf0e5 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -28,4 +28,4 @@ alert(dictionary); // "apple,__proto__" When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable. -See the the chapter [](info:property-descriptors) for review. +See the chapter [](info:property-descriptors) for review. diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index d517e1564..9c5f1eb3d 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -3,15 +3,18 @@ In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. -The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard). +Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only). -The modern methods are: +The modern methods to get/set a prototype are: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`. -These should be used instead of `__proto__`. +The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`. + +Although, there's a special method for this too: + +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. For instance: @@ -22,7 +25,7 @@ let animal = { // create a new object with animal as a prototype *!* -let rabbit = Object.create(animal); +let rabbit = Object.create(animal); // same as {__proto__: animal} */!* alert(rabbit.eats); // true @@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} */!* ``` -`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this: +The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors. + +We can provide additional properties to the new object there, like this: ```js run let animal = { @@ -57,26 +62,34 @@ The descriptors are in the same format as described in the chapter ... get __proto__: function set __proto__: functionObject.prototypeObjectobj[[Prototype]]prototype \ No newline at end of file +... get __proto__: function set __proto__: functionObject.prototypeObjectobj[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg index 8b802eb44..9ccb34229 100644 --- a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg +++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg @@ -1 +1 @@ -obj[[Prototype]]null \ No newline at end of file +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/09-classes/01-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md index 05365e410..4477de679 100644 --- a/1-js/09-classes/01-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -4,6 +4,6 @@ importance: 5 # Rewrite to class -The `Clock` class is written in functional style. Rewrite it the "class" syntax. +The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax. P.S. The clock ticks in the console, open it to see. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 49a891b71..135d24929 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -51,7 +51,7 @@ user.sayHi(); When `new User("John")` is called: 1. A new object is created. -2. The `constructor` runs with the given argument and assigns `this.name` to it. +2. The `constructor` runs with the given argument and assigns it to `this.name`. ...Then we can call object methods, such as `user.sayHi()`. @@ -110,7 +110,7 @@ alert(typeof User); // function alert(User === User.prototype.constructor); // true // The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // alert(this.name); +alert(User.prototype.sayHi); // the code of the sayHi method // there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi @@ -118,7 +118,7 @@ alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ## Not just a syntactic sugar -Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without `class` keyword at all: +Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same thing without using the `class` keyword at all: ```js run // rewriting class User in pure functions @@ -144,7 +144,7 @@ The result of this definition is about the same. So, there are indeed reasons wh Still, there are important differences. -1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. +1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`: @@ -218,7 +218,7 @@ function makeClass(phrase) { return class { sayHi() { alert(phrase); - }; + } }; } diff --git a/1-js/09-classes/01-class/class-user.svg b/1-js/09-classes/01-class/class-user.svg index 95b58179b..418d71d18 100644 --- a/1-js/09-classes/01-class/class-user.svg +++ b/1-js/09-classes/01-class/class-user.svg @@ -1 +1 @@ -sayHi: functionUserUser.prototypeprototypeconstructor: User \ No newline at end of file +sayHi: functionUserUser.prototypeprototypeconstructor: User \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg index 3471904ab..63b5a18a1 100644 --- a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg +++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg @@ -1 +1 @@ -constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructorextends \ No newline at end of file +constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructorextends \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 3ce6d2995..464042d82 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -55,7 +55,7 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -Object of `Rabbit` class have access to both `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. +Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. @@ -76,8 +76,8 @@ For instance, a function call that generates the parent class: ```js run function f(phrase) { return class { - sayHi() { alert(phrase) } - } + sayHi() { alert(phrase); } + }; } *!* @@ -106,7 +106,7 @@ class Rabbit extends Animal { } ``` -Usually we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +Usually, however, we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. Classes provide `"super"` keyword for that. @@ -151,7 +151,7 @@ class Rabbit extends Animal { let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stands still. White rabbit hides! +rabbit.stop(); // White Rabbit stands still. White Rabbit hides! ``` Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. @@ -160,6 +160,7 @@ Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the p As was mentioned in the chapter , arrow functions do not have `super`. If accessed, it's taken from the outer function. For instance: + ```js class Rabbit extends Animal { stop() { @@ -176,7 +177,6 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` - ## Overriding constructor With constructors it gets a little bit tricky. @@ -280,8 +280,6 @@ alert(rabbit.earLength); // 10 */!* ``` - - ### Overriding class fields: a tricky note ```warn header="Advanced note" @@ -300,7 +298,7 @@ Consider this example: ```js run class Animal { - name = 'animal' + name = 'animal'; constructor() { alert(this.name); // (*) @@ -317,13 +315,13 @@ new Rabbit(); // animal */!* ``` -Here, class `Rabbit` extends `Animal` and overrides `name` field with its own value. +Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value. There's no own constructor in `Rabbit`, so `Animal` constructor is called. What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`. -**In other words, parent constructor always uses its own field value, not the overridden one.** +**In other words, the parent constructor always uses its own field value, not the overridden one.** What's odd about it? @@ -360,23 +358,22 @@ And that's what we naturally expect. When the parent constructor is called in th ...But for class fields it's not so. As said, the parent constructor always uses the parent field. -Why is there the difference? +Why is there a difference? -Well, the reason is in the field initialization order. The class field is initialized: +Well, the reason is the field initialization order. The class field is initialized: - Before constructor for the base class (that doesn't extend anything), -- Imediately after `super()` for the derived class. +- Immediately after `super()` for the derived class. In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used. -This subtle difference between fields and methods is specific to JavaScript +This subtle difference between fields and methods is specific to JavaScript. Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here. If it becomes a problem, one can fix it by using methods or getters/setters instead of fields. - ## Super: internals, [[HomeObject]] ```warn header="Advanced information" @@ -545,7 +542,7 @@ Here's the demo of a wrong `super` result after copying: ```js run let animal = { sayHi() { - console.log(`I'm an animal`); + alert(`I'm an animal`); } }; @@ -559,7 +556,7 @@ let rabbit = { let plant = { sayHi() { - console.log("I'm a plant"); + alert("I'm a plant"); } }; diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg index 10af6c4c2..5ea9bf29e 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg @@ -1 +1 @@ -slice: function ...Array.prototypearrhasOwnProperty: function ...Object.prototype[1, 2, 3][[Prototype]][[Prototype]] \ No newline at end of file +slice: function ...Array.prototypearrhasOwnProperty: function ...Object.prototype[1, 2, 3][[Prototype]][[Prototype]] \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg index a81676e25..72e47e34c 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg @@ -1 +1 @@ -jump: functionRabbit.prototyperabbiteat: functionAnimal.prototypename: "White Rabbit"[[Prototype]][[Prototype]]Rabbit.prototype.__proto__ = Animal.prototype sets thistoString: function hasOwnProperty: function ...Object.prototype[[Prototype]][[Prototype]]null \ No newline at end of file +jump: functionRabbit.prototyperabbiteat: functionAnimal.prototypename: "White Rabbit"[[Prototype]][[Prototype]]Rabbit.prototype.__proto__ = Animal.prototype sets thistoString: function hasOwnProperty: function ...Object.prototype[[Prototype]][[Prototype]]null \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg index 35529aa43..bced3d355 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg @@ -1 +1 @@ -methods of RabbitRabbit.prototyperabbitmethods of AnimalAnimal.prototype[[Prototype]][[Prototype]]properties of rabbit \ No newline at end of file +methods of RabbitRabbit.prototyperabbitmethods of AnimalAnimal.prototype[[Prototype]][[Prototype]]properties of rabbit \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg index 905efe37a..f53fc92de 100644 --- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg @@ -1 +1 @@ - constructor: Animal run: function stop: functionAnimal.prototypeAnimalnew Animal[[Prototype]]prototypename: "My animal" \ No newline at end of file + constructor: Animal run: function stop: functionAnimal.prototypeAnimalnew Animal[[Prototype]]prototypename: "My animal" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg index 81bf1850b..2f30a3a90 100644 --- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg @@ -1 +1 @@ - constructor: Rabbit hide: functionRabbit.prototypeRabbitnew Rabbit[[Prototype]]prototypename: "My rabbit" \ No newline at end of file + constructor: Rabbit hide: functionRabbit.prototypeRabbitnew Rabbit[[Prototype]]prototypename: "My rabbit" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg index f13d441c9..f6450ddc4 100644 --- a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg +++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg @@ -1 +1 @@ -sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file +sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.svg b/1-js/09-classes/02-class-inheritance/this-super-loop.svg index bc200fab3..4f5f45034 100644 --- a/1-js/09-classes/02-class-inheritance/this-super-loop.svg +++ b/1-js/09-classes/02-class-inheritance/this-super-loop.svg @@ -1 +1 @@ -rabbitlongEarrabbitlongEar \ No newline at end of file +rabbitlongEarrabbitlongEar \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg index 34d783b4d..915ab9aa6 100644 --- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg @@ -1 +1 @@ -call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file +call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md index ca9e80601..cb9829ce0 100644 --- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md @@ -21,14 +21,14 @@ alert( rabbit.hasOwnProperty('name') ); // true But that's not all yet. -Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`. +Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`. As we know, the "extends" syntax sets up two prototypes: 1. Between `"prototype"` of the constructor functions (for methods). 2. Between the constructor functions themselves (for static methods). -In our case, for `class Rabbit extends Object` it means: +In the case of `class Rabbit extends Object` it means: ```js run class Rabbit extends Object {} @@ -37,7 +37,7 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true ``` -So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this: +So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this: ```js run class Rabbit extends Object {} @@ -67,7 +67,7 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error So `Rabbit` doesn't provide access to static methods of `Object` in that case. -By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. +By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. Here's the picture: diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg index 18093d7cf..3e354b895 100644 --- a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg +++ b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg @@ -1 +1 @@ -constructor: Animal run: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitrabbit[[Prototype]][[Prototype]][[Prototype]]prototypeprototypecomparename: "White Rabbit" \ No newline at end of file +constructor: Animal run: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitrabbit[[Prototype]][[Prototype]][[Prototype]]prototypeprototypecomparename: "White Rabbit" \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index ab08f2ded..4b493a5e8 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -1,9 +1,9 @@ # Static properties and methods -We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*. +We can also assign a method to the class as a whole. Such methods are called *static*. -In a class, they are prepended by `static` keyword, like this: +In a class declaration, they are prepended by `static` keyword, like this: ```js run class User { @@ -31,9 +31,11 @@ User.staticMethod(); // true The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule). -Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it. +Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it. -For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this: +For instance, we have `Article` objects and need a function to compare them. + +A natural solution would be to add `Article.compare` static method: ```js run class Article { @@ -63,9 +65,11 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. +Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. + +Another example would be a so-called "factory" method. -Another example would be a so-called "factory" method. Imagine, we need few ways to create an article: +Let's say, we need multiple ways to create an article: 1. Create by given parameters (`title`, `date` etc). 2. Create an empty article with today's date. @@ -73,7 +77,7 @@ Another example would be a so-called "factory" method. Imagine, we need few ways The first way can be implemented by the constructor. And for the second one we can make a static method of the class. -Like `Article.createTodays()` here: +Such as `Article.createTodays()` here: ```js run class Article { @@ -101,10 +105,21 @@ Static methods are also used in database-related classes to search/save/remove e ```js // assuming Article is a special class for managing articles -// static method to remove the article: +// static method to remove the article by id: Article.remove({id: 12345}); ``` +````warn header="Static methods aren't available for individual objects" +Static methods are callable on classes, not on individual objects. + +E.g. such code won't work: + +```js +// ... +article.createTodays(); /// Error: article.createTodays is not a function +``` +```` + ## Static properties [recent browser=Chrome] @@ -125,7 +140,7 @@ That is the same as a direct assignment to `Article`: Article.publisher = "Ilya Kantor"; ``` -## Inheritance of static properties and methods +## Inheritance of static properties and methods [#statics-and-inheritance] Static properties and methods are inherited. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 60ed0ef1b..91efb89ee 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -96,7 +96,9 @@ class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) { + value = 0; + } this._waterAmount = value; } @@ -114,10 +116,10 @@ class CoffeeMachine { let coffeeMachine = new CoffeeMachine(100); // add water -coffeeMachine.waterAmount = -10; // Error: Negative water +coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10 ``` -Now the access is under control, so setting the water below zero fails. +Now the access is under control, so setting the water amount below zero becomes impossible. ## Read-only "power" @@ -159,7 +161,7 @@ class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this._waterAmount = value; } @@ -190,7 +192,7 @@ There's a finished JavaScript proposal, almost in the standard, that provides la Privates should start with `#`. They are only accessible from inside the class. -For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`: +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: ```js run class CoffeeMachine { @@ -199,19 +201,23 @@ class CoffeeMachine { */!* *!* - #checkWater(value) { - if (value < 0) throw new Error("Negative water"); - if (value > this.#waterLimit) throw new Error("Too much water"); + #fixWaterAmount(value) { + if (value < 0) return 0; + if (value > this.#waterLimit) return this.#waterLimit; } */!* + setWaterAmount(value) { + this.#waterLimit = this.#fixWaterAmount(value); + } + } let coffeeMachine = new CoffeeMachine(); *!* // can't access privates from outside of the class -coffeeMachine.#checkWater(); // Error +coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` @@ -232,7 +238,7 @@ class CoffeeMachine { } set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this.#waterAmount = value; } } diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg index 470aabf7f..be47d7fd9 100644 --- a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg +++ b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg @@ -1 +1 @@ -constructor: Object toString: function hasOwnProperty: function ...Object.prototypeconstructor: Date toString: function getDate: function ...Date.prototypeObjectDatenew Date()[[Prototype]][[Prototype]]prototypeprototypedefineProperty keys ...now parse ...1 Jan 2019 \ No newline at end of file +constructor: Object toString: function hasOwnProperty: function ...Object.prototypeconstructor: Date toString: function getDate: function ...Date.prototypeObjectDatenew Date()[[Prototype]][[Prototype]]prototypeprototypedefineProperty keys ...now parse ...1 Jan 2019 \ No newline at end of file diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index aa973da06..f9db989ca 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -2,7 +2,7 @@ The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account. -Such a check may be necessary in many cases. Here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type. +Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type. ## The instanceof operator [#ref-instanceof] @@ -93,7 +93,7 @@ The algorithm of `obj instanceof Class` works roughly as follows: alert(rabbit instanceof Animal); // true */!* - // rabbit.__proto__ === Rabbit.prototype + // rabbit.__proto__ === Animal.prototype (no match) *!* // rabbit.__proto__.__proto__ === Animal.prototype (match!) */!* @@ -190,7 +190,7 @@ For most environment-specific objects, there is such a property. Here are some b ```js run // toStringTag for the environment-specific object and class: -alert( window[Symbol.toStringTag]); // window +alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] diff --git a/1-js/09-classes/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg index 78bff9f12..d63b03a8a 100644 --- a/1-js/09-classes/06-instanceof/instanceof.svg +++ b/1-js/09-classes/06-instanceof/instanceof.svg @@ -1 +1 @@ -Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file +Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 60f6f7c4a..526b832ef 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -69,7 +69,7 @@ let sayMixin = { }; let sayHiMixin = { - __proto__: sayMixin, // (or we could use Object.create to set the prototype here) + __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here) sayHi() { *!* @@ -103,7 +103,7 @@ Here's the diagram (see the right part): That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. ## EventMixin @@ -154,7 +154,7 @@ let eventMixin = { * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { - if (!this._eventHandlers || !this._eventHandlers[eventName]) { + if (!this._eventHandlers?.[eventName]) { return; // no handlers for that event name } diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.svg b/1-js/09-classes/07-mixins/mixin-inheritance.svg index aaa8cb7d0..1fdc22393 100644 --- a/1-js/09-classes/07-mixins/mixin-inheritance.svg +++ b/1-js/09-classes/07-mixins/mixin-inheritance.svg @@ -1 +1 @@ -sayHi: function sayBye: functionsayHiMixinsay: functionsayMixin[[Prototype]]constructor: User sayHi: function sayBye: functionUser.prototype[[Prototype]]name: ...user[[HomeObject] \ No newline at end of file +sayHi: function sayBye: functionsayHiMixinsay: functionsayMixin[[Prototype]]constructor: User sayHi: function sayBye: functionUser.prototype[[Prototype]]name: ...user[[HomeObject] \ No newline at end of file diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index 303431d6d..ec0dabc9a 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -1,8 +1,8 @@ The difference becomes obvious when we look at the code inside a function. -The behavior is different if there's a "jump out" of `try..catch`. +The behavior is different if there's a "jump out" of `try...catch`. -For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control. +For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control. ```js run function f() { @@ -11,7 +11,7 @@ function f() { *!* return "result"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('cleanup!'); @@ -28,11 +28,11 @@ function f() { try { alert('start'); throw new Error("an error"); - } catch (e) { + } catch (err) { // ... if("can't handle the error") { *!* - throw e; + throw err; */!* } diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md index c573cc232..b6dc81326 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md @@ -6,12 +6,12 @@ importance: 5 Compare the two code fragments. -1. The first one uses `finally` to execute the code after `try..catch`: +1. The first one uses `finally` to execute the code after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } finally { *!* @@ -19,12 +19,12 @@ Compare the two code fragments. */!* } ``` -2. The second fragment puts the cleaning right after `try..catch`: +2. The second fragment puts the cleaning right after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 7d41b71b1..bf548373a 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,14 +1,14 @@ -# Error handling, "try..catch" +# Error handling, "try...catch" No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons. Usually, a script "dies" (immediately stops) in case of an error, printing it to console. -But there's a syntax construct `try..catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. +But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. -## The "try..catch" syntax +## The "try...catch" syntax -The `try..catch` construct has two main blocks: `try`, and then `catch`: +The `try...catch` construct has two main blocks: `try`, and then `catch`: ```js try { @@ -25,12 +25,12 @@ try { It works like this: 1. First, the code in `try {...}` is executed. -2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. -3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch(err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. +2. If there were no errors, then `catch (err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. +3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. ![](try-catch-flow.svg) -So, an error inside the `try {…}` block does not kill the script -- we have a chance to handle it in `catch`. +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. Let's look at some examples. @@ -45,7 +45,7 @@ Let's look at some examples. alert('End of try runs'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) @@ -64,7 +64,7 @@ Let's look at some examples. alert('End of try (never reached)'); // (2) - } catch(err) { + } catch (err) { alert(`Error has occurred!`); // *!*(3) <--*/!* @@ -72,45 +72,45 @@ Let's look at some examples. ``` -````warn header="`try..catch` only works for runtime errors" -For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript. +````warn header="`try...catch` only works for runtime errors" +For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript. It won't work if the code is syntactically wrong, for instance it has unmatched curly braces: ```js run try { {{{{{{{{{{{{ -} catch(e) { +} catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. -So, `try..catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". ```` -````warn header="`try..catch` works synchronously" -If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it: +````warn header="`try...catch` works synchronously" +If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it: ```js run try { setTimeout(function() { noSuchVariable; // script will die here }, 1000); -} catch (e) { +} catch (err) { alert( "won't work" ); } ``` -That's because the function itself is executed later, when the engine has already left the `try..catch` construct. +That's because the function itself is executed later, when the engine has already left the `try...catch` construct. -To catch an exception inside a scheduled function, `try..catch` must be inside that function: +To catch an exception inside a scheduled function, `try...catch` must be inside that function: ```js run setTimeout(function() { try { - noSuchVariable; // try..catch handles the error! + noSuchVariable; // try...catch handles the error! } catch { alert( "error is caught here!" ); } @@ -125,7 +125,7 @@ When an error occurs, JavaScript generates an object containing the details abou ```js try { // ... -} catch(err) { // <-- the "error object", could use another word instead of err +} catch (err) { // <-- the "error object", could use another word instead of err // ... } ``` @@ -150,7 +150,7 @@ try { *!* lalala; // error, variable is not defined! */!* -} catch(err) { +} catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) @@ -175,9 +175,9 @@ try { } ``` -## Using "try..catch" +## Using "try...catch" -Let's explore a real-life use case of `try..catch`. +Let's explore a real-life use case of `try...catch`. As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values. @@ -205,7 +205,7 @@ Should we be satisfied with that? Of course not! This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message. -Let's use `try..catch` to handle the error: +Let's use `try...catch` to handle the error: ```js run let json = "{ bad json }"; @@ -217,12 +217,12 @@ try { */!* alert( user.name ); // doesn't work -} catch (e) { +} catch (err) { *!* // ...the execution jumps here alert( "Our apologies, the data has errors, we'll try to request it one more time." ); - alert( e.name ); - alert( e.message ); + alert( err.name ); + alert( err.message ); */!* } ``` @@ -245,7 +245,7 @@ try { alert( user.name ); // no name! */!* -} catch (e) { +} catch (err) { alert( "doesn't execute" ); } ``` @@ -294,11 +294,11 @@ Let's see what kind of error `JSON.parse` generates: ```js run try { JSON.parse("{ bad json o_O }"); -} catch(e) { +} catch (err) { *!* - alert(e.name); // SyntaxError + alert(err.name); // SyntaxError */!* - alert(e.message); // Unexpected token b in JSON at position 2 + alert(err.message); // Unexpected token b in JSON at position 2 } ``` @@ -323,8 +323,8 @@ try { alert( user.name ); -} catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name +} catch (err) { + alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name } ``` @@ -334,7 +334,7 @@ Now `catch` became a single place for all error handling: both for `JSON.parse` ## Rethrowing -In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. +In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. For example: @@ -345,7 +345,7 @@ try { user = JSON.parse(json); // <-- forgot to put "let" before user // ... -} catch(err) { +} catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (no JSON Error actually) } @@ -353,7 +353,7 @@ try { Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks. -In our case, `try..catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. +In our case, `try...catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. To avoid such problems, we can employ the "rethrowing" technique. The rule is simple: @@ -362,15 +362,15 @@ To avoid such problems, we can employ the "rethrowing" technique. The rule is si The "rethrowing" technique can be explained in more detail as: 1. Catch gets all errors. -2. In the `catch(err) {...}` block we analyze the error object `err`. -2. If we don't know how to handle it, we do `throw err`. +2. In the `catch (err) {...}` block we analyze the error object `err`. +3. If we don't know how to handle it, we do `throw err`. Usually, we can check the error type using the `instanceof` operator: ```js run try { user = { /*...*/ }; -} catch(err) { +} catch (err) { *!* if (err instanceof ReferenceError) { */!* @@ -399,24 +399,24 @@ try { alert( user.name ); -} catch(e) { +} catch (err) { *!* - if (e instanceof SyntaxError) { - alert( "JSON Error: " + e.message ); + if (err instanceof SyntaxError) { + alert( "JSON Error: " + err.message ); } else { - throw e; // rethrow (*) + throw err; // rethrow (*) } */!* } ``` -The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script. +The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script. So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others. -The example below demonstrates how such errors can be caught by one more level of `try..catch`: +The example below demonstrates how such errors can be caught by one more level of `try...catch`: ```js run function readData() { @@ -427,11 +427,11 @@ function readData() { *!* blabla(); // error! */!* - } catch (e) { + } catch (err) { // ... - if (!(e instanceof SyntaxError)) { + if (!(err instanceof SyntaxError)) { *!* - throw e; // rethrow (don't know how to deal with it) + throw err; // rethrow (don't know how to deal with it) */!* } } @@ -439,20 +439,20 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* - alert( "External catch got: " + e ); // caught it! + alert( "External catch got: " + err ); // caught it! */!* } ``` -Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything. +Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything. -## try..catch..finally +## try...catch...finally Wait, that's not all. -The `try..catch` construct may have one more code clause: `finally`. +The `try...catch` construct may have one more code clause: `finally`. If it exists, it runs in all cases: @@ -464,7 +464,7 @@ The extended syntax looks like this: ```js *!*try*/!* { ... try to execute the code ... -} *!*catch*/!*(e) { +} *!*catch*/!* (err) { ... handle errors ... } *!*finally*/!* { ... execute always ... @@ -477,7 +477,7 @@ Try running this code: try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); -} catch (e) { +} catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); @@ -513,7 +513,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -531,14 +531,14 @@ You can check by running the code with entering `35` into `prompt` -- it execute In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. -```smart header="Variables are local inside `try..catch..finally`" -Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`. +```smart header="Variables are local inside `try...catch...finally`" +Please note that `result` and `diff` variables in the code above are declared *before* `try...catch`. Otherwise, if we declared `let` in `try` block, it would only be visible inside of it. ``` ````smart header="`finally` and `return`" -The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`. +The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`. In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code. @@ -550,7 +550,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -563,9 +563,9 @@ alert( func() ); // first works alert from finally, and then this one ``` ```` -````smart header="`try..finally`" +````smart header="`try...finally`" -The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. +The `try...finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. ```js function func() { @@ -586,7 +586,7 @@ In the code above, an error inside `try` always falls out, because there's no `c The information from this section is not a part of the core JavaScript. ``` -Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or some other terrible thing. +Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing. Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc. @@ -632,7 +632,7 @@ For instance: The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -There are also web-services that provide error-logging for such cases, like or . +There are also web-services that provide error-logging for such cases, like or . They work like this: @@ -643,14 +643,14 @@ They work like this: ## Summary -The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. +The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. The syntax is: ```js try { // run this code -} catch(err) { +} catch (err) { // if an error happened, then jump here // err is the error object } finally { @@ -658,7 +658,7 @@ try { } ``` -There may be no `catch` section or no `finally`, so shorter constructs `try..catch` and `try..finally` are also valid. +There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid. Error objects have following properties: @@ -666,10 +666,10 @@ Error objects have following properties: - `name` -- the string with error name (error constructor name). - `stack` (non-standard, but well-supported) -- the stack at the moment of error creation. -If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`. +If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`. We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. *Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. -Even if we don't have `try..catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. +Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg index ac816e356..2c0d71348 100644 --- a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg +++ b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg @@ -1 +1 @@ -BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file +BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md index ff2e4c529..d28b07439 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -21,9 +21,9 @@ Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it thr Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field. -Our `ValidationError` class should inherit from the built-in `Error` class. +Our `ValidationError` class should inherit from the `Error` class. -That class is built-in, but here's its approximate code so we can understand what we're extending: +The `Error` class is built-in, but here's its approximate code so we can understand what we're extending: ```js // The "pseudocode" for the built-in Error class defined by JavaScript itself @@ -38,7 +38,7 @@ class Error { Now let's inherit `ValidationError` from it and try it in action: -```js run untrusted +```js run *!* class ValidationError extends Error { */!* @@ -117,15 +117,15 @@ We could also look at `err.name`, like this: // instead of (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof. -Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through. +Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through. ## Further inheritance -The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. +The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. ```js run class ValidationError extends Error { diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index 9d1a260d5..57115a909 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -28,7 +28,7 @@ function loadScript(src) { } ``` -It appends to the document the new, dynamically created, tag ` ``` -If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason. +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` ### A module code is evaluated only the first time when imported -If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers. +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. + +The one-time evaluation has important consequences, that we should be aware of. -That has important consequences. Let's look at them using examples: +Let's see a couple of examples. First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: @@ -133,9 +146,11 @@ import `./alert.js`; // Module is evaluated! import `./alert.js`; // (shows nothing) ``` -In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it. +The second import shows nothing, because the module has already been evaluated. -Now, a more advanced example. +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. + +Now, let's consider a deeper example. Let's say, a module exports an object: @@ -160,54 +175,67 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* -// Both 1.js and 2.js imported the same object +// Both 1.js and 2.js reference the same admin object // Changes made in 1.js are visible in 2.js */!* ``` -So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. + +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that. -Such behavior allows us to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready. +**Such behavior is actually very convenient, because it allows us to *configure* modules.** -For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -Another module can also see `admin.name`: +...Now the module `admin.js` is configured. -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +Further importers can call it, and it correctly shows the current user: -alert(admin.name); // *!*Pete*/!* +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta The object `import.meta` contains the information about the current module. -Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML: +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: ```html run height=0 ``` @@ -233,7 +261,7 @@ Compare it to non-module scripts, where `this` is a global object: There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. -You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. +You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. ### Module scripts are deferred @@ -244,7 +272,7 @@ In other words: - module scripts wait until the HTML document is fully ready (even if they are tiny and load faster than HTML), and then run. - relative order of scripts is maintained: scripts that go first in the document, execute first. -As a side-effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. +As a side effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. For instance: @@ -260,7 +288,7 @@ Compare to regular script below: diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 4bd41a168..1b5649c69 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -46,7 +46,7 @@ Also, we can put `export` separately. Here we first declare, and then export: -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -93,25 +93,14 @@ At first sight, "import everything" seems such a cool thing, short to write, why Well, there are few reasons. -1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff. - - Let's say, we added a 3rd-party library `say.js` to our project with many functions: - ```js - // 📁 say.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` +1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. +2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. - Now if we only use one of `say.js` functions in our project: - ```js - // 📁 main.js - import {sayHi} from './say.js'; - ``` - ...Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking". +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. -2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. -3. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +``` ## Import "as" @@ -224,7 +213,7 @@ Without `default`, such an export would give an error: export class { // Error! (non-default export needs a name) constructor() {} } -``` +``` ### The "default" name @@ -321,12 +310,12 @@ export {default as User} from './user.js'; // re-export default Why would that be needed? Let's see a practical use case. -Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages), and many modules are just "helpers", for internal use in other package modules. +Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules. The file structure could be like this: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -337,13 +326,19 @@ auth/ ... ``` -We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this: +We'd like to expose the package functionality via a single entry point. + +In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`. + +Like this: ```js import {login, logout} from 'auth/index.js' ``` -The idea is that outsiders, developers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. +The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package. + +The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. As the actual exported functionality is scattered among the package, we can import it into `auth/index.js` and export from it: @@ -366,19 +361,21 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// import login/logout and immediately export them +// re-export login/logout export {login, logout} from './helpers.js'; -// import default as User and export it +// re-export the default export as User export {default as User} from './user.js'; ... ``` +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. + ### Re-exporting the default export The default export needs separate handling when re-exporting. -Let's say we have `user.js`, and we'd like to re-export class `User` from it: +Let's say we have `user.js` with the `export default class User` and would like to re-export it: ```js // 📁 user.js @@ -387,19 +384,21 @@ export default class User { } ``` -1. `export User from './user.js'` won't work. What can go wrong?... But that's a syntax error! +We can come across two problems with it: + +1. `export User from './user.js'` won't work. That would lead to a syntax error. - To re-export the default export, we have to write `export {default as User}`, as in the example above. + To re-export the default export, we have to write `export {default as User}`, as in the example above. 2. `export * from './user.js'` re-exports only named exports, but ignores the default one. - If we'd like to re-export both named and the default export, then two statements are needed: + If we'd like to re-export both named and default exports, then two statements are needed: ```js export * from './user.js'; // to re-export named exports export {default} from './user.js'; // to re-export the default export ``` -Such oddities of re-exporting the default export are one of the reasons why some developers don't like them. +Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones. ## Summary @@ -418,14 +417,14 @@ You can check yourself by reading them and recalling what they mean: Import: -- Named exports from module: +- Importing named exports: - `import {x [as y], ...} from "module"` -- Default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` -- Everything: +- Import all: - `import * as obj from "module"` -- Import the module (its code runs), but do not assign it to a variable: +- Import the module (its code runs), but do not assign any of its exports to variables: - `import "module"` We can put `import/export` statements at the top or at the bottom of a script, that doesn't matter. diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md index 357a57313..9db69cb2f 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md @@ -19,5 +19,5 @@ function wrap(target) { user = wrap(user); alert(user.name); // John -alert(user.age); // ReferenceError: Property doesn't exist "age" +alert(user.age); // ReferenceError: Property doesn't exist: "age" ``` diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md index 827cf35e3..47985e1a7 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md @@ -1,8 +1,8 @@ -# Error on reading non-existant property +# Error on reading non-existent property -Usually, an attempt to read a non-existant property returns `undefined`. +Usually, an attempt to read a non-existent property returns `undefined`. -Create a proxy that throws an error for an attempt to read of a non-existant property instead. +Create a proxy that throws an error for an attempt to read of a non-existent property instead. That can help to detect programming mistakes early. @@ -27,6 +27,6 @@ user = wrap(user); alert(user.name); // John *!* -alert(user.age); // ReferenceError: Property doesn't exist "age" +alert(user.age); // ReferenceError: Property doesn't exist: "age" */!* ``` diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index a4aec1fe5..1f84912e5 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -39,7 +39,7 @@ As there are no traps, all operations on `proxy` are forwarded to `target`. As we can see, without any traps, `proxy` is a transparent wrapper around `target`. -![](proxy.svg) +![](proxy.svg) `Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`. @@ -61,13 +61,13 @@ For every internal method, there's a trap in this table: the name of the method | `[[Delete]]` | `deleteProperty` | `delete` operator | | `[[Call]]` | `apply` | function call | | `[[Construct]]` | `construct` | `new` operator | -| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | -| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | -| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) | -| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) | -| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) | -| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | -| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` | +| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | +| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | +| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) | +| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) | +| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) | +| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | +| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` | ```warn header="Invariants" JavaScript enforces some invariants -- conditions that must be fulfilled by internal methods and traps. @@ -335,7 +335,7 @@ let user = { _password: "secret" }; -alert(user._password); // secret +alert(user._password); // secret ``` Let's use proxies to prevent any access to properties starting with `_`. @@ -376,7 +376,7 @@ user = new Proxy(user, { }, *!* deleteProperty(target, prop) { // to intercept property deletion -*/!* +*/!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { @@ -437,7 +437,7 @@ user = { ``` -A call to `user.checkPassword()` call gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. +A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps. @@ -840,7 +840,7 @@ So there's no such problem when proxying an array. ### Private fields -The similar thing happens with private class fields. +A similar thing happens with private class fields. For example, `getName()` method accesses the private `#name` property and breaks after proxying: @@ -963,9 +963,13 @@ revoke(); alert(proxy.data); // Error ``` -A call to `revoke()` removes all internal references to the target object from the proxy, so they are no more connected. The target object can be garbage-collected after that. +A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. + +Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope. -We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object: +We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`. + +Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy: ```js run *!* @@ -980,21 +984,19 @@ let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); -// ..later in our code.. +// ..somewhere else in our code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked) ``` -The benefit of such an approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needed. - We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. ## References - Specification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). -- MDN: [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). +- MDN: [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). ## Summary @@ -1016,13 +1018,13 @@ We can trap: - Reading (`get`), writing (`set`), deleting (`deleteProperty`) a property (even a non-existing one). - Calling a function (`apply` trap). - The `new` operator (`construct` trap). -- Many other operations (the full list is at the beginning of the article and in the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)). +- Many other operations (the full list is at the beginning of the article and in the [docs](mdn:/JavaScript/Reference/Global_Objects/Proxy)). That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more. We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality. -The [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. +The [Reflect](mdn:/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. Proxies have some limitations: diff --git a/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg b/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg index a5e158400..bc6c4ce2f 100644 --- a/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg +++ b/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg @@ -1 +1 @@ -_name: "Guest" name: getter_name: "Admin"user (proxied)original useradmin[[Prototype]] \ No newline at end of file +_name: "Guest" name: getter_name: "Admin"user (proxied)original useradmin[[Prototype]] \ No newline at end of file diff --git a/1-js/99-js-misc/01-proxy/proxy-inherit.svg b/1-js/99-js-misc/01-proxy/proxy-inherit.svg index 510dcef1b..6c34c0f4e 100644 --- a/1-js/99-js-misc/01-proxy/proxy-inherit.svg +++ b/1-js/99-js-misc/01-proxy/proxy-inherit.svg @@ -1 +1 @@ -_name: "Guest" name: getteruser (proxied)original user \ No newline at end of file +_name: "Guest" name: getteruser (proxied)original user \ No newline at end of file diff --git a/1-js/99-js-misc/01-proxy/proxy.svg b/1-js/99-js-misc/01-proxy/proxy.svg index 76a41670c..6b2224cfd 100644 --- a/1-js/99-js-misc/01-proxy/proxy.svg +++ b/1-js/99-js-misc/01-proxy/proxy.svg @@ -1 +1 @@ -test: 5proxytargetget proxy.test5 \ No newline at end of file +test: 5proxytargetget proxy.test5 \ No newline at end of file diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md index bb308847c..d71ac23f8 100644 --- a/1-js/99-js-misc/03-currying-partials/article.md +++ b/1-js/99-js-misc/03-currying-partials/article.md @@ -155,7 +155,7 @@ function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { - return function pass(...args2) { // (2) + return function(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } @@ -164,18 +164,10 @@ function curried(...args) { When we run it, there are two `if` execution branches: -1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it. -2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result. +1. If passed `args` count is the same or more than the original function has in its definition (`func.length`) , then just pass the call to it using `func.apply`. +2. Otherwise, get a partial: we don't call `func` just yet. Instead, another wrapper is returned, that will re-apply `curried` providing previous arguments together with the new ones. -For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`. - -For the call `curried(1)(2)(3)`: - -1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`. -2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. As the argument count is still less than 3, `curry` returns `pass`. -3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function. - -If that's still not obvious, just trace the calls sequence in your mind or on paper. +Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result. ```smart header="Fixed-length functions only" The currying requires the function to have a fixed number of arguments. diff --git a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md index 31ea4ff88..e4ee78748 100644 --- a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md +++ b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md @@ -5,7 +5,7 @@ Here's the explanations. 2. The same, parentheses do not change the order of operations here, the dot is first anyway. -3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines: +3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines: ```js no-beautify f = obj.go; // calculate the expression @@ -14,7 +14,7 @@ Here's the explanations. Here `f()` is executed as a function, without `this`. -4. The similar thing as `(3)`, to the left of the dot `.` we have an expression. +4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression. To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type. diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index ef843ab79..894db8fc6 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -4,7 +4,7 @@ ```warn header="In-depth language feature" This article covers an advanced topic, to understand certain edge-cases better. -It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood. +It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. ``` A dynamically evaluated method call can lose `this`. @@ -59,7 +59,7 @@ If we put these operations on separate lines, then `this` will be lost for sure: let user = { name: "John", hi() { alert(this.name); } -} +}; *!* // split getting and calling the method in two lines @@ -87,13 +87,13 @@ The result of a property access `user.hi` is not a function, but a value of Refe (user, "hi", true) ``` -When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case). +When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`user` in this case). Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`. Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`. -So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). +So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). ## Summary @@ -106,9 +106,3 @@ That's for the subsequent method call `()` to get the object and set `this` to i For all other operations, the reference type automatically becomes the property value (a function in our case). The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression. - - - - - - result of dot `.` isn't actually a method, but a value of `` needs a way to pass the information about `obj` diff --git a/1-js/99-js-misc/05-bigint/article.md b/1-js/99-js-misc/05-bigint/article.md index 72f06d0c7..2a1cfc843 100644 --- a/1-js/99-js-misc/05-bigint/article.md +++ b/1-js/99-js-misc/05-bigint/article.md @@ -50,7 +50,7 @@ The conversion operations are always silent, never give errors, but if the bigin ````smart header="The unary plus is not supported on bigints" The unary plus operator `+value` is a well-known way to convert `value` to a number. -On bigints it's not supported, to avoid confusion: +In order to avoid confusion, it's not supported on bigints: ```js run let bigint = 1n; @@ -126,5 +126,5 @@ We can use such JSBI code "as is" for engines that don't support bigints and for ## References -- [MDN docs on BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). +- [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt). - [Specification](https://tc39.es/ecma262/#sec-bigint-objects). diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 000000000..4f144f824 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```warn header="Advanced knowledge" +The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, the length of such symbols is `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare Chinese character +``` + +That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! + +We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. + +Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 000000000..777bf703c --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

+ +To increase page loading speed, it would be reasonable to download and display photo thumbnails in *compressed* quality. +But, to create a collage from selected photos, download and use them in *full-size* quality. + +Below, we can see, that the intrinsic size of the thumbnails is 240x240 pixels. +The size was chosen on purpose to increase loading speed. +Moreover, we do not need full-size photos in preview mode. + +![](weakref-finalizationregistry-demo-02.png) + +
+Let's assume, that we need to create a collage of 4 photos: we select them, and then click the "Create collage" button. +At this stage, the already known to us weakRefCache function checks whether the required image is in the cache. +If not, it downloads it from the cloud and puts it in the cache for further use. +This happens for each selected image: +

+ +![](weakref-finalizationregistry-demo-03.gif) + +
+ +Paying attention to the output in the console, you can see, which of the photos were downloaded from the cloud - this is indicated by FETCHED_IMAGE. +Since this is the first attempt to create a collage, this means, that at this stage the "weak cache" was still empty, and all the photos were downloaded from the cloud and put in it. + +But, along with the process of downloading images, there is also a process of memory cleanup by the garbage collector. +This means, that the object stored in the cache, which we refer to, using a weak reference, is deleted by the garbage collector. +And our finalizer executes successfully, thereby deleting the key, by which the image was stored in the cache. +CLEANED_IMAGE notifies us about it: + +![](weakref-finalizationregistry-demo-04.jpg) + +
+Next, we realize that we do not like the resulting collage, and decide to change one of the images and create a new one. +To do this, just deselect the unnecessary image, select another one, and click the "Create collage" button again: +

+ +![](weakref-finalizationregistry-demo-05.gif) + +
+But this time not all images were downloaded from the network, and one of them was taken from the weak cache: the CACHED_IMAGE message tells us about it. +This means that at the time of collage creation, the garbage collector had not yet deleted our image, and we boldly took it from the cache, +thereby reducing the number of network requests and speeding up the overall time of the collage creation process: +

+ +![](weakref-finalizationregistry-demo-06.jpg) + +
+Let's "play around" a little more, by replacing one of the images again and creating a new collage: +

+ +![](weakref-finalizationregistry-demo-07.gif) + +
+This time the result is even more impressive. Of the 4 images selected, 3 of them were taken from the weak cache, and only one had to be downloaded from the network. +The reduction in network load was about 75%. Impressive, isn't it? +

+ +![](weakref-finalizationregistry-demo-08.jpg) + +
+ +Of course, it is important to remember, that such behavior is not guaranteed, and depends on the specific implementation and operation of the garbage collector. + +Based on this, a completely logical question immediately arises: why do not we use an ordinary cache, where we can manage its entities ourselves, instead of relying on the garbage collector? +That's right, in the vast majority of cases there is no need to use `WeakRef` and `FinalizationRegistry`. + +Here, we simply demonstrated an alternative implementation of similar functionality, using a non-trivial approach with interesting language features. +Still, we cannot rely on this example, if we need a constant and predictable result. + +You can [open this example in the sandbox](sandbox:weakref-finalizationregistry). + +## Summary + +`WeakRef` - designed to create weak references to objects, allowing them to be deleted from memory by the garbage collector if there are no longer strong references to them. +This is beneficial for addressing excessive memory usage and optimizing the utilization of system resources in applications. + +`FinalizationRegistry` - is a tool for registering callbacks, that are executed when objects that are no longer strongly referenced, are destroyed. +This allows releasing resources associated with the object or performing other necessary operations before deleting the object from memory. \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png new file mode 100644 index 000000000..021637342 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css new file mode 100644 index 000000000..f6df812d0 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css @@ -0,0 +1,49 @@ +.app { + display: flex; + flex-direction: column; + gap: 16px; +} + +.start-messages { + width: fit-content; +} + +.window { + width: 100%; + border: 2px solid #464154; + overflow: hidden; +} + +.window__header { + position: sticky; + padding: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #736e7e; +} + +.window__title { + margin: 0; + font-size: 24px; + font-weight: 700; + color: white; + letter-spacing: 1px; +} + +.window__button { + padding: 4px; + background: #4f495c; + outline: none; + border: 2px solid #464154; + color: white; + font-size: 16px; + cursor: pointer; +} + +.window__body { + height: 250px; + padding: 16px; + overflow: scroll; + background-color: #736e7e33; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html new file mode 100644 index 000000000..7f93af4c7 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html @@ -0,0 +1,28 @@ + + + + + + + WeakRef DOM Logger + + + + +
+ +
+
+

Messages:

+ +
+
+ No messages. +
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 000000000..ea55b4478 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 000000000..2a507dbcd --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 000000000..6cc199a12 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 000000000..949a14f9f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 000000000..1177d6580 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 000000000..e738f8e7e --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png new file mode 100644 index 000000000..fc33a023a Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 000000000..7d8bb01e8 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 000000000..b81966dda Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 000000000..ba60f1e86 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 000000000..d34bda4d7 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 000000000..b2655540f Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 000000000..51f874518 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 000000000..5f98aec14 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 000000000..e6c9e3960 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 000000000..7ce52f927 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +
+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 000000000..983b34d9a --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 56b568833..eedc28fb3 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,10 +1,10 @@ # Browser environment, specs -The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms. +The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. -A platform may be a browser, or a web-server or another *host*, even a "smart" coffee machine, if it can run JavaScript. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*. +A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*. -A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. +A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. Here's a bird's-eye view of what we have when JavaScript runs in a web browser: @@ -15,9 +15,9 @@ There's a "root" object called `window`. It has two roles: 1. First, it is a global object for JavaScript code, as described in the chapter . 2. Second, it represents the "browser window" and provides methods to control it. -For instance, here we use it as a global object: +For instance, we can use it as a global object: -```js run +```js run global function sayHi() { alert("Hello"); } @@ -26,17 +26,17 @@ function sayHi() { window.sayHi(); ``` -And here we use it as a browser window, to see the window height: +And we can use it as a browser window, to show the window height: ```js run alert(window.innerHeight); // inner window height ``` -There are more window-specific methods and properties, we'll cover them later. +There are more window-specific methods and properties, which we'll cover later. ## DOM (Document Object Model) -Document Object Model, or DOM for short, represents all page content as objects that can be modified. +The Document Object Model, or DOM for short, represents all page content as objects that can be modified. The `document` object is the main "entry point" to the page. We can change or create anything on the page using it. @@ -49,18 +49,18 @@ document.body.style.background = "red"; setTimeout(() => document.body.style.background = "", 1000); ``` -Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). +Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). ```smart header="DOM is not only for browsers" The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too. -For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though. +For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though. ``` ```smart header="CSSOM for styling" There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them. -CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. +The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. ``` ## BOM (Browser Object Model) @@ -69,7 +69,7 @@ The Browser Object Model (BOM) represents additional objects provided by the bro For instance: -- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). +- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc). - The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one. Here's how we can use the `location` object: @@ -81,12 +81,12 @@ if (confirm("Go to Wikipedia?")) { } ``` -Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. +The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user. ```smart header="Specifications" -BOM is the part of the general [HTML specification](https://html.spec.whatwg.org). +The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org). -Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . +Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . ``` ## Summary @@ -94,20 +94,20 @@ Yes, you heard that right. The HTML spec at is no Talking about standards, we have: DOM specification -: Describes the document structure, manipulations and events, see . +: Describes the document structure, manipulations, and events, see . CSSOM specification -: Describes stylesheets and style rules, manipulations with them and their binding to documents, see . +: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see . HTML specification : Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods. Additionally, some classes are described separately at . -Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything. +Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all. -When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g , . -Now we'll get down to learning DOM, because the document plays the central role in the UI. +Now, we'll get down to learning the DOM, because the document plays the central role in the UI. diff --git a/2-ui/1-document/01-browser-environment/windowObjects.svg b/2-ui/1-document/01-browser-environment/windowObjects.svg index d1b280ee8..b7e18bb34 100644 --- a/2-ui/1-document/01-browser-environment/windowObjects.svg +++ b/2-ui/1-document/01-browser-environment/windowObjects.svg @@ -1 +1 @@ -windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM \ No newline at end of file +windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 019398be9..f7f2be91d 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -51,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -143,7 +143,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); ````warn header="Tables always have ``" -An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in the DOM automatically. +An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically. For the HTML: @@ -160,7 +160,7 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); -You see? The `` appeared out of nowhere. You should keep this in mind while working with tables to avoid surprises. +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. ```` ## Other node types @@ -188,7 +188,7 @@ For example, comments:
@@ -199,7 +199,7 @@ We may think -- why is a comment added to the DOM? It doesn't affect the visual **Everything in HTML, even comments, becomes a part of the DOM.** -Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. We are not going to touch that node, we even don't draw it on diagrams for that reason, but it's there. +Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there. The `document` object that represents the whole document is, formally, a DOM node as well. @@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg index c0096060a..eb99f193f 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole0.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg index db92359d5..02ef5f0a6 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole1.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg index 19ea221d2..448eea9d1 100644 --- a/2-ui/1-document/02-dom-nodes/elk.svg +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg index 658ee5ea2..60696ec0d 100644 --- a/2-ui/1-document/02-dom-nodes/inspect.svg +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index f7123d70d..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Element-only navigation -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg index a9ce1fd8d..fd0b2826a 100644 --- a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg +++ b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg @@ -1 +1 @@ -document.documentElement <HTML>document.body (if inside body)parent Element<DIV>next Element Siblingprevious Element Siblingchildrenfirst Element Child last Element Child \ No newline at end of file +document.documentElement <HTML>document.body (if inside body)parent Element<DIV>next Element Siblingprevious Element Siblingchildrenfirst Element Child last Element Child \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/dom-links.svg b/2-ui/1-document/03-dom-navigation/dom-links.svg index 126530e93..6c34bca4a 100644 --- a/2-ui/1-document/03-dom-navigation/dom-links.svg +++ b/2-ui/1-document/03-dom-navigation/dom-links.svg @@ -1 +1 @@ -documentdocument.documentElement <HTML>document.body (if inside body)parentNode<DIV>nextSiblingpreviousSiblingchildNodesfirstChild lastChild \ No newline at end of file +documentdocument.documentElement <HTML>document.body (if inside body)parentNode<DIV>nextSiblingpreviousSiblingchildNodesfirstChild lastChild \ No newline at end of file diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index f5ab0b785..405129694 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element: ``` ```warn header="Please don't use id-named global variables to access elements" -This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's kind of standard. But it is supported mainly for compatibility. +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from. @@ -71,7 +71,7 @@ If there are multiple elements with the same `id`, then the behavior of methods ``` ```warn header="Only `document.getElementById`, not `anyElem.getElementById`" -The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. +The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document. ``` ## querySelectorAll [#querySelectorAll] @@ -116,7 +116,7 @@ In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but t Previous methods were searching the DOM. -The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. +The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us. @@ -142,7 +142,7 @@ For instance: *Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. -The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned. @@ -154,7 +154,7 @@ For instance:
  • Chapter 1
  • -
  • Chapter 1
  • +
  • Chapter 2
@@ -363,7 +363,7 @@ There are 6 main methods to search for nodes in DOM: -By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts. +By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts. Besides that: diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index 76469c187..99dde5bcd 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -10,7 +10,7 @@ Different DOM nodes may have different properties. For instance, an element node Each DOM node belongs to the corresponding built-in class. -The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. Here's the picture, explanations to follow: @@ -18,16 +18,39 @@ Here's the picture, explanations to follow: The classes are: -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. -- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. -- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. -- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by concrete HTML elements: +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything. + + Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. + +- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. + + It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality). + +- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. + + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

Hello

`. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements. + + It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. + + A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`. + +- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time. + + It is inherited by concrete HTML elements: - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `` elements, - - ...and so on, each tag has its own class that may provide specific properties and methods. + - ...and so on. + +There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, `
`, `
` do not have any specific properties, so they are instances of `HTMLElement` class. -So, the full set of properties and methods of a given node comes as the result of the inheritance. +So, the full set of properties and methods of a given node comes as the result of the chain of inheritance. For example, let's consider the DOM object for an `` element. It belongs to [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class. @@ -128,13 +151,13 @@ For instance: ```html run - ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run @@ -298,7 +298,7 @@ For instance, here for the order state the attribute `order-state` is used:
``` -Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`? +Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`? Because an attribute is more convenient to manage. The state can be changed as easy as: diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index e127bc0ef..40c75dff3 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -6,7 +6,7 @@ importance: 5 We have an empty DOM element `elem` and a string `text`. -Which of these 3 commands do exactly the same? +Which of these 3 commands will do exactly the same? 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md index 15238fcf4..1414e90c1 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md @@ -39,15 +39,19 @@ The clock-managing functions: ```js let timerId; -function clockStart() { // run the clock - timerId = setInterval(update, 1000); +function clockStart() { // run the clock + if (!timerId) { // only set a new interval if the clock is not running + timerId = setInterval(update, 1000); + } update(); // (*) } function clockStop() { clearInterval(timerId); - timerId = null; + timerId = null; // (**) } ``` Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then. + +Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`. diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html index 1bf642b10..84ee26f19 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html @@ -43,15 +43,19 @@ } function clockStart() { - timerId = setInterval(update, 1000); + // set a new interval only if the clock is stopped + // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again + if (!timerId) { + timerId = setInterval(update, 1000); + } update(); // <-- start right now, don't wait 1 second till the first setInterval works } function clockStop() { clearInterval(timerId); + timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart() } - clockStart(); diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b9..3d1f6698f 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ The HTML in the task is incorrect. That's the reason of the odd thing. -The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
`. +The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`. Now it's obvious that when we remove the table, it remains. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
`. +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`. The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index f87074dba..861f70503 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -22,6 +22,6 @@ Why does that happen? alert(table); // the table, as it should be table.remove(); - // why there's still aaa in the document? + // why there's still "aaa" in the document? ``` diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index 43b0a34a7..a57e7e2d9 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -10,7 +10,7 @@ For every list item: 1. Ask a user about its content using `prompt`. 2. Create the `
  • ` with it and add it to `
      `. -3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt). +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). All elements should be created dynamically. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 9a2677df4..75ce1fbb0 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -61,7 +61,7 @@ let div = document.createElement('div'); // 2. Set its class to "alert" div.className = "alert"; -// Fill it with the content +// 3. Fill it with the content div.innerHTML = "Hi there! You've read an important message."; ``` diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg index 6e1fb4874..0843713ce 100644 --- a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg +++ b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg @@ -1 +1 @@ -ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file +ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.svg b/2-ui/1-document/07-modifying-document/insert-adjacent.svg index 64beee037..e26fd023a 100644 --- a/2-ui/1-document/07-modifying-document/insert-adjacent.svg +++ b/2-ui/1-document/07-modifying-document/insert-adjacent.svg @@ -1 +1 @@ -ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file +ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 9154d43d6..46aaa3b00 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // back to normal If we set `style.display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style.display` property at all. +Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this: + +```js run +document.body.style.background = 'red'; //set background to red + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second +``` + ````smart header="Full rewrite with `style.cssText`" Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. @@ -261,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: - -```html run - - -``` ```` ```smart header="Styles applied to `:visited` links are hidden!" diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg index ca8bbc3bd..f5bd9f4f9 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index 13e245ebb..66f28115f 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -17,8 +17,8 @@ As a sample element to demonstrate properties we'll use the one given below: width: 300px; height: 200px; border: 25px solid #E8C48F; - padding: 20px; - overflow: auto; + padding: 20px; + overflow: auto; } ``` @@ -106,7 +106,7 @@ Geometry properties are calculated only for displayed elements. If an element (or any of its ancestors) has `display:none` or is not in the document, then all geometry properties are zero (or `null` for `offsetParent`). -For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or it's ancestor) has `display:none`. +For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or its ancestor) has `display:none`. We can use this to check if an element is hidden, like this: @@ -116,7 +116,7 @@ function isHidden(elem) { } ``` -Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty `
      `). +Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes. ```` ## clientTop/Left diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg index 3c77f09ac..20a59e18d 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-all.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg index 12bc456a6..e8dd3d60a 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg @@ -1 +1 @@ -clientTop: 25px = borderclientLeft: 41px \ No newline at end of file +clientTop: 25px = borderclientLeft: 41px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg index 5708047d4..8097afa78 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg index fe4b3c69d..2603b05fb 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg index 62de5f572..330d2a7c0 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg @@ -1 +1 @@ -clientWidth: 284px = content widthCSS width: 300pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with padding: 0; width: 300px; \ No newline at end of file +clientWidth: 284px = content widthCSS width: 300pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with padding: 0; width: 300px; \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.svg b/2-ui/1-document/09-size-and-scroll/metric-css.svg index e910a9c87..1f2e5f780 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-css.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-css.svg @@ -1 +1 @@ -padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg index d72a20138..2d108473e 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg @@ -1 +1 @@ -offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file +offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg index 76e20b1bc..4d30d90cc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg index 3c00bed00..7f72de422 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg index 29e25eb6f..75a24e3bc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 10898dbf7..08a2f6576 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -2,11 +2,11 @@ How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript? -For most such requests, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities important enough to consider. +For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider. ## Width/height of the window -To get window width and height we can use `clientWidth/clientHeight` of `document.documentElement`: +To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`: ![](document-client-width-height.svg) @@ -16,12 +16,12 @@ For instance, this button shows the height of your window: ``` -````warn header="Not `window.innerWidth/Height`" -Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So why not to use them instead? +````warn header="Not `window.innerWidth/innerHeight`" +Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead? -If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return width/height of the visible part of the document, available for the content. +If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content. -...And `window.innerWidth/innerHeight` include the scrollbar. +`window.innerWidth/innerHeight` includes the scrollbar. If there's a scrollbar, and it occupies some space, then these two lines show different values: ```js run @@ -29,7 +29,7 @@ alert( window.innerWidth ); // full window width alert( document.documentElement.clientWidth ); // window width minus the scrollbar ``` -In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`. +In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`. ```` ```warn header="`DOCTYPE` is important" @@ -40,9 +40,9 @@ In modern HTML we should always write `DOCTYPE`. ## Width/height of the document -Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure document full size as `document.documentElement.scrollWidth/scrollHeight`. +Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`. -But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right? +But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Weird, right? To reliably obtain the full document height, we should take the maximum of these properties: @@ -60,11 +60,11 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a " ## Get the current scroll [#page-scroll] -DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`. +DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties. -For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. +For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. -Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`: +Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`: ```js run alert('Current scroll from the top: ' + window.pageYOffset); @@ -73,19 +73,25 @@ alert('Current scroll from the left: ' + window.pageXOffset); These properties are read-only. +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + ## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] ```warn -To scroll the page from JavaScript, its DOM must be fully built. +To scroll the page with JavaScript, its DOM must be fully built. -For instance, if we try to scroll the page from the script in ``, it won't work. +For instance, if we try to scroll the page with a script in ``, it won't work. ``` Regular elements can be scrolled by changing `scrollTop/scrollLeft`. -We can do the same for the page using `document.documentElement.scrollTop/Left` (except Safari, where `document.body.scrollTop/Left` should be used instead). +We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead). -Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). +Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). - The method `scrollBy(x,y)` scrolls the page *relative to its current position*. For instance, `scrollBy(0,10)` scrolls the page `10px` down. @@ -106,28 +112,28 @@ These methods work for all browsers the same way. ## scrollIntoView -For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument: -- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top. -- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom. +- If `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element will be aligned with the window top. +- If `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element will be aligned with the window bottom. ```online -The button below scrolls the page to make itself show at the window top: +The button below scrolls the page to position itself at the window top: -And this button scrolls the page to show it at the bottom: +And this button scrolls the page to position itself at the bottom: ``` ## Forbid the scrolling -Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. +Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. -To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll. +To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position. ```online Try it: @@ -136,20 +142,20 @@ Try it: -The first button freezes the scroll, the second one resumes it. +The first button freezes the scroll, while the second one releases it. ``` -We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`. +We can use the same technique to freeze the scroll for other elements, not just for `document.body`. -The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it. +The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it. -That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same. +That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same. ## Summary Geometry: -- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height` +- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight` - Width/height of the whole document, with the scrolled out part: ```js diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg index b0dff4d4e..65e77ae80 100644 --- a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg +++ b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg @@ -1 +1 @@ -documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file +documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index fb55edf57..fc605c414 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,7 @@ Additionally, there are derived properties: ```online For instance click this button to see its window coordinates: -

      +

      ``` -Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `mouse.onclick`, so event handlers are totally separate. +Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `menu.onclick`, so event handlers are totally separate. The output order becomes: 1 -> 2 -> nested. @@ -283,9 +282,9 @@ Other constructors of native events like `MouseEvent`, `KeyboardEvent` and so on For custom events we should use `CustomEvent` constructor. It has an additional option named `detail`, we should assign the event-specific data to it. Then all handlers can access it as `event.detail`. -Despite the technical possibility to generate browser events like `click` or `keydown`, we should use with the great care. +Despite the technical possibility of generating browser events like `click` or `keydown`, we should use them with great care. -We shouldn't generate browser events as it's a hacky way to run handlers. That's a bad architecture most of the time. +We shouldn't generate browser events as it's a hacky way to run handlers. That's bad architecture most of the time. Native events might be generated: diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md index 551bc564a..9574b0c83 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/article.md +++ b/2-ui/3-event-details/1-mouse-events-basics/article.md @@ -1,3 +1,4 @@ + # Mouse events In this chapter we'll get into more details about mouse events and their properties. @@ -24,7 +25,7 @@ We've already seen some of these events: : Triggers after two clicks on the same element within a short timeframe. Rarely used nowadays. `contextmenu` -: Triggers when when the right mouse button is pressed. There are other ways to open a context menu, e.g. using a special keyboard key, it triggers in that case also, so it's not exactly the mouse event. +: Triggers when the right mouse button is pressed. There are other ways to open a context menu, e.g. using a special keyboard key, it triggers in that case also, so it's not exactly the mouse event. ...There are several other events too, we'll cover them later. @@ -34,14 +35,14 @@ As you can see from the list above, a user action may trigger multiple events. For instance, a left-button click first triggers `mousedown`, when the button is pressed, then `mouseup` and `click` when it's released. -In cases when a single action initiates multiple events, their order is fixed. That is, the handlers are called in the order `mousedown` -> `mouseup` -> `click`. +In cases when a single action initiates multiple events, their order is fixed. That is, the handlers are called in the order `mousedown` -> `mouseup` -> `click`. ```online Click the button below and you'll see the events. Try double-click too. -On the teststand below all mouse events are logged, and if there is more than a 1 second delay between them they are separated by a horizontal ruler. +On the teststand below, all mouse events are logged, and if there is more than a 1 second delay between them, they are separated by a horizontal rule. -Also we can see the `button` property that allows to detect the mouse button, it's explained below. +Also, we can see the `button` property that allows us to detect the mouse button; it's explained below.
      ``` @@ -50,23 +51,23 @@ Also we can see the `button` property that allows to detect the mouse button, it Click-related events always have the `button` property, which allows to get the exact mouse button. -We usually don't use it for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click. +We usually don't use it for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click. -From the other hand, `mousedown` and `mouseup` handlers we may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". +On the other hand, `mousedown` and `mouseup` handlers may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". The possible values of `event.button` are: | Button state | `event.button` | |--------------|----------------| | Left button (primary) | 0 | -| Middle button (auxillary) | 1 | +| Middle button (auxiliary) | 1 | | Right button (secondary) | 2 | | X1 button (back) | 3 | | X2 button (forward) | 4 | Most mouse devices only have the left and right buttons, so possible values are `0` or `2`. Touch devices also generate similar events when one taps on them. -Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons) if you ever need it. +Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](mdn:/api/MouseEvent/buttons) if you ever need it. ```warn header="The outdated `event.which`" Old code may use `event.which` property that's an old non-standard way of getting a button, with possible values: @@ -154,9 +155,9 @@ Move the mouse over the input field to see `clientX/clientY` (the example is in ## Preventing selection on mousedown -Double mouse click has a side-effect that may be disturbing in some interfaces: it selects text. +Double mouse click has a side effect that may be disturbing in some interfaces: it selects text. -For instance, a double-click on the text below selects it in addition to our handler: +For instance, double-clicking on the text below selects it in addition to our handler: ```html autorun height=50 Double-click me diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html index e998165fd..84d52b18c 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html @@ -54,7 +54,7 @@

      Once upon a time there was a mother pig who had three little pigs.

      -

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

      The three little pigs set off. "We will take care that the wolf does not catch us," they said.

      diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html index 2dc4394e7..774e24a21 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html @@ -54,7 +54,7 @@

      Once upon a time there was a mother pig who had three little pigs.

      -

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

      The three little pigs set off. "We will take care that the wolf does not catch us," they said.

      diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js index 4e6e2a3e9..7503ca9c2 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js @@ -88,7 +88,7 @@ class HoverIntent { if (speed < this.sensitivity) { clearInterval(this.checkSpeedInterval); this.isHover = true; - this.over.call(this.elem, event); + this.over.call(this.elem); } else { // speed fast, remember new coordinates as the previous ones this.prevX = this.lastX; diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md index c7ac0d4db..d409c3f12 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md @@ -80,7 +80,7 @@ An important feature of `mouseout` -- it triggers, when the pointer moves from a
      ``` -If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`! +If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`! ![](mouseover-to-child.svg) diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg index 07830295c..6044eff17 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg @@ -1 +1 @@ -mouseoutmouseover#parent#child \ No newline at end of file +mouseoutmouseover#parent#child \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg index 07176ba2d..22335b52e 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg @@ -1 +1 @@ -#TOtargetrelatedTarget = null \ No newline at end of file +#TOtargetrelatedTarget = null \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg index 262ddf596..437f03b10 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg @@ -1 +1 @@ -#TO#FROM<DIV><DIV><DIV>mouseovermouseout \ No newline at end of file +#TO#FROM<DIV><DIV><DIV>mouseovermouseout \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg index 784f435de..1277ddff5 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg @@ -1 +1 @@ -<DIV>mouseovermouseout \ No newline at end of file +<DIV>mouseovermouseout \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg index b38d76fbe..78210845b 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg @@ -1 +1 @@ -mouseoutmouseover#parent#child \ No newline at end of file +mouseoutmouseover#parent#child \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js index 6d87199c2..5752e83ae 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js @@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler; function handler(event) { let type = event.type; - while (type < 11) type += ' '; + while (type.length < 11) type += ' '; log(type + " target=" + event.target.id) return false; diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg index ca8bbc3bd..f5bd9f4f9 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index a47bfade6..4c928eef1 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -18,19 +18,19 @@ The basic Drag'n'Drop algorithm looks like this: 2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. 3. On `mouseup` - perform all actions related to finishing the drag'n'drop. -These are the basics. Later we'll see how to other features, such as highlighting current underlying elements while we drag over them. +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. Here's the implementation of dragging a ball: ```js -ball.onmousedown = function(event) { +ball.onmousedown = function(event) { // (1) prepare to moving: make absolute and on top by z-index ball.style.position = 'absolute'; ball.style.zIndex = 1000; // move it out of any current parents directly into body // to make it positioned relative to the body - document.body.append(ball); + document.body.append(ball); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { @@ -93,14 +93,14 @@ So we should listen on `document` to catch it. ## Correct positioning -In the examples above the ball is always moved so, that it's center is under the pointer: +In the examples above the ball is always moved so that its center is under the pointer: ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` -Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. +Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. It would be better if we keep the initial shift of the element relative to the pointer. @@ -124,7 +124,7 @@ Let's update our algorithm: ```js // onmousemove - // ball has position:absoute + // ball has position:absolute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -219,7 +219,7 @@ That's why the initial idea to put handlers on potential droppables doesn't work So, what to do? -There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). +There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned. We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this: @@ -276,7 +276,7 @@ function onMouseMove(event) { } ``` -In the example below when the ball is dragged over the soccer gate, the gate is highlighted. +In the example below when the ball is dragged over the soccer goal, the goal is highlighted. [codetabs height=250 src="ball4"] @@ -300,4 +300,4 @@ We can lay a lot on this foundation. - We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements. - And so on. -There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-part solution. +There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution. diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html index 36219e303..8751c70ad 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html @@ -13,7 +13,7 @@ diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css index 9b3d3b82d..a84cd5e7e 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/style.css +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -8,6 +8,7 @@ } .thumb { + touch-action: none; width: 10px; height: 25px; border-radius: 3px; diff --git a/2-ui/3-event-details/7-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md index 617852ccf..12fe63201 100644 --- a/2-ui/3-event-details/7-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters To reliably track layout-dependent characters, `event.key` may be a better way. -On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch. +On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch. Do we want to handle layout-dependant keys? Then `event.key` is the way to go. @@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept ```html autorun height=60 run ``` -Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. -Let's relax it a little bit: +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +165,9 @@ function checkPhoneKey(key) { Now arrows and deletion works well. -...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid. +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. ## Legacy @@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more. +## Mobile Keyboards + +When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values). + +While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices. + ## Summary Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS. diff --git a/2-ui/3-event-details/7-keyboard-events/german-layout.svg b/2-ui/3-event-details/7-keyboard-events/german-layout.svg index 8a880e8e0..7ac9a4008 100644 --- a/2-ui/3-event-details/7-keyboard-events/german-layout.svg +++ b/2-ui/3-event-details/7-keyboard-events/german-layout.svg @@ -1 +1 @@ -StrgStrgAl tAlt GrWinWinMenu \ No newline at end of file +StrgStrgAl tAlt GrWinWinMenu \ No newline at end of file diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html index 401062830..a0d5a4f40 100644 --- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html @@ -28,7 +28,7 @@ - + diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js index 5eba24c7a..d97f7a7b5 100644 --- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js @@ -5,6 +5,8 @@ let lastTime = Date.now(); function handle(e) { if (form.elements[e.type + 'Ignore'].checked) return; + area.scrollTop = 1e6; + let text = e.type + ' key=' + e.key + ' code=' + e.code + diff --git a/2-ui/3-event-details/7-keyboard-events/us-layout.svg b/2-ui/3-event-details/7-keyboard-events/us-layout.svg index 699277e02..353f225f1 100644 --- a/2-ui/3-event-details/7-keyboard-events/us-layout.svg +++ b/2-ui/3-event-details/7-keyboard-events/us-layout.svg @@ -1 +1 @@ -Caps LockShiftShift \ No newline at end of file +Caps LockShiftShift \ No newline at end of file diff --git a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md index 10945ccd7..54c101193 100644 --- a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md +++ b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md @@ -55,11 +55,11 @@ function populate() { // document bottom let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom; - // if the user scrolled far enough (<100px to the end) - if (windowRelativeBottom < document.documentElement.clientHeight + 100) { - // let's add more data - document.body.insertAdjacentHTML("beforeend", `

      Date: ${new Date()}

      `); - } + // if the user hasn't scrolled far enough (>100px to the end) + if (windowRelativeBottom > document.documentElement.clientHeight + 100) break; + + // let's add more data + document.body.insertAdjacentHTML("beforeend", `

      Date: ${new Date()}

      `); } } ``` diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md index 7b5cf4848..734bd84c6 100644 --- a/2-ui/3-event-details/8-onscroll/article.md +++ b/2-ui/3-event-details/8-onscroll/article.md @@ -1,6 +1,6 @@ # Scrolling -The `scroll` event allows to react on a page or element scrolling. There are quite a few good things we can do here. +The `scroll` event allows reacting to a page or element scrolling. There are quite a few good things we can do here. For instance: - Show/hide additional controls or information depending on where in the document the user is. @@ -34,4 +34,4 @@ If we add an event handler to these events and `event.preventDefault()` in it, t There are many ways to initiate a scroll, so it's more reliable to use CSS, `overflow` property. -Here are few tasks that you can solve or look through to see the applications on `onscroll`. +Here are few tasks that you can solve or look through to see applications of `onscroll`. diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index 01af1f400..7bc87a0f0 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them. Document forms are members of the special collection `document.forms`. -That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form. +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. ```js no-beautify -document.forms.my - the form with name="my" -document.forms[0] - the first form in the document +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document ``` When we have a form, then any element is available in the named collection `form.elements`. @@ -36,9 +36,9 @@ For instance: ``` -There may be multiple elements with the same name, that's often the case with radio buttons. +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. -In that case `form.elements[name]` is a collection, for instance: +In that case, `form.elements[name]` is a *collection*. For instance: ```html run height=40
      @@ -119,7 +119,7 @@ That's easy to see in an example: ``` -That's usually not a problem, because we rarely change names of form elements. +That's usually not a problem, however, because we rarely change names of form elements. ```` @@ -155,7 +155,7 @@ Let's talk about form controls. ### input and textarea -We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes. +We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons. Like this: @@ -177,18 +177,16 @@ It stores only the HTML that was initially on the page, not the current value. A ``: -1. Find the corresponding `
  • ` is in "edit mode", clicks on other cells are ignored. - The table may have many cells. Use event delegation. diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index fc48c21ff..644d814d9 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -9,4 +9,5 @@ Focus on the mouse. Then use arrow keys to move it: [demo src="solution"] P.S. Don't put event handlers anywhere except the `#mouse` element. + P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index d42013e5b..c253dc11d 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -1,6 +1,6 @@ # Focusing: focus/blur -An element receives a focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus into an element by default when a page loads and other means of getting a focus. +An element receives the focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus onto an element by default when a page loads and other means of getting the focus. Focusing on an element generally means: "prepare to accept the data here", so that's the moment when we can run the code to initialize the required functionality. @@ -18,7 +18,7 @@ Let's use them for validation of an input field. In the example below: -- The `blur` handler checks if the field the email is entered, and if not -- shows an error. +- The `blur` handler checks if the field has an email entered, and if not -- shows an error. - The `focus` handler hides the error message (on `blur` it will be checked again): ```html run autorun height=60 @@ -90,6 +90,8 @@ If we enter something into the input and then try to use `key:Tab` or click away Please note that we can't "prevent losing focus" by calling `event.preventDefault()` in `onblur`, because `onblur` works *after* the element lost the focus. +In practice though, one should think well, before implementing something like this, because we generally *should show errors* to the user, but *should not prevent their progress* in filling our form. They may want to fill other fields first. + ```warn header="JavaScript-initiated focus loss" A focus loss can occur for many reasons. @@ -104,11 +106,11 @@ The best recipe is to be careful when using these events. If we want to track us ``` ## Allow focusing on any element: tabindex -By default many elements do not support focusing. +By default, many elements do not support focusing. The list varies a bit between browsers, but one thing is always correct: `focus/blur` support is guaranteed for elements that a visitor can interact with: `