From 140179666b9ba7d2ab0fe772d816a9e245f77d93 Mon Sep 17 00:00:00 2001 From: Dave Eckhardt Date: Sat, 28 Apr 2018 20:00:03 -0400 Subject: [PATCH 001/392] Add missing word in Chapter 17 --- 17_canvas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/17_canvas.md b/17_canvas.md index 38a801565..e6a90c288 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -307,7 +307,7 @@ determine the curvature of the line, the method is given a ((control point)) as well as a destination point. Imagine this control point as _attracting_ the line, giving it its curve. The line won't go through the control point, but its direction at the start and end points will -be such that a straight in that direction would point towards the +be such that a straight line in that direction would point towards the control point. The following example illustrates this: ```{lang: "text/html"} From 1d27233ee2fa6c6e1590d04c6c6963fe6ba59c77 Mon Sep 17 00:00:00 2001 From: Dave Eckhardt Date: Sat, 28 Apr 2018 19:56:13 -0400 Subject: [PATCH 002/392] Fix singular/plural glitch in level description in Chapter 16 --- 16_game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/16_game.md b/16_game.md index 386e1b5b5..9437fdbb8 100644 --- a/16_game.md +++ b/16_game.md @@ -133,7 +133,7 @@ var simpleLevelPlan = ` Periods are empty space, hash ("#") characters are walls, and plus signs are lava. The ((player))'s starting position is the ((at sign)) -(`@`). Every O characters is a coin, and the equals sign (`=`) at the +(`@`). Every O character is a coin, and the equals sign (`=`) at the top is a block of lava that moves back and forth horizontally. {{index bouncing}} From 8dd30ebd0dec42ce3ad332ff402e23999747e68d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 30 Apr 2018 10:49:15 +0200 Subject: [PATCH 003/392] Clarify sentence in Chapter 15 Issue #436 --- 15_event.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/15_event.md b/15_event.md index a8f9a20d5..e675b1d74 100644 --- a/15_event.md +++ b/15_event.md @@ -64,8 +64,8 @@ argument occurs. {{index "addEventListener method", "event handling", "window object"}} -Each ((browser)) event handler is registered in a context. We called -`addEventListener` on the `window` object before to register a handler +Each ((browser)) event handler is registered in a context. In the last example we called +`addEventListener` on the `window` object to register a handler for the whole window. Such a method can also be found on ((DOM)) elements and some other types of objects. Event listeners are only called when the event happens in the context of the object they are From 4b4e0aeabbe28be05901284d40e6f5f98e16ab8b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 2 May 2018 10:29:13 +0200 Subject: [PATCH 004/392] Integrate tech editing for Chapter 8 --- 08_error.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/08_error.md b/08_error.md index cc74d19bf..35db6c191 100644 --- a/08_error.md +++ b/08_error.md @@ -161,7 +161,7 @@ You could add a comment like this above the `goalOrientedRobot` function from the last chapter, to describe its type. ``` -// (WorldState, Array) → {direction: string, memory: Array} +// (VillageState, Array) → {direction: string, memory: Array} function goalOrientedRobot(state, memory) { // ... } @@ -603,7 +603,7 @@ leaving, it notices that it was aborted at a point where it had created an inconsistent program state, it repairs the damage it did. Note that, even though the `finally` code is run when an exception -leaves the `try` block, it does not interfere with the exception. +is thrown in the `try` block, it does not interfere with the exception. After the `finally` block runs, the stack continues unwinding. {{index "exception safety"}} @@ -861,7 +861,7 @@ block. The corresponding `catch` block should rethrow the exception when it is not an instance of `MultiplicatorUnitFailure` and ensure the call is retried when it is. -To do the retrying, you can either use a loop that breaks only when a +To do the retrying, you can either use a loop that stops only when a call succeeds—as in the [`look` example](error#look) earlier in this chapter—or use ((recursion)) and hope you don't get a string of failures so long that it overflows the stack (which is a pretty safe From 4a6c3c58dee7140f51e4e8c54969af753c9b1f41 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 2 May 2018 11:11:26 +0200 Subject: [PATCH 005/392] Integrate editing for Chapter 13 --- 13_browser.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/13_browser.md b/13_browser.md index cda3afb86..b95245fdf 100644 --- a/13_browser.md +++ b/13_browser.md @@ -19,9 +19,9 @@ no one would ever have paid any attention to it. Web technology has, from the start, been decentralized, not just technically but also in the way it evolved. Various browser vendors -have added new functionality in ad hoc and sometimes poorly thought -out ways, which then, sometimes, ended up being adopted by others—and -finally set down as a ((standard)). +have added new functionality in ad hoc and sometimes poorly thought-out +ways, which then, sometimes, ended up being adopted by others—and +finally set down as in ((standards)). This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved @@ -53,7 +53,7 @@ on the kind of thing that it is trying to express and on the A _network ((protocol))_ describes a style of communication over a ((network)). There are protocols for sending email, for fetching email, -for sharing files, or even for controlling computers that happen to be +for sharing files, and even for controlling computers that happen to be infected by malicious software. {{indexsee "Hypertext Transfer Prototol", HTTP}} @@ -68,7 +68,7 @@ the version of the protocol that it is trying to use. GET /index.html HTTP/1.1 ``` -There's a lot more rules about the way the requester can include more +There are a lot more rules about the way the requester can include more information in the ((request)) and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter ?](http). @@ -158,7 +158,7 @@ domain name to serve web pages. {{index browser}} -If you type the URL we saw into your browser's ((address bar)), it +If you type this URL into your browser's ((address bar)), the browser will try to retrieve and display the ((document)) at that URL. First, your browser has to find out what address _eloquentjavascript.net_ refers to. Then, using the ((HTTP)) protocol, it will make a @@ -220,8 +220,8 @@ _about_ the document, and the body contains the document itself. In this case, the head declares that the title of this document is "My home page" and that it uses the UTF-8 encoding, which is a way to encode Unicode text as binary data. The document's body contains a -heading (`

`, meaning "heading 1"—`

` to `

` produce more -minor headings) and two ((paragraph))s (`

`). +heading (`

`, meaning "heading 1"—`

` to `

` produce +subheadings) and two ((paragraph))s (`

`). {{index "href attribute", "a (HTML tag)"}} @@ -247,7 +247,7 @@ even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` ("less than"), and a closing bracket is written as `>` ("greater than"). In HTML, an ampersand (`&`) character -followed by a word and a semicolon (`;`) is called an _((entity))_, +followed by a name or character code and a semicolon (`;`) is called an _((entity))_, and will be replaced by the character it encodes. {{index "backslash character", "ampersand character", "double-quote character"}} @@ -298,7 +298,7 @@ I will also usually omit the ((doctype)) and `charset` declaration. This is not to be taken as an encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. You should consider the doctype and the `charset` metadata -implicitly present in examples, even when they are not actually shown +to be implicitly present in examples, even when they are not actually shown in the text. {{id script_tag}} @@ -337,7 +337,7 @@ program) from a URL. The _code/hello.js_ file included here contains the same program—`alert("hello!")`. When an HTML page references other URLs as -part of itself, for example an image file or a script—web browsers +part of itself—for example, an image file or a script—web browsers will retrieve them immediately and include them in the page. {{index "script (HTML tag)", "closing tag"}} @@ -420,7 +420,7 @@ the market. After a few years, the balance had shifted to Microsoft's ((Internet Explorer)). At any point where a single ((browser)) was dominant, that browser's vendor would feel entitled to unilaterally invent new features for the Web. Since most users used -the same browser, ((website))s would simply start using those +the most popular browser, ((website))s would simply start using those features—never mind the other browsers. This was the dark age of ((compatibility)), often called the From a92e698b8ad9b27c34706b1e0e518c33699c5509 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 2 May 2018 11:16:01 +0200 Subject: [PATCH 006/392] Integrate editing for Chapter 14 --- 14_dom.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/14_dom.md b/14_dom.md index 822bf9068..62c114e3a 100644 --- a/14_dom.md +++ b/14_dom.md @@ -158,7 +158,7 @@ numbers to access the child nodes. But it is an instance of the Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or -((attribute))s to it. Instead, you have to first create it, then add +((attribute))s to it. Instead, you have to first create it, and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly. @@ -200,8 +200,8 @@ child, `nextSibling` will be null. {{index "children property", "text node", element}} -There's also the `children` property, which is like `childNodes`, but -which only contains element (type 1) children, not other types of +There's also the `children` property, which is like `childNodes` but +contains only element (type 1) children, not other types of child nodes. This can be useful when you aren't interested in text nodes. @@ -234,7 +234,7 @@ console.log(talksAbout(document.body, "book")); {{index "childNodes property", "array-like object"}} -Because `childNodes` is not a real array, we can not loop over it with +Because `childNodes` is not a real array, we cannot loop over it with `for`/`of` and have to run over the index range using a regular `for` loop. @@ -254,7 +254,7 @@ of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the ((whitespace)) between nodes. The -example document's body tag does not have just three children (`

` +example document's `` tag does not have just three children (`

` and two `

` elements) but actually has seven: those three, plus the spaces before, after, and between them. @@ -373,7 +373,7 @@ to replace them. Text nodes are created with the {{index "text node"}} -Given a string, `createTextNode` gives us a text node, which we can +Given a string, `createTextNode` gives us a text node that we can insert into the document to make it show up on the screen. {{index "live data structure", "getElementsByTagName method", "childNodes property"}} @@ -460,7 +460,7 @@ object. This is the case for most commonly used standard attributes. But HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such -attributes will not be present as a property on the element's node. +attributes will not be present as properties on the element's node. Instead, you have to use the `getAttribute` and `setAttribute` methods to work with them. @@ -608,14 +608,14 @@ one takes. We have seen that different HTML elements are drawn differently. Some are displayed as blocks, others inline. Some add styling—`` -makes its content ((bold)) and `` makes it blue and underlines it. +makes its content ((bold)), and `` makes it blue and underlines it. {{index "img (HTML tag)", "default behavior", "style attribute"}} The way an `` tag shows an image or an `` tag causes a link to be followed when it is clicked is strongly tied to the element type. -But the default styling associated with an element, such as the text -color or underline, can be changed by us. Here is an example that uses +But we can change the styling associated with an element, such +as the text color or underline. Here is an example that uses the `style` property: ```{lang: "text/html"} @@ -640,7 +640,7 @@ separated by ((semicolon))s, as in `"color: red; border: none"`. {{index "display (CSS)", layout}} -There are a lot of aspects of the document that can be influenced by +A lot of aspects of the document can be influenced by styling. For example, the `display` property controls whether an element is displayed as a block or an inline element. @@ -699,7 +699,7 @@ letters after them capitalized (`style.fontFamily`). {{indexsee "Cascading Style Sheets", CSS}} -The styling system for HTML is called ((CSS)) for _Cascading Style +The styling system for HTML is called ((CSS)), for _Cascading Style Sheets_. A _((style sheet))_ is a set of rules for how to style elements in a document. It can be given inside a `

+
+

Eloquent JavaScript
3rd edition

This is a book about JavaScript, programming, and the wonders of @@ -15,7 +28,7 @@

Eloquent JavaScript
3rd edition

copy
of the second edition. A paper third edition is being worked on, but it isn't clear yet when it'll be available.

-

+

Cover image

@@ -50,15 +63,11 @@

Eloquent JavaScript
3rd edition

alt="Ghostery" class=logo>.

-

Contents

+ - +
+ +

Contents

  1. Introduction @@ -94,6 +103,10 @@

    (Part 3: Node)

  2. Project: Skill-Sharing Website
+
+ +
From 5ceee43bdab53e3c337000f560719fec7d9bca3d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 7 May 2018 17:07:21 +0200 Subject: [PATCH 018/392] Fix alignment of part headers in TOC --- html/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/index.html b/html/index.html index c89d9a86b..4566eda0c 100644 --- a/html/index.html +++ b/html/index.html @@ -7,10 +7,10 @@ From 90b7ef422e0caab8f68b1c5b5cecec97e342c6cf Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 May 2018 14:00:17 +0200 Subject: [PATCH 022/392] Rip out canvas dependency It appears to no longer even be necessary with current jsdom, and doesn't build cleanly on node 10 Issue #189 --- package.json | 1 - src/run_tests.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/package.json b/package.json index d143edc51..de10280ff 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "uglify-js": "^2.0.0" }, "devDependencies": { - "canvas": "^1.6.7", "jsdom": "^11.2.0", "promise": "^8.0.1" }, diff --git a/src/run_tests.js b/src/run_tests.js index 7d63df8b1..19ea4f423 100644 --- a/src/run_tests.js +++ b/src/run_tests.js @@ -209,8 +209,6 @@ function report(err) { console.log("error raised (" + _console.pos + "): " + msg, err.stack) } -require("canvas/lib/context2d").prototype.drawImage = function() {} - // Gruesome kludgery to make the node chapter tests run let fakeFS = {} From 531c5cea9f643f839503aa767fe36ed221e5c418 Mon Sep 17 00:00:00 2001 From: fenekku Date: Thu, 10 May 2018 08:33:30 -0400 Subject: [PATCH 023/392] Add missing 'to' --- 06_object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_object.md b/06_object.md index 13af98b19..7a36f3fbb 100644 --- a/06_object.md +++ b/06_object.md @@ -470,7 +470,7 @@ console.log(Object.prototype.toString.call([1, 2])); We saw the word _map_ used in the [previous chapter](higher_order#map) for an operation that transforms a data structure by applying a -function its elements. Confusing as it is, in programming the same +function to its elements. Confusing as it is, in programming the same word is also used for a related but rather different thing. {{index "map (data structure)", "ages example", "data structure"}} From c94701501e8534d59eed9f131b6bd5b7a470ed77 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 May 2018 14:46:21 +0200 Subject: [PATCH 024/392] Integrate tech editing for Chapter 11 --- 11_async.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/11_async.md b/11_async.md index ed1995916..72fab7da8 100644 --- a/11_async.md +++ b/11_async.md @@ -383,7 +383,7 @@ and rejections are automatically propagated to the new promise that is returned by `then`. And when a handler throws an exception, this automatically causes the promise produced by its `then` call to be rejected. So if any element in a chain of asynchronous actions fails, -the outcome of the whole chain is marked as rejected, and no regular +the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed. {{index "Promise.reject function", "Promise class"}} @@ -426,6 +426,18 @@ and their outcome determines what kind of value comes next—success when it returns a non-promise value, rejection when it throws an exception, and the outcome of a promise when it returns one of those. +```{test: no} +new Promise((_, reject) => reject(new Error("Fail"))) + .then(value => console.log("Handler 1")) + .catch(reason => { + console.log("Caught failure " + reason); + return "nothing"; + }) + .then(value => console.log("Handler 2", value)); +// → Caught failure Error: Fail +// → Handler 2 nothing +``` + {{index "uncaught exception", "exception handling"}} Much like an uncaught exception is handled by the environment, @@ -901,7 +913,8 @@ one. In a synchronous programming model, it'd be simpler to express. {{index "async function", "await keyword"}} The good news is that JavaScript allows you write pseudo-synchronous -code. An `async` function is a function that implicitly returns a +code to describe asynchronous computation. An `async` function is a +function that implicitly returns a promise and that can, in its body, `await` other promises in a way that _looks_ synchronous. From 3c008777852748af8c4be33700a92fe305f6cc10 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 May 2018 14:50:55 +0200 Subject: [PATCH 025/392] Integrate editing for Chapter 16 --- 16_game.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/16_game.md b/16_game.md index 420d2a207..28fc13147 100644 --- a/16_game.md +++ b/16_game.md @@ -30,7 +30,7 @@ amusing. This chapter will walk through the implementation of a small ((platform game)). Platform games (or "jump and run" games) are games that expect the ((player)) to move a figure through a ((world)), which -is usually two-dimensional and viewed from the side, jumping over and +is usually two-dimensional and viewed from the side, while jumping over and onto things. ## The game @@ -55,7 +55,7 @@ the yellow boxes (coins) while avoiding the red stuff (lava). A The player can walk around with the left and right arrow keys, and jump with the up arrow. Jumping is a specialty of this game character. -It can reach several times its own height and is able to change +It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the onscreen ((avatar)). @@ -133,7 +133,7 @@ var simpleLevelPlan = ` {{index level}} -Periods are empty space, hash ("#") characters are walls, and plus +Periods are empty space, hash (`#`) characters are walls, and plus signs are lava. The ((player))'s starting position is the ((at sign)) (`@`). Every O character is a coin, and the equals sign (`=`) at the top is a block of lava that moves back and forth horizontally. @@ -198,11 +198,11 @@ field types like `"empty"`, `"wall"`, or `"lava"`. {{index "map method"}} -To create these arrays we map over the rows, and then over their +To create these arrays, we map over the rows and then over their content. Remember that `map` passes the array index as a second argument to the mapping function, which tells us the the x- and y-coordinates of a given character. Positions in the game will be -stored as pairs of coordinates, with the top left being 0,0, and each +stored as pairs of coordinates, with the top left being 0,0 and each background square being 1 unit high and wide. {{index "static method"}} @@ -629,7 +629,7 @@ To give an element more than one class, we separate the class names by spaces. In the ((CSS)) code shown next, the `actor` class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don't have to define the `lava` class -again because we reuse the class for the lava grid squares which we +again because we're reusing the class for the lava grid squares we defined earlier. ```{lang: "text/css"} @@ -901,7 +901,7 @@ State.prototype.update = function(time, keys) { }; ``` -It is passed a time step and a data structure that tells it which keys +The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the `update` method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base @@ -929,7 +929,7 @@ function overlap(actor1, actor2) { If any actor does overlap, its `collide` method gets a chance to update the state. Touching a lava actor sets the game status to -`"lost"`, coins vanish when you touch them, and set the status to +`"lost"`, coins vanish when you touch them and set the status to `"won"` when this was the last coin. ```{includeCode: true} @@ -970,7 +970,7 @@ Lava.prototype.update = function(time, state) { {{index bouncing, multiplication, "Vect class", "collision detection"}} -It computes a new position by adding the product of the ((time)) step +This `update` method computes a new position by adding the product of the ((time)) step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the ((lava)) block—dripping lava has a `reset` @@ -1213,7 +1213,7 @@ async function runGame(plans, Display) { Because we made `runLevel` return a promise, `runGame` can be written using an `async` function, as seen in [Chapter ?](async). It returns -another promise, which resolves when the player finished the game. +another promise, which resolves when the player finishes the game. {{index game, "GAME_LEVELS data set"}} From 97635e6cad14dc37eb54208448f73ec1c1fea90a Mon Sep 17 00:00:00 2001 From: fenekku Date: Fri, 11 May 2018 10:30:23 -0400 Subject: [PATCH 026/392] Link to Chapter 7 programming environment While trying to make sure I didn't break anything in the pdf, I realized I wasn't able to build it. So I fixed the needed packages, but there seems to be a missing file (book.idx I think?) that still prevents `make book.pdf` from running properly. --- 07_robot.md | 7 ++++--- README.md | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/07_robot.md b/07_robot.md index 75578d45b..076415982 100644 --- a/07_robot.md +++ b/07_robot.md @@ -347,9 +347,10 @@ isn't planning ahead very well. We'll address that soon. {{if interactive For a more pleasant perspective on the simulation, you can use the -`runRobotAnimation` function that's available in this chapter's -programming environment. This runs the simulation, but instead of -outputting text, it shows you the robot moving around the village map. +`runRobotAnimation` function that's available in [this chapter's +programming environment](https://eloquentjavascript.net/code/#7). +This runs the simulation, but instead of outputting text, it shows +you the robot moving around the village map. ```{test: no} runRobotAnimation(VillageState.random(), randomRobot); diff --git a/README.md b/README.md index d3708def0..32d8a8c38 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,5 @@ Feedback welcome, in the form of issues and pull requests. To build the PDF file: - apt-get install texlive texlive-xetex fonts-inconsolata fonts-symbola texlive-fonts-chinese + apt-get install texlive texlive-xetex fonts-inconsolata fonts-symbola texlive-lang-chinese inkscape make book.pdf From 1d0389cafaa00e33a0592ec80cfb113911e15942 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 12 May 2018 23:22:12 +0200 Subject: [PATCH 027/392] Don't pre-load code that adds 'peanut teeth' event in Chapter 4 --- 04_data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04_data.md b/04_data.md index 2f32e9fd8..0191aaaa1 100644 --- a/04_data.md +++ b/04_data.md @@ -799,7 +799,7 @@ a significant negative effect. Interesting. Let's try something. -```{includeCode: strip_log} +``` for (let entry of JOURNAL) { if (entry.events.includes("peanuts") && !entry.events.includes("brushed teeth")) { From ad92d27e96d944da40630b00d2aecfce6905255c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 15 May 2018 09:07:39 +0200 Subject: [PATCH 028/392] Integrate editing for Chapter 17 --- 17_canvas.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index e84109f40..053af26a2 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -435,7 +435,7 @@ if}} {{index "pie chart example"}} Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your -first assignment is to draw a pie chart of their customer satisfaction +first assignment is to draw a pie chart of its customer satisfaction ((survey)) results. The `results` binding contains an array of objects that represent the @@ -630,7 +630,7 @@ up an interval (repeated timer) to draw the next ((frame)): {{index "remainder operator", "% operator"}} -The `cycle` binding tracks our position in the ((animation)). Each +The `cycle` binding tracks our position in the ((animation)). For each ((frame)), it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the @@ -686,7 +686,7 @@ be position -100. {{index "drawImage method"}} So to turn a picture around, we can't simply add `cx.scale(-1, 1)` -before the call to `drawImage` since that would move our picture +before the call to `drawImage` because that would move our picture outside of the ((canvas)), where it won't be visible. You could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, @@ -782,10 +782,10 @@ It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever -transformation the code that called the function was using. Then, the +transformation the code that called the function was using. Then the function does its thing (on top of the existing transformation), possibly adding more transformations. And finally, we revert to the -transformation that we started with. +transformation we started with. {{index "save method", "restore method"}} @@ -801,7 +801,7 @@ transformation. The `branch` function in the following example illustrates what you can do with a function that changes the transformation and then calls -another function (in this case itself), which continues drawing with +a function (in this case itself), which continues drawing with the given transformation. This function draws a treelike shape by drawing a line, moving the @@ -899,7 +899,7 @@ class CanvasDisplay { } ``` -The `setState` method first computes a new viewport, and then draws +The `setState` method first computes a new viewport and then draws the game scene at the appropriate position. ```{sandbox: "game", includeCode: true} @@ -915,7 +915,7 @@ CanvasDisplay.prototype.setState = function(state) { Contrary to `DOMDisplay`, this display style _does_ have to redraw the background on every update. Because shapes on a canvas are just -((pixel))s, after we draw them, there is no good way to move them (or +((pixel))s, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position. @@ -952,8 +952,8 @@ CanvasDisplay.prototype.updateViewport = function(state) { The calls to `Math.max` and `Math.min` ensure that the viewport does not end up showing space outside of the level. `Math.max(x, 0)` makes -sure the resulting number is not less than zero. `Math.min`, -similarly, guarantees that a value stays below a given bound. +sure the resulting number is not less than zero. `Math.min` +similarly guarantees that a value stays below a given bound. When ((clearing)) the display, we'll use a slightly different ((color)) depending on whether the game is won (brighter) or lost @@ -1158,8 +1158,8 @@ blocks of text. {{index zooming, SVG}} SVG can be used to produce ((crisp)) ((graphics)) that look good at -any zoom level. Contrary to HTML, it is actually designed for drawing, -and thus more suitable for that purpose. +any zoom level. Unlike HTML, it is designed for drawing +and is thus more suitable for that purpose. {{index DOM, SVG, "event handling"}} @@ -1182,7 +1182,7 @@ pixel surface gives canvas a lower cost per shape. {{index "ray tracer"}} There are also effects, such as rendering a scene one pixel at a time -(for example using a ray tracer) or postprocessing an image with +(for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a ((pixel))-based approach. @@ -1341,11 +1341,11 @@ hint}} [Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a -pleasing-looking way to automatically position this text, which would +pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave ample room for their labels. -You might again need `Math.sin` and `Math.cos`, as described in +You might need `Math.sin` and `Math.cos` again, which are described in [Chapter ?](dom#sin_cos). {{if interactive @@ -1390,7 +1390,7 @@ but rather move the text out to the side of the pie by a given number of pixels. The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The -following code finds a position on this line, 120 pixels from the +following code finds a position on this line 120 pixels from the center: ```{test: no} @@ -1400,9 +1400,9 @@ let textY = Math.sin(middleAngle) * 120 + centerY; ``` For `textBaseline`, the value `"middle"` is probably appropriate when -using this approach. What to use for `textAlign` depends on the side +using this approach. What to use for `textAlign` depends on which side of the circle we are on. On the left, it should be `"right"`, and on -the right, it should be `"left"` so that the text is positioned away +the right, it should be `"left"`, so that the text is positioned away from the pie. {{index "Math.cos function"}} @@ -1464,8 +1464,8 @@ whole circle. Then fill the path. To model the ball's position and ((speed)), you can use the `Vec` class from [Chapter ?](game#vector)[ (which is available on this page)]{if interactive}. Give it a starting speed, preferably one that -is not purely vertical or horizontal, and every ((frame)), multiply -that speed with the amount of time that elapsed. When the ball gets +is not purely vertical or horizontal, and for every ((frame)) multiply +that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall. @@ -1481,9 +1481,9 @@ hint}} {{index optimization, "bitmap graphics", mirror}} One unfortunate thing about ((transformation))s is that they slow down -drawing of bitmaps. The position and size of each ((pixel)) has to be -transformed, and though it is possible that ((browser))s will get more -clever about this in the ((future)), this currently causes a +the drawing of bitmaps. The position and size of each ((pixel)) has to be +transformed, and though it is possible that ((browser))s will get +cleverer about transformation in the ((future)), they currently cause a measurable increase in the time it takes to draw a bitmap. In a game like ours, where we are drawing only a single transformed From e232dd328a5491acfcb7750624e41c2c08bdbe4f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 15 May 2018 09:13:56 +0200 Subject: [PATCH 029/392] Integrate tech editing for Chapter 12 --- code/solutions/12_1_arrays.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/solutions/12_1_arrays.js b/code/solutions/12_1_arrays.js index f31d7bcd8..e857c914b 100644 --- a/code/solutions/12_1_arrays.js +++ b/code/solutions/12_1_arrays.js @@ -1,8 +1,8 @@ -topEnv.array = (...values) => values; +topScope.array = (...values) => values; -topEnv.length = array => array.length; +topScope.length = array => array.length; -topEnv.element = (array, i) => array[i]; +topScope.element = (array, i) => array[i]; run(` do(define(sum, fun(array, From 91508db3dc5be56357e91e083be98ba735512671 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 16 May 2018 08:46:29 +0200 Subject: [PATCH 030/392] Link to Amazon pre-order page for 3rd edition --- html/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/index.html b/html/index.html index 4993bed06..b617abb33 100644 --- a/html/index.html +++ b/html/index.html @@ -25,8 +25,8 @@

Eloquent JavaScript
3rd edition

This is a book about JavaScript, programming, and the wonders of the digital. You can read it online here, or get your own paperback - copy of the second edition. A paper third edition is - being worked on, but it isn't clear yet when it'll be available.

+ copy of the second edition. A paper third edition is expected to be available this October.

+ .

Cover image From c1ad6ae5709d31895e9ea738bc20c31ca6c08d07 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 17 May 2018 10:21:29 +0200 Subject: [PATCH 031/392] Integrate editing for Chapter 18 --- 09_regexp.md | 6 ++--- 18_http.md | 56 +++++++++++++++++++++++----------------------- 21_skillsharing.md | 2 +- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/09_regexp.md b/09_regexp.md index 408ad0ff8..fdb139cad 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -836,7 +836,7 @@ When creating the `\b` ((boundary)) markers, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the `RegExp` constructor contains the options for the regular expression—in this -case, `"gi"` for global and case-insensitive. +case, `"gi"` for global and case insensitive. But what if the name is `"dea+hl[]rd"` because our user is a ((nerd))y teenager? That would result in a nonsensical regular expression that @@ -1223,7 +1223,7 @@ starting position of the match. Their `replace` method can replace matches of a pattern with a replacement string or function. Regular expressions can have options, which are written after the -closing slash. The `i` option makes the match case-insensitive. The +closing slash. The `i` option makes the match case insensitive. The `g` option makes the expression _global_, which, among other things, causes the `replace` method to replace all instances instead of just the first. The `y` option makes it sticky, which means that it will @@ -1421,7 +1421,7 @@ digits _or_ a dot followed by one or more digits. {{index exponent, "case sensitivity", ["regular expression", flags]}} -Finally, to make the _e_ case-insensitive, either add an `i` option to +Finally, to make the _e_ case insensitive, either add an `i` option to the regular expression or use `[eE]`. hint}} diff --git a/18_http.md b/18_http.md index 231ef2972..e589a351c 100644 --- a/18_http.md +++ b/18_http.md @@ -74,7 +74,7 @@ it to `DELETE` its main page, it'll probably refuse. {{index [path, URL], GitHub}} -The part after the ((method)) name is the path of the ((resource)) the +The part after the ((method)) name is the path of the _((resource))_ the request applies to. In the simplest case, a resource is simply a ((file)) on the ((server)), but the protocol doesn't require it to be. A resource may be anything that can be transferred _as if_ it is a @@ -89,7 +89,7 @@ After the resource path, the first line of the request mentions is using. In practice, many sites use HTTP version 2, which supports the same -concepts as version 1.1, but is a lot more complicated so that it can +concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless which version is used. Because version @@ -230,7 +230,7 @@ console.log(decodeURIComponent("Yes%3F")); If we change the `method` attribute of the HTML form in the example we saw earlier to `POST`, the ((HTTP)) request made to submit the ((form)) will use the `POST` method and put the ((query string)) in -body of the request, rather than adding it to the URL. +the body of the request, rather than adding it to the URL. ```{lang: http} POST /example/message.html HTTP/1.1 @@ -275,21 +275,21 @@ fetch("example/data.txt").then(response => { Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object -that treats its keys (the header names) as case-insensitive, because +that treats its keys (the header names) as case insensitive, because header names are not supposed to be case sensitive. This means that `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It _might_ also be -rejected, if there is a network error or the ((server)) that the +rejected, if there is a network error or if the ((server)) that the request is addressed to can't be found. {{index [path, URL], "relative URL"}} The first argument to `fetch` is the URL that should be requested. When that ((URL)) doesn't start with a protocol name (such as _http:_) -it is treated as relative, which means that it is interpreted relative +it is treated as _relative_, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last @@ -317,7 +317,7 @@ rejects if it's not valid JSON. {{index "GET method", "body (HTTP)", "DELETE method", "method property"}} -By default, `fetch` uses the `GET` method to make its request, and +By default, `fetch` uses the `GET` method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete `example/data.txt`. @@ -374,7 +374,7 @@ _mybank.com_). {{index "Access-Control-Allow-Origin header", "cross-domain request"}} -This can be an annoying problem when building systems that wants to +This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, ((server))s can include a ((header)) like this in their ((response)) to explicitly indicate to the browser that it is okay for the request @@ -430,7 +430,7 @@ server interface around. Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from -coffee-shop Wi-Fi to ((network))s controlled by various companies and +coffee-shop Wi-Fi hotspots to ((network))s controlled by various companies and states. At any point along its route it may be inspected or even modified. @@ -445,16 +445,16 @@ to via your bank's website, plain HTTP is not good enough. {{indexsee "Secure HTTP", HTTPS}} -The secure ((HTTP)) protocol, whose ((URL))s start with _https://_, +The secure ((HTTP)) protocol, used for ((URL))s start with _https://_, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is -who it claims to be, by asking it to prove that it has a cryptographic +who it claims to be by asking it to prove that it has a cryptographic ((certificate)) issued by a certificate authority that the ((browser)) recognizes. Next, all data going over the ((connection)) is encrypted in a way that should prevent eavesdropping and tampering. -Thus, when it works right, ((HTTPS)) prevents both the someone -impersonating the website you were trying to talk to and the someone +Thus, when it works right, ((HTTPS)) prevents other people from +impersonating the website you are trying to talk to and from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a _lot_ safer than plain @@ -564,7 +564,7 @@ predefined options. Such a field looks like this: -{{figure {url: "img/form_select.png", alt: "A select field",width: "4cm"}}} +{{figure {url: "img/form_select.png", alt: "A select field", width: "4cm"}}} if}} @@ -636,7 +636,7 @@ the OK button, rather than going through the help link first: {{index "tabindex attribute"}} By default, most types of HTML elements cannot be focused. But you can -add a `tabindex` attribute to any element, which will make it +add a `tabindex` attribute to any element that will make it focusable. A `tabindex` of -1 makes tabbing skip over an element, even if it is normally focusable. @@ -665,8 +665,8 @@ if}} {{index "user experience", "asynchronous programming"}} When a program is -in the process of handling an action caused by some ((button)) or other control, -which might require communication with the server and thus take a +in the process of handling an action caused by some ((button)) or other control +that might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don't accidentally repeat @@ -1104,7 +1104,7 @@ prevents the feature from eating up too much space. {{index "localStorage object", "note-taking example", "select (HTML tag)", "button (HTML tag)", "textarea (HTML tag)"}} The following code implements a crude note-taking application. It -keeps a set of named notes, and allows the user to edit notes and +keeps a set of named notes and allows the user to edit notes and create new ones. ```{lang: "text/html", startCode: true} @@ -1165,7 +1165,7 @@ exist from `localStorage` will yield `null`. Passing `null` to Thus, the `||` operator can be used to provide a default value in a situation like this. -The `setState` method makes sure the DOM is showing a given state, and +The `setState` method makes sure the DOM is showing a given state and stores the new state to `localStorage`. Event handlers call this function to move to a new state. @@ -1173,11 +1173,11 @@ function to move to a new state. The use of `Object.assign` in the example is intended to create a new object that is a clone of the old `state.notes`, but with one property -added or overwritten. `Object.assign` takes its first argument, and +added or overwritten. `Object.assign` takes its first argument and adds all properties from any further arguments to it. Thus, giving it an empty object will cause it to fill a fresh object. The ((square brackets)) notation in the third argument is used to create a property -whose names is based on some dynamic value. +whose name is based on some dynamic value. {{index "sessionStorage object"}} @@ -1229,7 +1229,7 @@ file picker field, the `FileReader` interface can be used to access the content of this file from a JavaScript program. The `localStorage` and `sessionStorage` objects can be used to save -information in a way that survives page reloads. The first saves the +information in a way that survives page reloads. The first object saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed. @@ -1239,7 +1239,7 @@ saves it until the browser is closed. {{index "Accept header", "media type", "document format", "content negotiation (exercise)"}} -One of the things that HTTP can do is called _content negotiation_. +One of the things HTTP can do is called _content negotiation_. The `Accept` request header is used to tell the server what type of document the client would like to get. Many servers ignore this header, but when a server knows of various ways to encode a resource, @@ -1295,7 +1295,7 @@ JavaScript code. {{index "textarea (HTML tag)", "button (HTML tag)", "Function constructor", "error message"}} -Put a button next to a ` @@ -884,17 +620,11 @@ delay), the timeout from the previous event will be canceled. {{index "sloppy programming"}} -Giving an undefined value to `clearTimeout` or calling it on a timeout -that has already fired has no effect. Thus, we don't have to be -careful about when to call it, and we simply do so for every event. +Giving an undefined value to `clearTimeout` or calling it on a timeout that has already fired has no effect. Thus, we don't have to be careful about when to call it, and we simply do so for every event. {{index "mousemove event"}} -We can use a slightly different pattern if we want to space responses -so that they're separated by at least a certain length of ((time)) but -want to fire them _during_ a series of events, not just afterward. For -example, we might want to respond to `"mousemove"` events by showing -the current coordinates of the mouse but only every 250 milliseconds. +We can use a slightly different pattern if we want to space responses so that they're separated by at least a certain length of ((time)) but want to fire them _during_ a series of events, not just afterward. For example, we might want to respond to `"mousemove"` events by showing the current coordinates of the mouse but only every 250 milliseconds. ```{lang: "text/html"} ``` -After creating the context object, the example draws a red -((rectangle)) 100 ((pixel))s wide and 50 pixels high, with its top-left -corner at coordinates (10,10). +After creating the context object, the example draws a red ((rectangle)) 100 ((pixel))s wide and 50 pixels high, with its top-left corner at coordinates (10,10). {{if book @@ -153,10 +104,7 @@ if}} {{index SVG, coordinates}} -Just like in HTML (and SVG), the coordinate system that the canvas -uses puts (0,0) at the top-left corner, and the positive y-((axis)) -goes down from there. So (10,10) is 10 pixels below and to the right -of the top-left corner. +Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-((axis)) goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner. {{id fill_stroke}} @@ -164,36 +112,23 @@ of the top-left corner. {{index filling, stroking, drawing, SVG}} -In the ((canvas)) interface, a shape can be _filled_, meaning its area -is given a certain color or pattern, or it can be _stroked_, which -means a ((line)) is drawn along its edge. The same terminology is used -by SVG. +In the ((canvas)) interface, a shape can be _filled_, meaning its area is given a certain color or pattern, or it can be _stroked_, which means a ((line)) is drawn along its edge. The same terminology is used by SVG. {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and -y-((coordinates)) of the rectangle's top-left corner, then its width, -and then its height. A similar method, `strokeRect`, draws the -((outline)) of a rectangle. +The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method, `strokeRect`, draws the ((outline)) of a rectangle. {{index [state, "of canvas"]}} -Neither method takes any further parameters. The color of the fill, -thickness of the stroke, and so on, are not determined by an argument -to the method (as you might reasonably expect) but rather by -properties of the context object. +Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method (as you might reasonably expect) but rather by properties of the context object. {{index filling, "fillStyle property"}} -The `fillStyle` property controls the way shapes are filled. It can be -set to a string that specifies a ((color)), using the color notation -used by ((CSS)). +The `fillStyle` property controls the way shapes are filled. It can be set to a string that specifies a ((color)), using the color notation used by ((CSS)). {{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} -The `strokeStyle` property works similarly but determines the color -used for a stroked line. The width of that line is determined by the -`lineWidth` property, which may contain any positive number. +The `strokeStyle` property works similarly but determines the color used for a stroked line. The width of that line is determined by the `lineWidth` property, which may contain any positive number. ```{lang: "text/html"} @@ -208,8 +143,7 @@ used for a stroked line. The width of that line is determined by the {{if book -This code draws two blue squares, using a thicker line for the second -one. +This code draws two blue squares, using a thicker line for the second one. {{figure {url: "img/canvas_stroke.png", alt: "Two stroked squares",width: "5cm"}}} @@ -217,19 +151,13 @@ if}} {{index "default value", [canvas, size]}} -When no `width` or `height` attribute is specified, as in the example, -a canvas element gets a default width of 300 pixels and height of 150 -pixels. +When no `width` or `height` attribute is specified, as in the example, a canvas element gets a default width of 300 pixels and height of 150 pixels. ## Paths {{index [path, canvas], [interface, design], [canvas, path]}} -A path is a sequence of ((line))s. The 2D canvas interface takes a -peculiar approach to describing such a path. It is done entirely -through ((side effect))s. Paths are not values that can be stored and -passed around. Instead, if you want to do something with a path, you -make a sequence of method calls to describe its shape. +A path is a sequence of ((line))s. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through ((side effect))s. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape. ```{lang: "text/html"} @@ -246,12 +174,7 @@ make a sequence of method calls to describe its shape. {{index canvas, "stroke method", "lineTo method", "moveTo method", shape}} -This example creates a path with a number of horizontal ((line)) -segments and then strokes it using the `stroke` method. Each segment -created with `lineTo` starts at the path's _current_ position. That -position is usually the end of the last segment, unless `moveTo` was -called. In that case, the next segment would start at the position -passed to `moveTo`. +This example creates a path with a number of horizontal ((line)) segments and then strokes it using the `stroke` method. Each segment created with `lineTo` starts at the path's _current_ position. That position is usually the end of the last segment, unless `moveTo` was called. In that case, the next segment would start at the position passed to `moveTo`. {{if book @@ -263,12 +186,7 @@ if}} {{index [path, canvas], filling, [path, closing], "fill method"}} -When filling a path (using the `fill` method), each ((shape)) is -filled separately. A path can contain multiple shapes—each `moveTo` -motion starts a new one. But the path needs to be _closed_ (meaning -its start and end are in the same position) before it can be filled. -If the path is not already closed, a line is added from its end to its -start, and the shape enclosed by the completed path is filled. +When filling a path (using the `fill` method), each ((shape)) is filled separately. A path can contain multiple shapes—each `moveTo` motion starts a new one. But the path needs to be _closed_ (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled. ```{lang: "text/html"} @@ -282,10 +200,7 @@ start, and the shape enclosed by the completed path is filled. ``` -This example draws a filled triangle. Note that only two of the -triangle's sides are explicitly drawn. The third, from the -bottom-right corner back to the top, is implied and wouldn't be there -when you stroke the path. +This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be there when you stroke the path. {{if book @@ -295,26 +210,17 @@ if}} {{index "stroke method", "closePath method", [path, closing], canvas}} -You could also use the `closePath` method to explicitly close a path -by adding an actual ((line)) segment back to the path's start. This -segment _is_ drawn when stroking the path. +You could also use the `closePath` method to explicitly close a path by adding an actual ((line)) segment back to the path's start. This segment _is_ drawn when stroking the path. ## Curves {{index [path, canvas], canvas, drawing}} -A path may also contain ((curve))d ((line))s. These are unfortunately -a bit more involved to draw. +A path may also contain ((curve))d ((line))s. These are unfortunately a bit more involved to draw. {{index "quadraticCurveTo method"}} -The `quadraticCurveTo` method draws a curve to a given point. To -determine the curvature of the line, the method is given a ((control -point)) as well as a destination point. Imagine this control point as -_attracting_ the line, giving it its curve. The line won't go through -the control point, but its direction at the start and end points will -be such that a straight line in that direction would point toward the -control point. The following example illustrates this: +The `quadraticCurveTo` method draws a curve to a given point. To determine the curvature of the line, the method is given a ((control point)) as well as a destination point. Imagine this control point as _attracting_ the line, giving it its curve. The line won't go through the control point, but its direction at the start and end points will be such that a straight line in that direction would point toward the control point. The following example illustrates this: ```{lang: "text/html"} @@ -340,20 +246,11 @@ if}} {{index "stroke method"}} -We draw a ((quadratic curve)) from the left to the right, with (60,10) -as control point, and then draw two ((line)) segments going through -that control point and back to the start of the line. The result -somewhat resembles a _((Star Trek))_ insignia. You can see the effect -of the control point: the lines leaving the lower corners start off in -the direction of the control point and then ((curve)) toward their -target. +We draw a ((quadratic curve)) from the left to the right, with (60,10) as control point, and then draw two ((line)) segments going through that control point and back to the start of the line. The result somewhat resembles a _((Star Trek))_ insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then ((curve)) toward their target. {{index canvas, "bezierCurveTo method"}} -The `bezierCurveTo` method draws a similar kind of curve. Instead of a -single ((control point)), this one has two—one for each of the -((line))'s endpoints. Here is a similar sketch to illustrate the -behavior of such a curve: +The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this one has two—one for each of the ((line))'s endpoints. Here is a similar sketch to illustrate the behavior of such a curve: ```{lang: "text/html"} @@ -370,9 +267,7 @@ behavior of such a curve: ``` -The two control points specify the direction at both ends of the -curve. The farther they are away from their corresponding point, the -more the curve will "bulge" in that direction. +The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more the curve will "bulge" in that direction. {{if book @@ -382,26 +277,15 @@ if}} {{index "trial and error"}} -Such ((curve))s can be hard to work with—it's not always clear how to -find the ((control point))s that provide the ((shape)) you are looking -for. Sometimes you can compute them, and sometimes you'll just have to -find a suitable value by trial and error. +Such ((curve))s can be hard to work with—it's not always clear how to find the ((control point))s that provide the ((shape)) you are looking for. Sometimes you can compute them, and sometimes you'll just have to find a suitable value by trial and error. {{index "arc method", arc}} -The `arc` method is a way to draw a line that curves along the edge of -a circle. It takes a pair of ((coordinates)) for the arc's center, a -radius, and then a start angle and end angle. +The `arc` method is a way to draw a line that curves along the edge of a circle. It takes a pair of ((coordinates)) for the arc's center, a radius, and then a start angle and end angle. {{index pi, "Math.PI constant"}} -Those last two parameters make it possible to draw only part of the -circle. The ((angle))s are measured in ((radian))s, not ((degree))s. -This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, -which is about 6.28. The angle starts counting at the point to the -right of the circle's center and goes clockwise from there. You can -use a start of 0 and an end bigger than 2π (say, 7) to draw a full -circle. +Those last two parameters make it possible to draw only part of the circle. The ((angle))s are measured in ((radian))s, not ((degree))s. This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, which is about 6.28. The angle starts counting at the point to the right of the circle's center and goes clockwise from there. You can use a start of 0 and an end bigger than 2π (say, 7) to draw a full circle. ```{lang: "text/html"} @@ -418,11 +302,7 @@ circle. {{index "moveTo method", "arc method", [path, " canvas"]}} -The resulting picture contains a ((line)) from the right of the full -circle (first call to `arc`) to the right of the quarter-((circle)) -(second call). Like other path-drawing methods, a line drawn with -`arc` is connected to the previous path segment. You can call `moveTo` -or start a new path to avoid this. +The resulting picture contains a ((line)) from the right of the full circle (first call to `arc`) to the right of the quarter-((circle)) (second call). Like other path-drawing methods, a line drawn with `arc` is connected to the previous path segment. You can call `moveTo` or start a new path to avoid this. {{if book @@ -436,12 +316,9 @@ if}} {{index "pie chart example"}} -Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your -first assignment is to draw a pie chart of its customer satisfaction -((survey)) results. +Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results. -The `results` binding contains an array of objects that represent the -survey responses. +The `results` binding contains an array of objects that represent the survey responses. ```{sandbox: "pie", includeCode: true} const results = [ @@ -454,12 +331,7 @@ const results = [ {{index "pie chart example"}} -To draw a pie chart, we draw a number of pie slices, each made up of -an ((arc)) and a pair of ((line))s to the center of that arc. We can -compute the ((angle)) taken up by each arc by dividing a full circle -(2π) by the total number of responses and then multiplying that number -(the angle per response) by the number of people who picked a given -choice. +To draw a pie chart, we draw a number of pie slices, each made up of an ((arc)) and a pair of ((line))s to the center of that arc. We can compute the ((angle)) taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice. ```{lang: "text/html", sandbox: "pie"} @@ -492,17 +364,13 @@ This draws the following chart: if}} -But a chart that doesn't tell us what the slices mean isn't very -helpful. We need a way to draw text to the ((canvas)). +But a chart that doesn't tell us what the slices mean isn't very helpful. We need a way to draw text to the ((canvas)). ## Text {{index stroking, filling, "fillStyle property", "fillText method", "strokeText method"}} -A 2D canvas drawing context provides the methods `fillText` and -`strokeText`. The latter can be useful for outlining letters, but -usually `fillText` is what you need. It will fill the outline of the -given ((text)) with the current `fillStyle`. +A 2D canvas drawing context provides the methods `fillText` and `strokeText`. The latter can be useful for outlining letters, but usually `fillText` is what you need. It will fill the outline of the given ((text)) with the current `fillStyle`. ```{lang: "text/html"} @@ -514,48 +382,25 @@ given ((text)) with the current `fillStyle`. ``` -You can specify the size, style, and ((font)) of the text with the -`font` property. This example just gives a font size and family name. -It is also possible to add `italic` or `bold` to the start of the -string to select a style. +You can specify the size, style, and ((font)) of the text with the `font` property. This example just gives a font size and family name. It is also possible to add `italic` or `bold` to the start of the string to select a style. {{index "fillText method", "strokeText method", "textAlign property", "textBaseline property"}} -The last two arguments to `fillText` and `strokeText` provide the -position at which the font is drawn. By default, they indicate the -position of the start of the text's alphabetic baseline, which is the -line that letters "stand" on, not counting hanging parts in letters -such as _j_ or _p_. You can change the horizontal position by setting the -`textAlign` property to `"end"` or `"center"` and the vertical -position by setting `textBaseline` to `"top"`, `"middle"`, or -`"bottom"`. +The last two arguments to `fillText` and `strokeText` provide the position at which the font is drawn. By default, they indicate the position of the start of the text's alphabetic baseline, which is the line that letters "stand" on, not counting hanging parts in letters such as _j_ or _p_. You can change the horizontal position by setting the `textAlign` property to `"end"` or `"center"` and the vertical position by setting `textBaseline` to `"top"`, `"middle"`, or `"bottom"`. {{index "pie chart example"}} -We'll come back to our pie chart, and the problem of ((label))ing the -slices, in the [exercises](canvas#exercise_pie_chart) at the end of -the chapter. +We'll come back to our pie chart, and the problem of ((label))ing the slices, in the [exercises](canvas#exercise_pie_chart) at the end of the chapter. ## Images {{index "vector graphics", "bitmap graphics"}} -In computer ((graphics)), a distinction is often made between _vector_ -graphics and _bitmap_ graphics. The first is what we have been doing -so far in this chapter—specifying a picture by giving a logical -description of ((shape))s. Bitmap graphics, on the other hand, don't -specify actual shapes but rather work with ((pixel)) data (rasters of -colored dots). +In computer ((graphics)), a distinction is often made between _vector_ graphics and _bitmap_ graphics. The first is what we have been doing so far in this chapter—specifying a picture by giving a logical description of ((shape))s. Bitmap graphics, on the other hand, don't specify actual shapes but rather work with ((pixel)) data (rasters of colored dots). {{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} -The `drawImage` method allows us to draw ((pixel)) data onto a -((canvas)). This pixel data can originate from an `` element or -from another canvas. The following example creates a detached `` -element and loads an image file into it. But it cannot immediately -start drawing from this picture because the browser may not have -loaded it yet. To deal with this, we register a `"load"` event handler -and do the drawing after the image has loaded. +The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This pixel data can originate from an `` element or from another canvas. The following example creates a detached `` element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a `"load"` event handler and do the drawing after the image has loaded. ```{lang: "text/html"} @@ -573,42 +418,27 @@ and do the drawing after the image has loaded. {{index "drawImage method", scaling}} -By default, `drawImage` will draw the image at its original size. You -can also give it two additional arguments to set a different width -and height. +By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to set a different width and height. -When `drawImage` is given _nine_ arguments, it can be used to draw -only a fragment of an image. The second through fifth arguments -indicate the rectangle (x, y, width, and height) in the source image -that should be copied, and the sixth to ninth arguments give the -rectangle (on the canvas) into which it should be copied. +When `drawImage` is given _nine_ arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied. {{index "player", "pixel art"}} -This can be used to pack multiple _((sprite))s_ (image elements) into -a single image file and then draw only the part you need. For example, -we have this picture containing a game character in multiple -((pose))s: +This can be used to pack multiple _((sprite))s_ (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple ((pose))s: {{figure {url: "img/player_big.png", alt: "Various poses of a game character",width: "6cm"}}} {{index [animation, "platform game"]}} -By alternating which pose we draw, we can show an animation that -looks like a walking character. +By alternating which pose we draw, we can show an animation that looks like a walking character. {{index "fillRect method", "clearRect method", clearing}} -To animate a ((picture)) on a ((canvas)), the `clearRect` method is -useful. It resembles `fillRect`, but instead of coloring the -rectangle, it makes it ((transparent)), removing the previously drawn -pixels. +To animate a ((picture)) on a ((canvas)), the `clearRect` method is useful. It resembles `fillRect`, but instead of coloring the rectangle, it makes it ((transparent)), removing the previously drawn pixels. {{index "setInterval function", "img (HTML tag)"}} -We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide -and 30 pixels high. The following code loads the image and then sets -up an interval (repeated timer) to draw the next ((frame)): +We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide and 30 pixels high. The following code loads the image and then sets up an interval (repeated timer) to draw the next ((frame)): ```{lang: "text/html"} @@ -634,11 +464,7 @@ up an interval (repeated timer) to draw the next ((frame)): {{index "remainder operator", "% operator", [animation, "platform game"]}} -The `cycle` binding tracks our position in the animation. For each -((frame)), it is incremented and then clipped back to the 0 to 7 range -by using the remainder operator. This binding is then used to compute -the x-coordinate that the sprite for the current pose has in the -picture. +The `cycle` binding tracks our position in the animation. For each ((frame)), it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the picture. ## Transformation @@ -646,15 +472,11 @@ picture. {{indexsee flipping, mirroring}} -But what if we want our character to walk to the left instead of to -the right? We could draw another set of sprites, of course. But we can -also instruct the ((canvas)) to draw the picture the other way round. +But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the ((canvas)) to draw the picture the other way round. {{index "scale method", scaling}} -Calling the `scale` method will cause anything drawn after it to be -scaled. This method takes two parameters, one to set a horizontal -scale and one to set a vertical scale. +Calling the `scale` method will cause anything drawn after it to be scaled. This method takes two parameters, one to set a horizontal scale and one to set a vertical scale. ```{lang: "text/html"} @@ -670,8 +492,7 @@ scale and one to set a vertical scale. {{if book -Because of the call to `scale`, the circle is drawn three times as wide -and half as high. +Because of the call to `scale`, the circle is drawn three times as wide and half as high. {{figure {url: "img/canvas_scale.png", alt: "A scaled circle",width: "6.6cm"}}} @@ -679,54 +500,29 @@ if}} {{index mirroring}} -Scaling will cause everything about the drawn image, including the -((line width)), to be stretched out or squeezed together as specified. -Scaling by a negative amount will flip the picture around. The -flipping happens around point (0,0), which means it will also flip the -direction of the coordinate system. When a horizontal scaling of -1 is -applied, a shape drawn at x position 100 will end up at what used to -be position -100. +Scaling will cause everything about the drawn image, including the ((line width)), to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at x position 100 will end up at what used to be position -100. {{index "drawImage method"}} -So to turn a picture around, we can't simply add `cx.scale(-1, 1)` -before the call to `drawImage` because that would move our picture -outside of the ((canvas)), where it won't be visible. You could adjust -the ((coordinates)) given to `drawImage` to compensate for this by -drawing the image at x position -50 instead of 0. Another solution, -which doesn't require the code that does the drawing to know about the -scale change, is to adjust the ((axis)) around which the scaling -happens. +So to turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage` because that would move our picture outside of the ((canvas)), where it won't be visible. You could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn't require the code that does the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens. {{index "rotate method", "translate method", transformation}} -There are several other methods besides `scale` that influence the -coordinate system for a ((canvas)). You can rotate subsequently drawn -shapes with the `rotate` method and move them with the `translate` -method. The interesting—and confusing—thing is that these -transformations _stack_, meaning that each one happens relative to the -previous transformations. +There are several other methods besides `scale` that influence the coordinate system for a ((canvas)). You can rotate subsequently drawn shapes with the `rotate` method and move them with the `translate` method. The interesting—and confusing—thing is that these transformations _stack_, meaning that each one happens relative to the previous transformations. {{index "rotate method", "translate method"}} -So if we translate by 10 horizontal pixels twice, everything will be -drawn 20 pixels to the right. If we first move the center of the -coordinate system to (50,50) and then rotate by 20 ((degree))s (about -0.1π ((radian))s), that rotation will happen _around_ point (50,50). +So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50,50). {{figure {url: "img/transform.svg", alt: "Stacking transformations",width: "9cm"}}} {{index coordinates}} -But if we _first_ rotate by 20 degrees and _then_ translate by -(50,50), the translation will happen in the rotated coordinate system -and thus produce a different orientation. The order in which -transformations are applied matters. +But if we _first_ rotate by 20 degrees and _then_ translate by (50,50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters. {{index axis, mirroring}} -To flip a picture around the vertical line at a given x position, we -can do the following: +To flip a picture around the vertical line at a given x position, we can do the following: ```{includeCode: true} function flipHorizontally(context, around) { @@ -738,26 +534,15 @@ function flipHorizontally(context, around) { {{index "flipHorizontally method"}} -We move the y-((axis)) to where we want our ((mirror)) to be, apply -the mirroring, and finally move the y-axis back to its proper place in -the mirrored universe. The following picture explains why this works: +We move the y-((axis)) to where we want our ((mirror)) to be, apply the mirroring, and finally move the y-axis back to its proper place in the mirrored universe. The following picture explains why this works: {{figure {url: "img/mirror.svg", alt: "Mirroring around a vertical line",width: "8cm"}}} {{index "translate method", "scale method", transformation, canvas}} -This shows the coordinate systems before and after mirroring across -the central line. The triangles are numbered to illustrate each step. -If we draw a triangle at a positive x position, it would, by default, -be in the place where triangle 1 is. A call to `flipHorizontally` -first does a translation to the right, which gets us to triangle 2. It -then scales, flipping the triangle over to position 3. This is not -where it should be, if it were mirrored in the given line. The second -`translate` call fixes this—it "cancels" the initial translation and -makes triangle 4 appear exactly where it should. +This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive x position, it would, by default, be in the place where triangle 1 is. A call to `flipHorizontally` first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second `translate` call fixes this—it "cancels" the initial translation and makes triangle 4 appear exactly where it should. -We can now draw a mirrored character at position (100,0) by flipping -the world around the character's vertical center. +We can now draw a mirrored character at position (100,0) by flipping the world around the character's vertical center. ```{lang: "text/html"} @@ -778,41 +563,19 @@ the world around the character's vertical center. {{index "side effect", canvas, transformation}} -Transformations stick around. Everything else we draw after -((drawing)) that mirrored character would also be mirrored. That might -be inconvenient. +Transformations stick around. Everything else we draw after ((drawing)) that mirrored character would also be mirrored. That might be inconvenient. -It is possible to save the current transformation, do some drawing and -transforming, and then restore the old transformation. This is usually -the proper thing to do for a function that needs to temporarily -transform the coordinate system. First, we save whatever -transformation the code that called the function was using. Then the -function does its thing, adding more transformations on top of the -current transformation. Finally, we revert to the -transformation we started with. +It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with. {{index "save method", "restore method", [state, "of canvas"]}} -The `save` and `restore` methods on the 2D ((canvas)) context do this -((transformation)) management. They conceptually keep a stack of -transformation states. When you call `save`, the current state is -pushed onto the stack, and when you call `restore`, the state on top -of the stack is taken off and used as the context's current -transformation. You can also call `resetTransform` to fully reset the -transformation. +The `save` and `restore` methods on the 2D ((canvas)) context do this ((transformation)) management. They conceptually keep a stack of transformation states. When you call `save`, the current state is pushed onto the stack, and when you call `restore`, the state on top of the stack is taken off and used as the context's current transformation. You can also call `resetTransform` to fully reset the transformation. {{index "branching recursion", "fractal example", recursion}} -The `branch` function in the following example illustrates what you -can do with a function that changes the transformation and then calls -a function (in this case itself), which continues drawing with -the given transformation. +The `branch` function in the following example illustrates what you can do with a function that changes the transformation and then calls a function (in this case itself), which continues drawing with the given transformation. -This function draws a treelike shape by drawing a line, moving the -center of the coordinate system to the end of the line, and calling -itself twice—first rotated to the left and then rotated to the right. -Every call reduces the length of the branch drawn, and the recursion -stops when the length drops below 8. +This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the end of the line, and calling itself twice—first rotated to the left and then rotated to the right. Every call reduces the length of the branch drawn, and the recursion stops when the length drops below 8. ```{lang: "text/html"} @@ -844,12 +607,7 @@ if}} {{index "save method", "restore method", canvas, "rotate method"}} -If the calls to `save` and `restore` were not there, the second -recursive call to `branch` would end up with the position and rotation -created by the first call. It wouldn't be connected to the current -branch but rather to the innermost, rightmost branch drawn by the -first call. The resulting shape might also be interesting, but it is -definitely not a tree. +If the calls to `save` and `restore` were not there, the second recursive call to `branch` would end up with the position and rotation created by the first call. It wouldn't be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree. {{id canvasdisplay}} @@ -857,26 +615,15 @@ definitely not a tree. {{index "drawImage method"}} -We now know enough about ((canvas)) drawing to start working on a -((canvas))-based ((display)) system for the ((game)) from the -[previous chapter](game). The new display will no longer be showing -just colored boxes. Instead, we'll use `drawImage` to draw pictures -that represent the game's elements. +We now know enough about ((canvas)) drawing to start working on a ((canvas))-based ((display)) system for the ((game)) from the [previous chapter](game). The new display will no longer be showing just colored boxes. Instead, we'll use `drawImage` to draw pictures that represent the game's elements. {{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} -We define another display object type called `CanvasDisplay`, -supporting the same interface as `DOMDisplay` from [Chapter -?](game#domdisplay), namely, the methods `syncState` and `clear`. +We define another display object type called `CanvasDisplay`, supporting the same interface as `DOMDisplay` from [Chapter ?](game#domdisplay), namely, the methods `syncState` and `clear`. {{index [state, "in objects"]}} -This object keeps a little more information than `DOMDisplay`. Rather -than using the scroll position of its DOM element, it tracks its own -((viewport)), which tells us what part of the level we are currently -looking at. Finally, it keeps a `flipPlayer` property so that even -when the player is standing still, it keeps facing the direction it -last moved in. +This object keeps a little more information than `DOMDisplay`. Rather than using the scroll position of its DOM element, it tracks its own ((viewport)), which tells us what part of the level we are currently looking at. Finally, it keeps a `flipPlayer` property so that even when the player is standing still, it keeps facing the direction it last moved in. ```{sandbox: "game", includeCode: true} class CanvasDisplay { @@ -903,8 +650,7 @@ class CanvasDisplay { } ``` -The `syncState` method first computes a new viewport and then draws -the game scene at the appropriate position. +The `syncState` method first computes a new viewport and then draws the game scene at the appropriate position. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.syncState = function(state) { @@ -917,19 +663,11 @@ CanvasDisplay.prototype.syncState = function(state) { {{index scrolling, clearing}} -Contrary to `DOMDisplay`, this display style _does_ have to redraw the -background on every update. Because shapes on a canvas are just -((pixel))s, after we draw them there is no good way to move them (or -remove them). The only way to update the canvas display is to clear it -and redraw the scene. We may also have scrolled, which requires the -background to be in a different position. +Contrary to `DOMDisplay`, this display style _does_ have to redraw the background on every update. Because shapes on a canvas are just ((pixel))s, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position. {{index "CanvasDisplay class"}} -The `updateViewport` method is similar to `DOMDisplay`'s -`scrollPlayerIntoView` method. It checks whether the player is too -close to the edge of the screen and moves the ((viewport)) when this -is the case. +The `updateViewport` method is similar to `DOMDisplay`'s `scrollPlayerIntoView` method. It checks whether the player is too close to the edge of the screen and moves the ((viewport)) when this is the case. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.updateViewport = function(state) { @@ -954,14 +692,9 @@ CanvasDisplay.prototype.updateViewport = function(state) { {{index boundary, "Math.max function", "Math.min function", clipping}} -The calls to `Math.max` and `Math.min` ensure that the viewport does -not end up showing space outside of the level. `Math.max(x, 0)` makes -sure the resulting number is not less than zero. `Math.min` -similarly guarantees that a value stays below a given bound. +The calls to `Math.max` and `Math.min` ensure that the viewport does not end up showing space outside of the level. `Math.max(x, 0)` makes sure the resulting number is not less than zero. `Math.min` similarly guarantees that a value stays below a given bound. -When ((clearing)) the display, we'll use a slightly different -((color)) depending on whether the game is won (brighter) or lost -(darker). +When ((clearing)) the display, we'll use a slightly different ((color)) depending on whether the game is won (brighter) or lost (darker). ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.clearDisplay = function(status) { @@ -979,9 +712,7 @@ CanvasDisplay.prototype.clearDisplay = function(status) { {{index "Math.floor function", "Math.ceil function", rounding}} -To draw the background, we run through the tiles that are visible in -the current viewport, using the same trick used in the `touches` -method from the [previous chapter](game#touches). +To draw the background, we run through the tiles that are visible in the current viewport, using the same trick used in the `touches` method from the [previous chapter](game#touches). ```{sandbox: "game", includeCode: true} let otherSprites = document.createElement("img"); @@ -1011,46 +742,25 @@ CanvasDisplay.prototype.drawBackground = function(level) { {{index "drawImage method", sprite, tile}} -Tiles that are not empty are drawn with `drawImage`. The -`otherSprites` image contains the pictures used for elements other -than the player. It contains, from left to right, the wall tile, the -lava tile, and the sprite for a coin. +Tiles that are not empty are drawn with `drawImage`. The `otherSprites` image contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin. {{figure {url: "img/sprites_big.png", alt: "Sprites for our game",width: "1.4cm"}}} {{index scaling}} -Background tiles are 20 by 20 pixels since we will use the same scale -that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 -(the value of the `scale` binding), and the offset for walls is 0. +Background tiles are 20 by 20 pixels since we will use the same scale that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 (the value of the `scale` binding), and the offset for walls is 0. {{index drawing, "load event", "drawImage method"}} -We don't bother waiting for the sprite image to load. Calling -`drawImage` with an image that hasn't been loaded yet will simply do -nothing. Thus, we might fail to draw the game properly for the first -few ((frame))s, while the image is still loading, but that is not a -serious problem. Since we keep updating the screen, the correct scene -will appear as soon as the loading finishes. +We don't bother waiting for the sprite image to load. Calling `drawImage` with an image that hasn't been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few ((frame))s, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes. {{index "player", [animation, "platform game"], drawing}} -The ((walking)) character shown earlier will be used to represent the -player. The code that draws it needs to pick the right ((sprite)) and -direction based on the player's current motion. The first eight -sprites contain a walking animation. When the player is moving along a -floor, we cycle through them based on the current time. We want to -switch frames every 60 milliseconds, so the ((time)) is divided by 60 -first. When the player is standing still, we draw the ninth sprite. -During jumps, which are recognized by the fact that the vertical speed -is not zero, we use the tenth, rightmost sprite. +The ((walking)) character shown earlier will be used to represent the player. The code that draws it needs to pick the right ((sprite)) and direction based on the player's current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current time. We want to switch frames every 60 milliseconds, so the ((time)) is divided by 60 first. When the player is standing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite. {{index "flipHorizontally function", "CanvasDisplay class"}} -Because the ((sprite))s are slightly wider than the player object—24 -instead of 16 pixels to allow some space for feet and arms—the method -has to adjust the x-coordinate and width by a given amount -(`playerXOverlap`). +Because the ((sprite))s are slightly wider than the player object—24 instead of 16 pixels to allow some space for feet and arms—the method has to adjust the x-coordinate and width by a given amount (`playerXOverlap`). ```{sandbox: "game", includeCode: true} let playerSprites = document.createElement("img"); @@ -1083,8 +793,7 @@ CanvasDisplay.prototype.drawPlayer = function(player, x, y, }; ``` -The `drawPlayer` method is called by `drawActors`, which is -responsible for drawing all the actors in the game. +The `drawPlayer` method is called by `drawActors`, which is responsible for drawing all the actors in the game. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.drawActors = function(actors) { @@ -1105,17 +814,11 @@ CanvasDisplay.prototype.drawActors = function(actors) { }; ``` -When ((drawing)) something that is not the ((player)), we look at its -type to find the offset of the correct sprite. The ((lava)) tile is -found at offset 20, and the ((coin)) sprite is found at 40 (two times -`scale`). +When ((drawing)) something that is not the ((player)), we look at its type to find the offset of the correct sprite. The ((lava)) tile is found at offset 20, and the ((coin)) sprite is found at 40 (two times `scale`). {{index viewport}} -We have to subtract the viewport's position when computing the actor's -position since (0,0) on our ((canvas)) corresponds to the top left of -the viewport, not the top left of the level. We could also have used -`translate` for this. Either way works. +We have to subtract the viewport's position when computing the actor's position since (0,0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works. {{if interactive @@ -1135,8 +838,7 @@ if}} {{index [game, screenshot], [game, "with canvas"]}} -That concludes the new ((display)) system. The resulting game looks -something like this: +That concludes the new ((display)) system. The resulting game looks something like this: {{figure {url: "img/canvas_game.png", alt: "The game as shown on canvas",width: "8cm"}}} @@ -1146,105 +848,53 @@ if}} ## Choosing a graphics interface -So when you need to generate graphics in the browser, you can choose -between plain HTML, ((SVG)), and ((canvas)). There is no single -_best_ approach that works in all situations. Each option has -strengths and weaknesses. +So when you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses. {{index "text wrapping"}} -Plain HTML has the advantage of being simple. It also integrates well -with ((text)). Both SVG and canvas allow you to draw text, but they -won't help you position that text or wrap it when it takes up more -than one line. In an HTML-based picture, it is much easier to include -blocks of text. +Plain HTML has the advantage of being simple. It also integrates well with ((text)). Both SVG and canvas allow you to draw text, but they won't help you position that text or wrap it when it takes up more than one line. In an HTML-based picture, it is much easier to include blocks of text. {{index zooming, SVG}} -SVG can be used to produce ((crisp)) ((graphics)) that look good at -any zoom level. Unlike HTML, it is designed for drawing -and is thus more suitable for that purpose. +SVG can be used to produce ((crisp)) ((graphics)) that look good at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose. {{index [DOM, graphics], SVG, "event handling", ["data structure", tree]}} -Both SVG and HTML build up a data structure (the DOM) that -represents your picture. This makes it possible to modify elements -after they are drawn. If you need to repeatedly change a small part of -a big ((picture)) in response to what the user is doing or as part of -an ((animation)), doing it in a canvas can be needlessly expensive. -The DOM also allows us to register mouse event handlers on every -element in the picture (even on shapes drawn with SVG). You can't do -that with canvas. +Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big ((picture)) in response to what the user is doing or as part of an ((animation)), doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can't do that with canvas. {{index performance, optimization}} -But ((canvas))'s ((pixel))-oriented approach can be an advantage when -drawing a huge number of tiny elements. The fact that it does not -build up a data structure but only repeatedly draws onto the same -pixel surface gives canvas a lower cost per shape. +But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape. {{index "ray tracer"}} -There are also effects, such as rendering a scene one pixel at a time -(for example, using a ray tracer) or postprocessing an image with -JavaScript (blurring or distorting it), that can be realistically -handled only by a ((pixel))-based approach. +There are also effects, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a ((pixel))-based approach. -In some cases, you may want to combine several of these techniques. -For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but -show ((text))ual information by positioning an HTML element on top -of the picture. +In some cases, you may want to combine several of these techniques. For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but show ((text))ual information by positioning an HTML element on top of the picture. {{index display}} -For nondemanding applications, it really doesn't matter much which -interface you choose. The display we built for our game in this -chapter could have been implemented using any of these three -((graphics)) technologies since it does not need to draw text, handle -mouse interaction, or work with an extraordinarily large number of -elements. +For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three ((graphics)) technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements. ## Summary -In this chapter we discussed techniques for drawing graphics in the -browser, focusing on the `` element. +In this chapter we discussed techniques for drawing graphics in the browser, focusing on the `` element. -A canvas node represents an area in a document that our program may -draw on. This drawing is done through a drawing context object, -created with the `getContext` method. +A canvas node represents an area in a document that our program may draw on. This drawing is done through a drawing context object, created with the `getContext` method. -The 2D drawing interface allows us to fill and stroke various shapes. -The context's `fillStyle` property determines how shapes are filled. -The `strokeStyle` and `lineWidth` properties control the way lines are -drawn. +The 2D drawing interface allows us to fill and stroke various shapes. The context's `fillStyle` property determines how shapes are filled. The `strokeStyle` and `lineWidth` properties control the way lines are drawn. -Rectangles and pieces of text can be drawn with a single method call. -The `fillRect` and `strokeRect` methods draw rectangles, and the -`fillText` and `strokeText` methods draw text. To create custom -shapes, we must first build up a path. +Rectangles and pieces of text can be drawn with a single method call. The `fillRect` and `strokeRect` methods draw rectangles, and the `fillText` and `strokeText` methods draw text. To create custom shapes, we must first build up a path. {{index stroking, filling}} -Calling `beginPath` starts a new path. A number of other methods add -lines and curves to the current path. For example, `lineTo` can add a -straight line. When a path is finished, it can be filled with the -`fill` method or stroked with the `stroke` method. +Calling `beginPath` starts a new path. A number of other methods add lines and curves to the current path. For example, `lineTo` can add a straight line. When a path is finished, it can be filled with the `fill` method or stroked with the `stroke` method. -Moving pixels from an image or another canvas onto our canvas is done -with the `drawImage` method. By default, this method draws the whole -source image, but by giving it more parameters, you can copy a -specific area of the image. We used this for our game by copying -individual poses of the game character out of an image that contained -many such poses. +Moving pixels from an image or another canvas onto our canvas is done with the `drawImage` method. By default, this method draws the whole source image, but by giving it more parameters, you can copy a specific area of the image. We used this for our game by copying individual poses of the game character out of an image that contained many such poses. -Transformations allow you to draw a shape in multiple orientations. A -2D drawing context has a current transformation that can be changed -with the `translate`, `scale`, and `rotate` methods. These will affect -all subsequent drawing operations. A transformation state can be saved -with the `save` method and restored with the `restore` method. +Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a current transformation that can be changed with the `translate`, `scale`, and `rotate` methods. These will affect all subsequent drawing operations. A transformation state can be saved with the `save` method and restored with the `restore` method. -When showing an animation on a canvas, the `clearRect` method can be -used to clear part of the canvas before redrawing it. +When showing an animation on a canvas, the `clearRect` method can be used to clear part of the canvas before redrawing it. ## Exercises @@ -1268,16 +918,11 @@ Write a program that draws the following ((shape))s on a ((canvas)): {{figure {url: "img/exercise_shapes.png", alt: "The shapes to draw",width: "8cm"}}} -When drawing the last two, you may want to refer to the explanation of -`Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes -how to get coordinates on a circle using these functions. +When drawing the last two, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions. {{index readability, "hard-coding"}} -I recommend creating a function for each shape. Pass the position, and -optionally other properties such as the size or the number of points, -as parameters. The alternative, which is to hard-code numbers all over -your code, tends to make the code needlessly hard to read and modify. +I recommend creating a function for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to hard-code numbers all over your code, tends to make the code needlessly hard to read and modify. {{if interactive @@ -1296,43 +941,23 @@ if}} {{index [path, canvas], "shapes (exercise)"}} -The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable -center coordinates and add each of the four corners around the center. +The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable center coordinates and add each of the four corners around the center. {{index "flipHorizontally function", rotation}} -The ((diamond)) (2) can be drawn the straightforward way, with a path, -or the interesting way, with a `rotate` ((transformation)). To use -rotation, you will have to apply a trick similar to what we did in the -`flipHorizontally` function. Because you want to rotate around the -center of your rectangle and not around the point (0,0), you must -first `translate` to there, then rotate, and then translate back. +The ((diamond)) (2) can be drawn the straightforward way, with a path, or the interesting way, with a `rotate` ((transformation)). To use rotation, you will have to apply a trick similar to what we did in the `flipHorizontally` function. Because you want to rotate around the center of your rectangle and not around the point (0,0), you must first `translate` to there, then rotate, and then translate back. -Make sure you reset the transformation after drawing any shape that -creates one. +Make sure you reset the transformation after drawing any shape that creates one. {{index "remainder operator", "% operator"}} -For the ((zigzag)) (3) it becomes impractical to write a new call to -`lineTo` for each line segment. Instead, you should use a ((loop)). -You can have each iteration draw either two ((line)) segments (right -and then left again) or one, in which case you must use the evenness -(`% 2`) of the loop index to determine whether to go left or right. +For the ((zigzag)) (3) it becomes impractical to write a new call to `lineTo` for each line segment. Instead, you should use a ((loop)). You can have each iteration draw either two ((line)) segments (right and then left again) or one, in which case you must use the evenness (`% 2`) of the loop index to determine whether to go left or right. -You'll also need a loop for the ((spiral)) (4). If you draw a series -of points, with each point moving further along a circle around the -spiral's center, you get a circle. If, during the loop, you vary the -radius of the circle on which you are putting the current point and go -around more than once, the result is a spiral. +You'll also need a loop for the ((spiral)) (4). If you draw a series of points, with each point moving further along a circle around the spiral's center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral. {{index "quadraticCurveTo method"}} -The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. -You could also draw one with straight lines. Divide a circle into -eight pieces for a star with eight points, or however many pieces you -want. Draw lines between these points, making them curve toward the -center of the star. With `quadraticCurveTo`, you can use the center as -the control point. +The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. You could also draw one with straight lines. Divide a circle into eight pieces for a star with eight points, or however many pieces you want. Draw lines between these points, making them curve toward the center of the star. With `quadraticCurveTo`, you can use the center as the control point. hint}} @@ -1342,15 +967,9 @@ hint}} {{index label, text, "pie chart example"}} -[Earlier](canvas#pie_chart) in the chapter, we saw an example program -that drew a pie chart. Modify this program so that the name of each -category is shown next to the slice that represents it. Try to find a -pleasing-looking way to automatically position this text that would -work for other data sets as well. You may assume that categories are -big enough to leave ample room for their labels. +[Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave ample room for their labels. -You might need `Math.sin` and `Math.cos` again, which are described in -[Chapter ?](dom#sin_cos). +You might need `Math.sin` and `Math.cos` again, which are described in [Chapter ?](dom#sin_cos). {{if interactive @@ -1383,19 +1002,11 @@ if}} {{index "fillText method", "textAlign property", "textBaseline property", "pie chart example"}} -You will need to call `fillText` and set the context's `textAlign` and -`textBaseline` properties in such a way that the text ends up where -you want it. +You will need to call `fillText` and set the context's `textAlign` and `textBaseline` properties in such a way that the text ends up where you want it. -A sensible way to position the labels would be to put the text on the -line going from the center of the pie through the middle of the slice. -You don't want to put the text directly against the side of the pie -but rather move the text out to the side of the pie by a given number -of pixels. +A sensible way to position the labels would be to put the text on the line going from the center of the pie through the middle of the slice. You don't want to put the text directly against the side of the pie but rather move the text out to the side of the pie by a given number of pixels. -The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The -following code finds a position on this line 120 pixels from the -center: +The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The following code finds a position on this line 120 pixels from the center: ```{test: no} let middleAngle = currentAngle + 0.5 * sliceAngle; @@ -1403,19 +1014,11 @@ let textX = Math.cos(middleAngle) * 120 + centerX; let textY = Math.sin(middleAngle) * 120 + centerY; ``` -For `textBaseline`, the value `"middle"` is probably appropriate when -using this approach. What to use for `textAlign` depends on which side -of the circle we are on. On the left, it should be `"right"`, and on -the right, it should be `"left"`, so that the text is positioned away -from the pie. +For `textBaseline`, the value `"middle"` is probably appropriate when using this approach. What to use for `textAlign` depends on which side of the circle we are on. On the left, it should be `"right"`, and on the right, it should be `"left"`, so that the text is positioned away from the pie. {{index "Math.cos function"}} -If you are not sure how to find out which side of the circle a given -angle is on, look to the explanation of `Math.cos` in [Chapter -?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it -corresponds to, which in turn tells us exactly which side of the -circle we are on. +If you are not sure how to find out which side of the circle a given angle is on, look to the explanation of `Math.cos` in [Chapter ?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circle we are on. hint}} @@ -1423,10 +1026,7 @@ hint}} {{index [animation, "bouncing ball"], "requestAnimationFrame function", bouncing}} -Use the `requestAnimationFrame` technique that we saw in [Chapter -?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a -((box)) with a bouncing ((ball)) in it. The ball moves at a constant -((speed)) and bounces off the box's sides when it hits them. +Use the `requestAnimationFrame` technique that we saw in [Chapter ?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a ((box)) with a bouncing ((ball)) in it. The ball moves at a constant ((speed)) and bounces off the box's sides when it hits them. {{if interactive @@ -1457,26 +1057,15 @@ if}} {{index "strokeRect method", animation, "arc method"}} -A ((box)) is easy to draw with `strokeRect`. Define a binding that -holds its size or define two bindings if your box's width and height -differ. To create a round ((ball)), start a path and call `arc(x, y, -radius, 0, 7)`, which creates an arc going from zero to more than a -whole circle. Then fill the path. +A ((box)) is easy to draw with `strokeRect`. Define a binding that holds its size or define two bindings if your box's width and height differ. To create a round ((ball)), start a path and call `arc(x, y, radius, 0, 7)`, which creates an arc going from zero to more than a whole circle. Then fill the path. {{index "collision detection", "Vec class"}} -To model the ball's position and ((speed)), you can use the `Vec` -class from [Chapter ?](game#vector)[ (which is available on this -page)]{if interactive}. Give it a starting speed, preferably one that -is not purely vertical or horizontal, and for every ((frame)) multiply -that speed by the amount of time that elapsed. When the ball gets -too close to a vertical wall, invert the x component in its speed. -Likewise, invert the y component when it hits a horizontal wall. +To model the ball's position and ((speed)), you can use the `Vec` class from [Chapter ?](game#vector)[ (which is available on this page)]{if interactive}. Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every ((frame)) multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall. {{index "clearRect method", clearing}} -After finding the ball's new position and speed, use `clearRect` to -delete the scene and redraw it using the new position. +After finding the ball's new position and speed, use `clearRect` to delete the scene and redraw it using the new position. hint}} @@ -1484,38 +1073,21 @@ hint}} {{index optimization, "bitmap graphics", mirror}} -One unfortunate thing about ((transformation))s is that they slow down -the drawing of bitmaps. The position and size of each ((pixel)) has to be -transformed, and though it is possible that ((browser))s will get -cleverer about transformation in the ((future)), they currently cause a -measurable increase in the time it takes to draw a bitmap. +One unfortunate thing about ((transformation))s is that they slow down the drawing of bitmaps. The position and size of each ((pixel)) has to be transformed, and though it is possible that ((browser))s will get cleverer about transformation in the ((future)), they currently cause a measurable increase in the time it takes to draw a bitmap. -In a game like ours, where we are drawing only a single transformed -sprite, this is a nonissue. But imagine that we need to draw hundreds -of characters or thousands of rotating particles from an explosion. +In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion. -Think of a way to allow us to draw an inverted character without -loading additional image files and without having to make transformed -`drawImage` calls every frame. +Think of a way to allow us to draw an inverted character without loading additional image files and without having to make transformed `drawImage` calls every frame. {{hint {{index mirror, scaling, "drawImage method"}} -The key to the solution is the fact that we can use a ((canvas)) -element as a source image when using `drawImage`. It is possible to -create an extra `` element, without adding it to the document, -and draw our inverted sprites to it, once. When drawing an actual -frame, we just copy the already inverted sprites to the main canvas. +The key to the solution is the fact that we can use a ((canvas)) element as a source image when using `drawImage`. It is possible to create an extra `` element, without adding it to the document, and draw our inverted sprites to it, once. When drawing an actual frame, we just copy the already inverted sprites to the main canvas. {{index "load event"}} -Some care would be required because images do not load instantly. We -do the inverted drawing only once, and if we do it before the image -loads, it won't draw anything. A `"load"` handler on the image can be -used to draw the inverted images to the extra canvas. This canvas can -be used as a drawing source immediately (it'll simply be blank until -we draw the character onto it). +Some care would be required because images do not load instantly. We do the inverted drawing only once, and if we do it before the image loads, it won't draw anything. A `"load"` handler on the image can be used to draw the inverted images to the extra canvas. This canvas can be used as a drawing source immediately (it'll simply be blank until we draw the character onto it). hint}} diff --git a/18_http.md b/18_http.md index 3f0713ca4..e920454a1 100644 --- a/18_http.md +++ b/18_http.md @@ -4,10 +4,7 @@ {{quote {author: "Roy Fielding", title: "Architectural Styles and the Design of Network-based Software Architectures", chapter: true} -Communication must be stateless in nature [...] such that each request -from client to server must contain all of the information necessary to -understand the request, and cannot take advantage of any stored -context on the server. +Communication must be stateless in nature [...] such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. quote}} @@ -17,22 +14,13 @@ quote}} {{index [browser, environment]}} -The _Hypertext Transfer Protocol_, already mentioned in [Chapter -?](browser#web), is the mechanism through which data is requested and -provided on the ((World Wide Web)). This chapter describes the -((protocol)) in more detail and explains the way browser -JavaScript has access to it. +The _Hypertext Transfer Protocol_, already mentioned in [Chapter ?](browser#web), is the mechanism through which data is requested and provided on the ((World Wide Web)). This chapter describes the ((protocol)) in more detail and explains the way browser JavaScript has access to it. ## The protocol {{index "IP address"}} -If you type _eloquentjavascript.net/18_http.html_ into your browser's -((address bar)), the ((browser)) first looks up the ((address)) of the -server associated with _eloquentjavascript.net_ and tries to open a -((TCP)) ((connection)) to it on ((port)) 80, the default port for -((HTTP)) traffic. If the ((server)) exists and accepts the connection, -the browser might send something like this: +If you type _eloquentjavascript.net/18_http.html_ into your browser's ((address bar)), the ((browser)) first looks up the ((address)) of the server associated with _eloquentjavascript.net_ and tries to open a ((TCP)) ((connection)) to it on ((port)) 80, the default port for ((HTTP)) traffic. If the ((server)) exists and accepts the connection, the browser might send something like this: ```{lang: http} GET /18_http.html HTTP/1.1 @@ -52,14 +40,11 @@ Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT ... the rest of the document ``` -The browser takes the part of the ((response)) after the blank line, -its _body_ (not to be confused with the HTML `` tag), and -displays it as an ((HTML)) document. +The browser takes the part of the ((response)) after the blank line, its _body_ (not to be confused with the HTML `` tag), and displays it as an ((HTML)) document. {{index HTTP}} -The information sent by the client is called the _((request))_. It -starts with this line: +The information sent by the client is called the _((request))_. It starts with this line: ```{lang: http} GET /18_http.html HTTP/1.1 @@ -67,42 +52,19 @@ GET /18_http.html HTTP/1.1 {{index "DELETE method", "PUT method", "GET method", [method, HTTP]}} -The first word is the _method_ of the ((request)). `GET` means -that we want to _get_ the specified resource. Other common methods are -`DELETE` to delete a resource, `PUT` to create or replace it, and `POST` to send -information to it. Note that the ((server)) is not obliged to carry -out every request it gets. If you walk up to a random website and tell -it to `DELETE` its main page, it'll probably refuse. +The first word is the _method_ of the ((request)). `GET` means that we want to _get_ the specified resource. Other common methods are `DELETE` to delete a resource, `PUT` to create or replace it, and `POST` to send information to it. Note that the ((server)) is not obliged to carry out every request it gets. If you walk up to a random website and tell it to `DELETE` its main page, it'll probably refuse. {{index [path, URL], GitHub, [file, resource]}} -The part after the method name is the path of the _((resource))_ the -request applies to. In the simplest case, a resource is simply a -file on the ((server)), but the protocol doesn't require it to be. -A resource may be anything that can be transferred _as if_ it is a -file. Many servers generate the responses they produce on the fly. For -example, if you open -[_https://github.com/marijnh_](https://github.com/marijnh), the server looks -in its database for a user named "marijnh", and if it finds one, it -will generate a profile page for that user. - -After the resource path, the first line of the request mentions -`HTTP/1.1` to indicate the ((version)) of the ((HTTP)) ((protocol)) it -is using. - -In practice, many sites use HTTP version 2, which supports the same -concepts as version 1.1 but is a lot more complicated so that it can -be faster. Browsers will automatically switch to the appropriate -protocol version when talking to a given server, and the outcome of a -request is the same regardless of which version is used. Because version -1.1 is more straightforward and easier to play around with, we'll -focus on that. +The part after the method name is the path of the _((resource))_ the request applies to. In the simplest case, a resource is simply a file on the ((server)), but the protocol doesn't require it to be. A resource may be anything that can be transferred _as if_ it is a file. Many servers generate the responses they produce on the fly. For example, if you open [_https://github.com/marijnh_](https://github.com/marijnh), the server looks in its database for a user named "marijnh", and if it finds one, it will generate a profile page for that user. + +After the resource path, the first line of the request mentions `HTTP/1.1` to indicate the ((version)) of the ((HTTP)) ((protocol)) it is using. + +In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we'll focus on that. {{index "status code"}} -The server's ((response)) will start with a version as well, followed -by the status of the response, first as a three-digit status code and -then as a human-readable string. +The server's ((response)) will start with a version as well, followed by the status of the response, first as a three-digit status code and then as a human-readable string. ```{lang: http} HTTP/1.1 200 OK @@ -110,20 +72,13 @@ HTTP/1.1 200 OK {{index "200 (HTTP status code)", "error response", "404 (HTTP status code)"}} -Status codes starting with a 2 indicate that the request succeeded. -Codes starting with 4 mean there was something wrong with the -((request)). 404 is probably the most famous HTTP status code—it means -that the resource could not be found. Codes that start with 5 mean an -error happened on the ((server)) and the request is not to blame. +Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the ((request)). 404 is probably the most famous HTTP status code—it means that the resource could not be found. Codes that start with 5 mean an error happened on the ((server)) and the request is not to blame. {{index HTTP}} {{id headers}} -The first line of a request or response may be followed by any number -of _((header))s_. These are lines in the form `name: value` that -specify extra information about the request or response. These headers -were part of the example ((response)): +The first line of a request or response may be followed by any number of _((header))s_. These are lines in the form `name: value` that specify extra information about the request or response. These headers were part of the example ((response)): ```{lang: null} Content-Length: 65585 @@ -133,46 +88,27 @@ Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT {{index "Content-Length header", "Content-Type header", "Last-Modified header"}} -This tells us the size and type of the response document. In this -case, it is an HTML document of 65,585 bytes. It also tells us when -that document was last modified. +This tells us the size and type of the response document. In this case, it is an HTML document of 65,585 bytes. It also tells us when that document was last modified. {{index "Host header", domain}} -For most ((header))s, the client and server are free to decide whether -to include them in a ((request)) or ((response)). But a few are -required. For example, the `Host` header, which specifies the -hostname, should be included in a request because a ((server)) might -be serving multiple hostnames on a single ((IP address)), and without -that header, the server won't know which hostname the client is trying -to talk to. +For most ((header))s, the client and server are free to decide whether to include them in a ((request)) or ((response)). But a few are required. For example, the `Host` header, which specifies the hostname, should be included in a request because a ((server)) might be serving multiple hostnames on a single ((IP address)), and without that header, the server won't know which hostname the client is trying to talk to. {{index "GET method", "DELETE method", "PUT method", "POST method", "body (HTTP)"}} -After the headers, both requests and responses may include a blank -line followed by a body, which contains the data being sent. `GET` and -`DELETE` requests don't send along any data, but `PUT` and `POST` -requests do. Similarly, some response types, such as error responses, -do not require a body. +After the headers, both requests and responses may include a blank line followed by a body, which contains the data being sent. `GET` and `DELETE` requests don't send along any data, but `PUT` and `POST` requests do. Similarly, some response types, such as error responses, do not require a body. ## Browsers and HTTP {{index HTTP, [file, resource]}} -As we saw in the example, a ((browser)) will make a request when we -enter a ((URL)) in its ((address bar)). When the resulting HTML page -references other files, such as ((image))s and JavaScript files, -those are also retrieved. +As we saw in the example, a ((browser)) will make a request when we enter a ((URL)) in its ((address bar)). When the resulting HTML page references other files, such as ((image))s and JavaScript files, those are also retrieved. {{index parallelism, "GET method"}} -A moderately complicated ((website)) can easily include anywhere from -10 to 200 ((resource))s. To be able to fetch those quickly, browsers -will make several `GET` requests simultaneously, rather than waiting -for the responses one at a time. +A moderately complicated ((website)) can easily include anywhere from 10 to 200 ((resource))s. To be able to fetch those quickly, browsers will make several `GET` requests simultaneously, rather than waiting for the responses one at a time. -HTML pages may include _((form))s_, which allow the user to fill out -information and send it to the server. This is an example of a form: +HTML pages may include _((form))s_, which allow the user to fill out information and send it to the server. This is an example of a form: ```{lang: "text/html"}

@@ -184,16 +120,9 @@ information and send it to the server. This is an example of a form: {{index form, "method attribute", "GET method"}} -This code describes a form with two ((field))s: a small one asking for -a name and a larger one to write a message in. When you click the Send -((button)), the form is _submitted_, meaning that the content of its -field is packed into an HTTP request and the browser navigates to the -result of that request. +This code describes a form with two ((field))s: a small one asking for a name and a larger one to write a message in. When you click the Send ((button)), the form is _submitted_, meaning that the content of its field is packed into an HTTP request and the browser navigates to the result of that request. -When the `` element's `method` attribute is `GET` (or is -omitted), the information in the form is added to the end of the -`action` URL as a _((query string))_. The browser might make a request -to this URL: +When the `` element's `method` attribute is `GET` (or is omitted), the information in the form is added to the end of the `action` URL as a _((query string))_. The browser might make a request to this URL: ```{lang: null} GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1 @@ -201,24 +130,11 @@ GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1 {{index "ampersand character"}} -The ((question mark)) indicates the end of the path part of the URL -and the start of the query. It is followed by pairs of names and -values, corresponding to the `name` attribute on the form field -elements and the content of those elements, respectively. An ampersand -character (`&`) is used to separate the pairs. +The ((question mark)) indicates the end of the path part of the URL and the start of the query. It is followed by pairs of names and values, corresponding to the `name` attribute on the form field elements and the content of those elements, respectively. An ampersand character (`&`) is used to separate the pairs. {{index [escaping, "in URLs"], "hexadecimal number", "encodeURIComponent function", "decodeURIComponent function"}} -The actual message encoded in the URL is "Yes?", but the question mark -is replaced by a strange code. Some characters in query strings must -be escaped. The question mark, represented as `%3F`, is one of those. -There seems to be an unwritten rule that every format needs its own -way of escaping characters. This one, called _((URL encoding))_, uses -a ((percent sign)) followed by two hexadecimal (base 16) digits that -encode the character code. In this case, 3F, which is 63 in decimal -notation, is the code of a question mark character. JavaScript -provides the `encodeURIComponent` and `decodeURIComponent` functions -to encode and decode this format. +The actual message encoded in the URL is "Yes?", but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as `%3F`, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called _((URL encoding))_, uses a ((percent sign)) followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the `encodeURIComponent` and `decodeURIComponent` functions to encode and decode this format. ``` console.log(encodeURIComponent("Yes?")); @@ -229,10 +145,7 @@ console.log(decodeURIComponent("Yes%3F")); {{index "body (HTTP)", "POST method"}} -If we change the `method` attribute of the HTML form in the example we -saw earlier to `POST`, the ((HTTP)) request made to submit the -((form)) will use the `POST` method and put the ((query string)) in -the body of the request, rather than adding it to the URL. +If we change the `method` attribute of the HTML form in the example we saw earlier to `POST`, the ((HTTP)) request made to submit the ((form)) will use the `POST` method and put the ((query string)) in the body of the request, rather than adding it to the URL. ```{lang: http} POST /example/message.html HTTP/1.1 @@ -242,16 +155,9 @@ Content-type: application/x-www-form-urlencoded name=Jean&message=Yes%3F ``` -`GET` requests should be used for requests that do not have ((side -effect))s but simply ask for information. Requests that change -something on the server, for example creating a new account or posting -a message, should be expressed with other methods, such as `POST`. -Client-side software such as a browser knows that it shouldn't blindly -make `POST` requests but will often implicitly make `GET` requests—for -example to prefetch a resource it believes the user will soon need. +`GET` requests should be used for requests that do not have ((side effect))s but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as `POST`. Client-side software such as a browser knows that it shouldn't blindly make `POST` requests but will often implicitly make `GET` requests—for example to prefetch a resource it believes the user will soon need. -We'll come back to forms and how to interact with them from JavaScript -[later in the chapter](http#forms). +We'll come back to forms and how to interact with them from JavaScript [later in the chapter](http#forms). {{id fetch}} @@ -259,9 +165,7 @@ We'll come back to forms and how to interact with them from JavaScript {{index "fetch function", "Promise class", [interface, module]}} -The interface through which browser JavaScript can make HTTP -requests is called `fetch`. Since it is relatively new, it -conveniently uses promises (which is rare for browser interfaces). +The interface through which browser JavaScript can make HTTP requests is called `fetch`. Since it is relatively new, it conveniently uses promises (which is rare for browser interfaces). ```{test: no} fetch("example/data.txt").then(response => { @@ -274,35 +178,17 @@ fetch("example/data.txt").then(response => { {{index "Response class", "status property", "headers property"}} -Calling `fetch` returns a promise that resolves to a `Response` object -holding information about the server's response, such as its status -code and its headers. The headers are wrapped in a `Map`-like object -that treats its keys (the header names) as case insensitive because -header names are not supposed to be case sensitive. This means -`headers.get("Content-Type")` and `headers.get("content-TYPE")` will -return the same value. +Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. -Note that the promise returned by `fetch` resolves successfully even -if the server responded with an error code. It _might_ also be -rejected if there is a network error or if the ((server)) that the -request is addressed to can't be found. +Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It _might_ also be rejected if there is a network error or if the ((server)) that the request is addressed to can't be found. {{index [path, URL], "relative URL"}} -The first argument to `fetch` is the URL that should be requested. -When that ((URL)) doesn't start with a protocol name (such as _http:_), -it is treated as _relative_, which means it is interpreted relative -to the current document. When it starts with a slash (/), it replaces -the current path, which is the part after the server name. When it -does not, the part of the current path up to and including its last -((slash character)) is put in front of the relative URL. +The first argument to `fetch` is the URL that should be requested. When that ((URL)) doesn't start with a protocol name (such as _http:_), it is treated as _relative_, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last ((slash character)) is put in front of the relative URL. {{index "text method", "body (HTTP)", "Promise class"}} -To get at the actual content of a response, you can use its `text` -method. Because the initial promise is resolved as soon as the -response's headers have been received and because reading the response body -might take a while longer, this again returns a promise. +To get at the actual content of a response, you can use its `text` method. Because the initial promise is resolved as soon as the response's headers have been received and because reading the response body might take a while longer, this again returns a promise. ```{test: no} fetch("example/data.txt") @@ -313,16 +199,11 @@ fetch("example/data.txt") {{index "json method"}} -A similar method, called `json`, returns a promise that -resolves to the value you get when parsing the body as ((JSON)) or -rejects if it's not valid JSON. +A similar method, called `json`, returns a promise that resolves to the value you get when parsing the body as ((JSON)) or rejects if it's not valid JSON. {{index "GET method", "body (HTTP)", "DELETE method", "method property"}} -By default, `fetch` uses the `GET` method to make its request and -does not include a request body. You can configure it differently by -passing an object with extra options as a second argument. For -example, this request tries to delete `example/data.txt`: +By default, `fetch` uses the `GET` method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete `example/data.txt`: ```{test: no} fetch("example/data.txt", {method: "DELETE"}).then(resp => { @@ -333,15 +214,11 @@ fetch("example/data.txt", {method: "DELETE"}).then(resp => { {{index "405 (HTTP status code)"}} -The 405 status code means "method not allowed", an HTTP server's way -of saying "I can't do that". +The 405 status code means "method not allowed", an HTTP server's way of saying "I can't do that". {{index "Range header", "body property", "headers property"}} -To add a request body, you can include a `body` option. To set -headers, there's the `headers` option. For example, this request -includes a `Range` header, which instructs the server to return only -part of a response. +To add a request body, you can include a `body` option. To set headers, there's the `headers` option. For example, this request includes a `Range` header, which instructs the server to return only part of a response. ```{test: no} fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) @@ -350,11 +227,7 @@ fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) // → the content ``` -The browser will automatically add some request ((header))s, such as -"Host" and those needed for the server to figure out the size of the -body. But adding your own headers is often useful to include things -such as authentication information or to tell the server which file -format you'd like to receive. +The browser will automatically add some request ((header))s, such as "Host" and those needed for the server to figure out the size of the body. But adding your own headers is often useful to include things such as authentication information or to tell the server which file format you'd like to receive. {{id http_sandbox}} @@ -362,25 +235,13 @@ format you'd like to receive. {{index sandbox, [browser, security]}} -Making ((HTTP)) requests in web page scripts once again raises -concerns about ((security)). The person who controls the script might -not have the same interests as the person on whose computer it is -running. More specifically, if I visit _themafia.org_, I do not want -its scripts to be able to make a request to _mybank.com_, using -identifying information from my browser, with instructions to -transfer all my money to some random account. +Making ((HTTP)) requests in web page scripts once again raises concerns about ((security)). The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if I visit _themafia.org_, I do not want its scripts to be able to make a request to _mybank.com_, using identifying information from my browser, with instructions to transfer all my money to some random account. -For this reason, browsers protect us by disallowing scripts to make -HTTP requests to other ((domain))s (names such as _themafia.org_ and -_mybank.com_). +For this reason, browsers protect us by disallowing scripts to make HTTP requests to other ((domain))s (names such as _themafia.org_ and _mybank.com_). {{index "Access-Control-Allow-Origin header", "cross-domain request"}} -This can be an annoying problem when building systems that want to -access several domains for legitimate reasons. Fortunately, -((server))s can include a ((header)) like this in their ((response)) -to explicitly indicate to the browser that it is okay for the request -to come from another domain: +This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, ((server))s can include a ((header)) like this in their ((response)) to explicitly indicate to the browser that it is okay for the request to come from another domain: ```{lang: null} Access-Control-Allow-Origin: * @@ -390,110 +251,55 @@ Access-Control-Allow-Origin: * {{index client, HTTP, [interface, HTTP]}} -When building a system that requires ((communication)) between a -JavaScript program running in the ((browser)) (client-side) and a -program on a ((server)) (server-side), there are several different -ways to model this communication. +When building a system that requires ((communication)) between a JavaScript program running in the ((browser)) (client-side) and a program on a ((server)) (server-side), there are several different ways to model this communication. {{index [network, abstraction], abstraction}} -A commonly used model is that of _((remote procedure call))s_. In this -model, communication follows the patterns of normal function calls, -except that the function is actually running on another machine. -Calling it involves making a request to the server that includes the -function's name and arguments. The response to that request contains -the returned value. +A commonly used model is that of _((remote procedure call))s_. In this model, communication follows the patterns of normal function calls, except that the function is actually running on another machine. Calling it involves making a request to the server that includes the function's name and arguments. The response to that request contains the returned value. -When thinking in terms of remote procedure calls, HTTP is just a -vehicle for communication, and you will most likely write an -abstraction layer that hides it entirely. +When thinking in terms of remote procedure calls, HTTP is just a vehicle for communication, and you will most likely write an abstraction layer that hides it entirely. {{index "media type", "document format", [method, HTTP]}} -Another approach is to build your communication around the concept of -((resource))s and ((HTTP)) methods. Instead of a remote procedure -called `addUser`, you use a `PUT` request to `/users/larry`. Instead -of encoding that user's properties in function arguments, you define a -JSON document format (or use an existing format) that represents a -user. The body of the `PUT` request to create a new resource is then -such a document. A resource is fetched by making a `GET` request to -the resource's URL (for example, `/user/larry`), which again returns -the document representing the resource. - -This second approach makes it easier to use some of the features that -HTTP provides, such as support for caching resources (keeping a copy -on the client for fast access). The concepts used in HTTP, which are -well designed, can provide a helpful set of principles to design your -server interface around. +Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/user/larry`), which again returns the document representing the resource. + +This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around. ## Security and HTTPS {{index "man-in-the-middle", security, HTTPS, [network, security]}} -Data traveling over the Internet tends to follow a long, dangerous -road. To get to its destination, it must hop through anything from -coffee shop Wi-Fi hotspots to networks controlled by various companies and -states. At any point along its route it may be inspected or even -modified. +Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route it may be inspected or even modified. {{index tampering}} -If it is important that something remain secret, such as the -((password)) to your ((email)) account, or that it arrive at its -destination unmodified, such as the account number you transfer money -to via your bank's website, plain HTTP is not good enough. +If it is important that something remain secret, such as the ((password)) to your ((email)) account, or that it arrive at its destination unmodified, such as the account number you transfer money to via your bank's website, plain HTTP is not good enough. {{index cryptography, encryption}} {{indexsee "Secure HTTP", HTTPS, [browser, security]}} -The secure ((HTTP)) protocol, used for ((URL))s starting with _https://_, -wraps HTTP traffic in a way that makes it harder to read and tamper -with. Before exchanging data, the client verifies that the server is -who it claims to be by asking it to prove that it has a cryptographic -((certificate)) issued by a certificate authority that the browser -recognizes. Next, all data going over the ((connection)) is encrypted -in a way that should prevent eavesdropping and tampering. - -Thus, when it works right, ((HTTPS)) prevents other people from -impersonating the website you are trying to talk to and from -snooping on your communication. It is not perfect, and there have been -various incidents where HTTPS failed because of forged or stolen -certificates and broken software, but it is a _lot_ safer than plain -HTTP. +The secure ((HTTP)) protocol, used for ((URL))s starting with _https://_, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic ((certificate)) issued by a certificate authority that the browser recognizes. Next, all data going over the ((connection)) is encrypted in a way that should prevent eavesdropping and tampering. + +Thus, when it works right, ((HTTPS)) prevents other people from impersonating the website you are trying to talk to and from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a _lot_ safer than plain HTTP. {{id forms}} ## Form fields -Forms were originally designed for the pre-JavaScript Web to allow -web sites to send user-submitted information in an HTTP request. This -design assumes that interaction with the server always happens by -navigating to a new page. +Forms were originally designed for the pre-JavaScript Web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. {{index [DOM, fields]}} -But their elements are part of the DOM like the rest of the page, -and the DOM elements that represent form ((field))s support a number -of properties and events that are not present on other elements. These -make it possible to inspect and control such input fields with -JavaScript programs and do things such as adding new functionality to -a form or using forms and fields as building blocks in a JavaScript -application. +But their elements are part of the DOM like the rest of the page, and the DOM elements that represent form ((field))s support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application. {{index "form (HTML tag)"}} -A web form consists of any number of input ((field))s grouped in a -`` tag. HTML allows several different styles of fields, ranging -from simple on/off checkboxes to drop-down menus and fields for text -input. This book won't try to comprehensively discuss all field types, -but we'll start with a rough overview. +A web form consists of any number of input ((field))s grouped in a `` tag. HTML allows several different styles of fields, ranging from simple on/off checkboxes to drop-down menus and fields for text input. This book won't try to comprehensively discuss all field types, but we'll start with a rough overview. {{index "input (HTML tag)", "type attribute"}} -A lot of field types use the -`` tag. This tag's `type` attribute is used to select the -field's style. These are some commonly used `` types: +A lot of field types use the `` tag. This tag's `type` attribute is used to select the field's style. These are some commonly used `` types: {{index "password field", checkbox, "radio button", "file field"}} @@ -507,11 +313,7 @@ field's style. These are some commonly used `` types: {{index "value attribute", "checked attribute", "form (HTML tag)"}} -Form fields do not necessarily have to appear in a `` tag. You -can put them anywhere in a page. Such form-less fields cannot be -((submit))ted (only a form as a whole can), but when responding to -input with JavaScript, we often don't want to submit our fields -normally anyway. +Form fields do not necessarily have to appear in a `` tag. You can put them anywhere in a page. Such form-less fields cannot be ((submit))ted (only a form as a whole can), but when responding to input with JavaScript, we often don't want to submit our fields normally anyway. ```{lang: "text/html"}

(text)

@@ -531,16 +333,11 @@ The fields created with this HTML code look like this: if}} -The JavaScript interface for such elements differs with the type of -the element. +The JavaScript interface for such elements differs with the type of the element. {{index "textarea (HTML tag)", "text field"}} -Multiline text fields have their own tag, `` -closing tag and uses the text between those two, instead of the -`value` attribute, as starting text. +Multiline text fields have their own tag, `` closing tag and uses the text between those two, instead of the `value` attribute, as starting text. ```{lang: "text/html"} @@ -800,23 +530,13 @@ tag with an event handler that, when you press F2, inserts the string {{index "replaceSelection function", "text field"}} -The `replaceSelection` -function replaces the currently selected part of a text field's -content with the given word and then moves the ((cursor)) after that -word so that the user can continue typing. +The `replaceSelection` function replaces the currently selected part of a text field's content with the given word and then moves the ((cursor)) after that word so that the user can continue typing. {{index "change event", "input event"}} -The `"change"` event for a ((text -field)) does not fire every time something is typed. Rather, it -fires when the field loses ((focus)) after its content was changed. -To respond immediately to changes in a text field, you should register -a handler for the `"input"` event instead, which fires for every -time the user types a character, deletes text, or otherwise manipulates -the field's content. +The `"change"` event for a ((text field)) does not fire every time something is typed. Rather, it fires when the field loses ((focus)) after its content was changed. To respond immediately to changes in a text field, you should register a handler for the `"input"` event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field's content. -The following example shows a text field and a counter displaying the -current length of the text in the field: +The following example shows a text field and a counter displaying the current length of the text in the field: ```{lang: "text/html"} length: 0 @@ -833,8 +553,7 @@ current length of the text in the field: {{index "input (HTML tag)", "checked attribute"}} -A ((checkbox)) field is a binary toggle. Its value can be extracted or -changed through its `checked` property, which holds a Boolean value. +A ((checkbox)) field is a binary toggle. Its value can be extracted or changed through its `checked` property, which holds a Boolean value. ```{lang: "text/html"}

@@ -27,7 +27,7 @@ <> <> <<> -   +   From 88e88697bf36ea5b47e6b85e00e9cb16449964ed Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 19 Feb 2024 11:36:29 +0100 Subject: [PATCH 262/392] Light pass over chapter 12 --- 12_language.md | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/12_language.md b/12_language.md index 75addd355..7723036fa 100644 --- a/12_language.md +++ b/12_language.md @@ -126,7 +126,7 @@ Because Egg, like JavaScript, allows any amount of whitespace between its elemen {{index "literal expression", "SyntaxError type"}} -After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use `SyntaxError` instead of `Error` as the exception constructor, which is another standard error type, because it is a little more specific—it is also the error type thrown when an attempt is made to run an invalid JavaScript program. +After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use the `SyntaxError` constructor here. This is an exception class defined by the standard, like `Error`, but more specific. {{index "parseApply function"}} @@ -237,7 +237,7 @@ We use plain JavaScript function values to represent Egg's function values. We w {{index readability, "evaluate function", recursion, parsing}} -The recursive structure of `evaluate` resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to combinge the parser and the evaluator into one function, and evaluate during parsing. But splitting them up this way makes the program clearer and more flexible. +The recursive structure of `evaluate` resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to combine the parser and the evaluator into one function, and evaluate during parsing. But splitting them up this way makes the program clearer and more flexible. {{index "Egg language", interpretation}} @@ -412,13 +412,13 @@ specialForms.fun = (args, scope) => { return expr.name; }); - return function() { - if (arguments.length != params.length) { + return function(...args) { + if (args.length != params.length) { throw new TypeError("Wrong number of arguments"); } let localScope = Object.create(scope); - for (let i = 0; i < arguments.length; i++) { - localScope[params[i]] = arguments[i]; + for (let i = 0; i < args.length; i++) { + localScope[params[i]] = args[i]; } return evaluate(body, localScope); }; @@ -468,32 +468,26 @@ If you are interested in this topic and willing to spend some time on it, I enco {{index "Egg language"}} -When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. +When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. Bridging the gap to a more primitive system, such as the machine code the processor understands, is more effort—but the way it works resembles what we are doing here. -If you compare the implementation of Egg, built on top of JavaScript, with the amount of work and complexity required to build a programming language directly on the raw functionality provided by a machine, the difference is huge. Regardless, this example ideally gave you an impression of the way ((programming language))s work. - -And when it comes to getting something done, cheating is more effective than doing everything yourself. Though the toy language in this chapter doesn't do anything that couldn't be done better in JavaScript, there _are_ situations where writing small languages helps get real work done. +Though the toy language in this chapter doesn't do anything that couldn't be done better in JavaScript, there _are_ situations where writing small languages helps get real work done. Such a language does not have to resemble a typical programming language. If JavaScript didn't come equipped with regular expressions, for example, you could write your own parser and evaluator for regular expressions. -{{index "artificial intelligence"}} +{{index "parser generator"}} -Or imagine you are building a giant robotic ((dinosaur)) and need to program its ((behavior)). JavaScript might not be the most effective way to do this. You might instead opt for a language that looks like this: +Or imagine you are building a program that makes it possible to quickly create parsers by providing a logical description of the language they need to parse. You could define a specific notation for that, and a compiler that compiles it to a parser program. ```{lang: null} -behavior walk - perform when - destination ahead - actions - move left-foot - move right-foot - -behavior attack - perform when - Godzilla in-view - actions - fire laser-eyes - launch arm-rockets +expr = number | string | name | application + +number = digit+ + +name = letter+ + +string = '"' (! '"')* '"' + +application = expr '(' (expr (',' expr)*)? ')' ``` {{index expressivity}} From 1e5942b87c470be7811acf61aebd8ff00db87f57 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 19 Feb 2024 12:55:57 +0100 Subject: [PATCH 263/392] Light pass over chapter 13 --- 13_browser.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/13_browser.md b/13_browser.md index 4edba4079..16b402d5b 100644 --- a/13_browser.md +++ b/13_browser.md @@ -42,7 +42,7 @@ There are a lot more rules about the way the requester can include more informat {{index layering, stream, ordering}} -Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Ensuring that on top of the primitive data-sending that the network gives you is already a rather tricky problem. +Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Providing those guarantees that on top of the primitive data-sending that the network gives you is already a rather tricky problem. {{index TCP}} @@ -218,7 +218,7 @@ You can load ((ES modules)) (see [Chapter ?](modules#es)) in the browser by givi {{index "button (HTML tag)", "onclick attribute"}} -Some attributes can also contain a JavaScript program. The ` @@ -244,7 +244,7 @@ The hard part of sandboxing is allowing the programs enough room to be useful ye {{index leak, exploit, security}} -Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or ((mafia)). +Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or criminal organization. ## Compatibility and the browser wars @@ -261,3 +261,5 @@ Mozilla ((Firefox)), a not-for-profit offshoot of ((Netscape)), challenged Inter {{index compatibility}} The new players had a more serious attitude toward ((standards)) and better ((engineering)) practices, giving us less incompatibility and fewer ((bug))s. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaces Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs. + +Unfortunately, with Firefox's market share getting ever smaller, and Edge becoming just a wrapper around Chrome's core in 2018, this uniformity might once again take the form of a single vendor—Google, this time—having enough control over the browser market to push its idea of what the Web should look like onto the rest of the world. From 2d277206262812f639fdf3c19bb13abe4aad9a4e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 20 Feb 2024 10:17:06 +0100 Subject: [PATCH 264/392] Prepare chapter 14 for editing --- 14_dom.md | 16 +++++++++------- img/html-tree.svg | 14 ++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/14_dom.md b/14_dom.md index 67e609860..55cb27102 100644 --- a/14_dom.md +++ b/14_dom.md @@ -314,7 +314,7 @@ Some element ((attribute))s, such as `href` for links, can be accessed through a {{index "data attribute", "getAttribute method", "setAttribute method", attribute}} -But HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such attributes will not be present as properties on the element's node. Instead, you have to use the `getAttribute` and `setAttribute` methods to work with them. +HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. To read or change custom attributes, which aren't available as regular object properties, you have to use the `getAttribute` and `setAttribute` methods. ```{lang: html}

The launch code is 00000000.

@@ -334,7 +334,7 @@ It is recommended to prefix the names of such made-up attributes with `data-` to {{index "getAttribute method", "setAttribute method", "className property", "class attribute"}} -There is a commonly used attribute, `class`, which is a ((keyword)) in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called `className`. You can also access it under its real name, `"class"`, by using the `getAttribute` and `setAttribute` methods. +There is a commonly used attribute, `class`, which is a ((keyword)) in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called `className`. You can also access it under its real name, `"class"` with the `getAttribute` and `setAttribute` methods. ## Layout @@ -360,7 +360,9 @@ Similarly, `clientWidth` and `clientHeight` give you the size of the space _insi ``` @@ -372,15 +374,15 @@ Giving a paragraph a border causes a rectangle to be drawn around it. if}} -{{index "getBoundingClientRect method", position, "pageXOffset property", "pageYOffset property"}} +{{index "getBoundingClientRect method", position}} {{id boundingRect}} -The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, which you can find in the `pageXOffset` and `pageYOffset` bindings. +The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. {{index "offsetHeight property", "getBoundingClientRect method", drawing, laziness, performance, efficiency}} -Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing correct information also requires computing a ((layout)). +Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing that information also requires computing a ((layout)). {{index "side effect", optimization, benchmark}} @@ -575,7 +577,7 @@ The `querySelectorAll` method, which is defined both on the `document` object an {{index "live data structure"}} -Unlike methods such as `getElementsByTagName`, the object returned by `querySelectorAll` is _not_ live. It won't change when you change the document. It is still not a real array, though, so you still need to call `Array.from` if you want to treat it like one. +Unlike methods such as `getElementsByTagName`, the object returned by `querySelectorAll` is _not_ live. It won't change when you change the document. It is still not a real array, though, so you need to call `Array.from` if you want to treat it like one. {{index "querySelector method"}} @@ -753,7 +755,7 @@ hint}} {{index "getElementsByTagName method", recursion}} -The `document.getElementsByTagName` method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name. +The `document.getElementsByTagName` method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name. Your function should go through the document itself. It may not use a method like `querySelectorAll` to do the work. {{index "nodeName property", capitalization, "toLowerCase method", "toUpperCase method"}} diff --git a/img/html-tree.svg b/img/html-tree.svg index c6c35108d..cbf501640 100644 --- a/img/html-tree.svg +++ b/img/html-tree.svg @@ -8,11 +8,11 @@ } .box { stroke: #666; - fill: url(#boxgrad); + fill: transparent; border-radius: 4px; } .leafbox { - fill: url(#leafgrad); + stroke: transparent; } .boxtext { font-size: 18px; @@ -23,14 +23,4 @@ .leafboxtext { font-family: "Georgia"; } .arrow { stroke: black; } - - - - - - - - - - htmlheadtitleMy home pagebodyh1My home pagepHello! I am...pI also wrote...herea. From 03fc370b5803299f37f6f74e8a0eedfecebec6a1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 20 Feb 2024 11:08:51 +0100 Subject: [PATCH 265/392] Prevent default on arrow/pageup/down keys in game chapters --- 16_game.md | 2 +- 17_canvas.md | 2 +- code/_stop_keys.js | 3 +++ src/chapter_info.mjs | 2 +- src/client/code.mjs | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 code/_stop_keys.js diff --git a/16_game.md b/16_game.md index 748175252..f5311fbc2 100644 --- a/16_game.md +++ b/16_game.md @@ -1,4 +1,4 @@ -{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js"], zip: "html include=[\"css/game.css\"]"}}} +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/_stop_keys.js"], zip: "html include=[\"css/game.css\"]"}}} # Project: A Platform Game diff --git a/17_canvas.md b/17_canvas.md index de3ed941e..643df27b6 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1,4 +1,4 @@ -{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/_stop_keys.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} # Drawing on Canvas diff --git a/code/_stop_keys.js b/code/_stop_keys.js new file mode 100644 index 000000000..bc5de4e01 --- /dev/null +++ b/code/_stop_keys.js @@ -0,0 +1,3 @@ +window.addEventListener("keydown", e => { + if (/Arrow|Home|End|Page/.test(e.key)) e.preventDefault() +}) diff --git a/src/chapter_info.mjs b/src/chapter_info.mjs index 7360c94ff..67e6bf1cf 100644 --- a/src/chapter_info.mjs +++ b/src/chapter_info.mjs @@ -189,7 +189,7 @@ function chapterZipFile(meta, chapter) { if (!chapter.start_code) throw new Error("zip but no start code"); let data = /(\S+)(?:\s+include=(.*))?/.exec(JSON.parse(spec[1])) let name = "code/chapter/" + chapter.id + ".zip"; - let files = (chapter.include || []).concat(data[2] ? JSON.parse(data[2]) : []); + let files = (chapter.include || []).concat(data[2] ? JSON.parse(data[2]) : []).filter(f => !/(^|\/)_/.test(f)); let exists = fs.existsSync(name) && fs.statSync(name).mtime; if (exists && files.every(file => fs.statSync("html/" + file).mtime < exists)) return name; diff --git a/src/client/code.mjs b/src/client/code.mjs index ff790d2ab..46de7acac 100644 --- a/src/client/code.mjs +++ b/src/client/code.mjs @@ -170,7 +170,7 @@ class CodeSandbox { }) if (chapter.include) chapter.include.forEach((file, i) => { if (!i) this.fileInfo.style.display = "" - addItem(this.fileList, file) + if (!/(^|\/)_/.test(file)) addItem(this.fileList, file) }) this.selectContext(context || "box") } From 1effc2219f0a5e3b136948f0bde97eab5060eff8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 20 Feb 2024 17:20:56 +0100 Subject: [PATCH 266/392] Prepare chapter 15 for editing --- 15_event.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/15_event.md b/15_event.md index 62785b14e..7629596f4 100644 --- a/15_event.md +++ b/15_event.md @@ -280,7 +280,7 @@ To get precise information about the place where a mouse event happened, you can {{id mouse_drawing}} -The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See [Chapter ?](paint) for a less primitive drawing program. +The following program implements a primitive drawing application. Every time you click the document, it adds a dot under your mouse pointer. See [Chapter ?](paint) for a less primitive drawing application. ```{lang: html} From 5a1e5a3b63871566eff87119b71520ed350e3a50 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 20 Feb 2024 17:22:05 +0100 Subject: [PATCH 267/392] Restore mention of pageXOffset/pageYOffset They are used in the next chapter. --- 14_dom.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/14_dom.md b/14_dom.md index 55cb27102..d21f4f5b7 100644 --- a/14_dom.md +++ b/14_dom.md @@ -374,11 +374,11 @@ Giving a paragraph a border causes a rectangle to be drawn around it. if}} -{{index "getBoundingClientRect method", position}} +{{index "getBoundingClientRect method", position, "pageXOffset property", "pageYOffset property"}} {{id boundingRect}} -The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. +The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, which you can find in the `pageXOffset` and `pageYOffset` bindings. {{index "offsetHeight property", "getBoundingClientRect method", drawing, laziness, performance, efficiency}} From d350e0c8e8f3c5225089f9002bf19f62f0fb0e56 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 23 Feb 2024 10:56:53 +0100 Subject: [PATCH 268/392] Prepare chapter 16 for editing --- 16_game.md | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/16_game.md b/16_game.md index f5311fbc2..ff77abb54 100644 --- a/16_game.md +++ b/16_game.md @@ -174,7 +174,7 @@ This is again a persistent data structure—updating the game state creates a ne {{index actor, "Vec class", [interface, object]}} -Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. Their `pos` property holds the coordinates of the element's top-left corner, and their `size` property holds its size. +Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. They have `size` and `pos` properties holding the size and the coordinates of the top-left corner of the rectangle representing this actor. Then they have an `update` method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object. @@ -316,29 +316,15 @@ console.log(`${simpleLevel.width} by ${simpleLevel.height}`); The task ahead is to display such levels on the screen and to model time and motion inside them. -## Encapsulation as a burden - -{{index "programming style", "program size", complexity}} - -Most of the code in this chapter does not worry about ((encapsulation)) very much for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, I've made an effort to keep the program small. - -{{index [interface, design]}} - -Second, the various elements in this game are so closely tied together that if the behavior of one of them changed, it is unlikely that any of the others would be able to stay the same. Interfaces between the elements would end up encoding a lot of assumptions about the way the game works. This makes them a lot less effective—whenever you change one part of the system, you still have to worry about the way it impacts the other parts because their interfaces wouldn't cover the new situation. - -Some _((cutting point))s_ in a system lend themselves well to separation through rigorous interfaces, but others don't. Trying to encapsulate something that isn't a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you'll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be changed often, as the program evolves. - -{{index graphics, encapsulation, graphics}} - -There is one thing that we _will_ encapsulate, and that is the ((drawing)) subsystem. The reason for this is that we'll ((display)) the same game in a different way in the [next chapter](canvas#canvasdisplay). By putting the drawing behind an interface, we can load the same game program there and plug in a new display ((module)). - {{id domdisplay}} ## Drawing -{{index "DOMDisplay class", [DOM, graphics]}} +{{index graphics, encapsulation, "DOMDisplay class", [DOM, graphics]}} + +In the [next chapter](canvas#canvasdisplay), we'll ((display)) the same game in a different way. To make that possible, we put the drawing logic behind an interface, and pass it to the game as an argument. That way, we can use the same game program with different new display ((module))s. -The encapsulation of the ((drawing)) code is done by defining a _((display))_ object, which displays a given ((level)) and state. The display type we define in this chapter is called `DOMDisplay` because it uses DOM elements to show the level. +A game display object draws a given ((level)) and state. We pass its constructor to the game to allow it to be replaced. The display class we define in this chapter is called `DOMDisplay` because it uses DOM elements to show the level. {{index "style attribute", CSS}} @@ -399,7 +385,7 @@ function drawGrid(level) { {{index "table (HTML tag)", "tr (HTML tag)", "td (HTML tag)", "spread operator"}} -As mentioned, the background is drawn as a `` element. This nicely corresponds to the structure of the `rows` property of the level—each row of the grid is turned into a table row (`` element). The strings in the grid are used as class names for the table cell (`
`) elements. The spread (triple dot) operator is used to pass arrays of child nodes to `elt` as separate arguments. +The `` element's form nicely corresponds to the structure of the `rows` property of the level—each row of the grid is turned into a table row (`` element). The strings in the grid are used as class names for the table cell (`
`) elements. The code uses the spread (triple dot) operator to pass arrays of child nodes to `elt` as separate arguments. {{id game_css}} @@ -420,7 +406,7 @@ Some of these (`table-layout`, `border-spacing`, and `padding`) are used to supp {{index "background (CSS)", "rgb (CSS)", CSS}} -The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) or with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. You can see that in the `.lava` rule, the first number (red) is the largest. +The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) or with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. In the `.lava` rule, the first number (red) is the largest. {{index [DOM, graphics]}} @@ -568,7 +554,7 @@ Now we're at the point where we can start adding motion. The basic approach, tak {{index obstacle, "collision detection"}} -Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, it must be collected. When touching lava, the game should be lost. +Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, that coin must be collected. When touching lava, the game should be lost. Solving this for the general case is a big task. You can find libraries, usually called _((physics engine))s_, that simulate interaction between physical objects in two or three ((dimensions)). We'll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way. @@ -755,7 +741,7 @@ Vertical motion works in a similar way but has to simulate ((jumping)) and ((gra We check for walls again. If we don't hit any, the new position is used. If there _is_ a wall, there are two possible outcomes. When the up arrow is pressed _and_ we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero. -The gravity strength, ((jumping)) speed, and pretty much all other ((constant))s in this game have been set purely by ((trial and error)). I tested values until I found a combination I liked. +The gravity strength, ((jumping)) speed, and other ((constant))s in the game were determined by simply trying out some numbers and seeing which ones felt right. You can try experimenting with them. ## Tracking keys @@ -803,7 +789,7 @@ The `requestAnimationFrame` function, which we saw in [Chapter ?](dom#animationF {{index "runAnimation function", "callback function", [function, "as value"], [function, "higher-order"], [animation, "platform game"]}} -Let's define a helper function that wraps those boring parts in a convenient interface and allows us to simply call `runAnimation`, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value `false`, the animation stops. +Let's define a helper function that wraps all that in a convenient interface and allows us to simply call `runAnimation`, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value `false`, the animation stops. ```{includeCode: true} function runAnimation(frameFunc) { @@ -936,7 +922,7 @@ Make it possible to pause (suspend) and unpause the game by pressing the Esc key {{index "runLevel function", "event handling"}} -This can be done by changing the `runLevel` function to use another keyboard event handler and interrupting or resuming the animation whenever the Esc key is hit. +This can be done by changing the `runLevel` function to set up a keyboard event handler that interrupts or resumes the animation whenever the Esc key is hit. {{index "runAnimation function"}} From 7a59f406d2af2851e099155d92187ad7d8ef0569 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 23 Feb 2024 13:25:42 +0100 Subject: [PATCH 269/392] Integrate copyediting for chapter 3 --- 03_functions.md | 113 +++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/03_functions.md b/03_functions.md index 85affb583..a11bdd49c 100644 --- a/03_functions.md +++ b/03_functions.md @@ -14,7 +14,7 @@ quote}} Functions are one of the most central tools in JavaScript programming. The concept of wrapping a piece of program in a value has many uses. It gives us a way to structure larger programs, to reduce repetition, to associate names with subprograms, and to isolate these subprograms from each other. -The most obvious application of functions is defining new ((vocabulary)). Creating new words in prose is usually bad style. But in programming, it is indispensable. +The most obvious application of functions is defining new ((vocabulary)). Creating new words in prose is usually bad style, but in programming, it is indispensable. {{index abstraction, vocabulary}} @@ -38,7 +38,7 @@ console.log(square(12)); {{indexsee "curly braces", braces}} {{index [braces, "function body"], block, [syntax, function], "function keyword", [function, body], [function, "as value"], [parentheses, arguments]}} -A function is created with an expression that starts with the keyword `function`. Functions have a set of _((parameter))s_ (in this case, only `x`) and a _body_, which contains the statements that are to be executed when the function is called. The function body of a function created this way must always be wrapped in braces, even when it consists of only a single ((statement)). +A function is created with an expression that starts with the keyword `function`. Functions have a set of _((parameter))s_ (in this case, only `x`) and a _body_, which contains the statements that are to be executed when the function is called. The body of a function created this way must always be wrapped in braces, even when it consists of only a single ((statement)). {{index "roundTo example"}} @@ -63,7 +63,7 @@ console.log(roundTo(23, 10)); {{index "return value", "return keyword", undefined}} -Some functions produce a value, such as `roundTo` and `square`, and some don't, such as `makeNoise`, whose only result is a ((side effect)). A `return` statement determines the value the function returns. When control comes across such a statement, it immediately jumps out of the current function and gives the returned value to the code that called the function. A `return` keyword without an expression after it will cause the function to return `undefined`. Functions that don't have a `return` statement at all, such as `makeNoise`, similarly return `undefined`. +Some functions, such as `roundTo` and `square`, produce a value, and some don't, such as `makeNoise`, whose only result is a ((side effect)). A `return` statement determines the value the function returns. When control comes across such a statement, it immediately jumps out of the current function and gives the returned value to the code that called the function. A `return` keyword without an expression after it will cause the function to return `undefined`. Functions that don't have a `return` statement at all, such as `makeNoise`, similarly return `undefined`. {{index parameter, [function, application], [binding, "from parameter"]}} @@ -78,23 +78,18 @@ Each binding has a _((scope))_, which is the part of the program in which the bi {{index "local scope", [binding, local]}} -But bindings created for function ((parameter))s or declared inside a function can be referenced only in that function, so they are known as _local_ bindings. Every time the function is called, new instances of these bindings are created. This provides some isolation between functions—each function call acts in its own little world (its local environment) and can often be understood without knowing a lot about what's going on in the global environment. +Bindings created for function ((parameter))s or declared inside a function can be referenced only in that function, so they are known as _local_ bindings. Every time the function is called, new instances of these bindings are created. This provides some isolation between functions—each function call acts in its own little world (its local environment) and can often be understood without knowing a lot about what's going on in the global environment. {{index "let keyword", "const keyword", "var keyword"}} -Bindings declared with `let` and `const` are in fact local to the _((block))_ that they are declared in, so if you create one of those inside of a loop, the code before and after the loop cannot "see" it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the `var` keyword, are visible throughout the whole function that they appear in—or throughout the global scope, if they are not in a function. +Bindings declared with `let` and `const` are in fact local to the _((block))_ in which they are declared, so if you create one of those inside of a loop, the code before and after the loop cannot "see" it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the `var` keyword, are visible throughout the whole function in which they appear—or throughout the global scope, if they are not in a function. ``` -let x = 10; +let x = 10; // global if (true) { - let y = 20; - var z = 30; - console.log(x + y + z); - // → 60 + let y = 20; // local to block + var z = 30; // also global } -// y is not visible here -console.log(x + z); -// → 40 ``` {{index [binding, visibility]}} @@ -115,11 +110,11 @@ console.log(n); {{id scoping}} -### Nested scope +## Nested scope {{index [nesting, "of functions"], [nesting, "of scope"], scope, "inner function", "lexical scoping"}} -JavaScript distinguishes not just _global_ and _local_ bindings. Blocks and functions can be created inside other blocks and functions, producing multiple degrees of locality. +JavaScript distinguishes not just global and local bindings. Blocks and functions can be created inside other blocks and functions, producing multiple degrees of locality. {{index "landscape example"}} @@ -145,7 +140,7 @@ const hummus = function(factor) { {{index [function, scope], scope}} -The code inside the `ingredient` function can see the `factor` binding from the outer function. But its local bindings, such as `unit` or `ingredientAmount`, are not visible in the outer function. +The code inside the `ingredient` function can see the `factor` binding from the outer function, but its local bindings, such as `unit` or `ingredientAmount`, are not visible in the outer function. The set of bindings visible inside a block is determined by the place of that block in the program text. Each local scope can also see all the local scopes that contain it, and all scopes can see the global scope. This approach to binding visibility is called _((lexical scoping))_. @@ -170,13 +165,13 @@ if (safeMode) { {{index [function, "higher-order"]}} -In [Chapter ?](higher_order), we will discuss the interesting things that can be done by passing around function values to other functions. +In [Chapter ?](higher_order), we'll discuss the interesting things that we can do by passing around function values to other functions. ## Declaration notation {{index [syntax, function], "function keyword", "square example", [function, definition], [function, declaration]}} -There is a slightly shorter way to create a function binding. When the `function` keyword is used at the start of a statement, it works differently. +There is a slightly shorter way to create a function binding. When the `function` keyword is used at the start of a statement, it works differently: ```{test: wrap} function square(x) { @@ -204,7 +199,7 @@ The preceding code works, even though the function is defined _below_ the code t {{index function, "arrow function"}} -There's a third notation for functions, which looks very different from the others. Instead of the `function` keyword, it uses an arrow (`=>`) made up of an equal sign and a greater-than character (not to be confused with the greater-than-or-equal operator, which is written `>=`). +There's a third notation for functions, which looks very different from the others. Instead of the `function` keyword, it uses an arrow (`=>`) made up of an equal sign and a greater-than character (not to be confused with the greater-than-or-equal operator, which is written `>=`): ```{test: wrap} const roundTo = (n, step) => { @@ -238,7 +233,7 @@ const horn = () => { {{index verbosity}} -There's no deep reason to have both arrow functions and `function` expressions in the language. Apart from a minor detail, which we'll discuss in [Chapter ?](object), they do the same thing. Arrow functions were added in 2015, mostly to make it possible to write small function expressions in a less verbose way. We'll be using them a lot in [Chapter ?](higher_order). +There's no deep reason to have both arrow functions and `function` expressions in the language. Apart from a minor detail, which we'll discuss in [Chapter ?](object), they do the same thing. Arrow functions were added in 2015, mostly to make it possible to write small function expressions in a less verbose way. We'll use them often in [Chapter ?](higher_order). {{id stack}} @@ -259,7 +254,7 @@ console.log("Bye"); {{index ["control flow", functions], "execution order", "console.log"}} -A run through this program goes roughly like this: the call to `greet` causes control to jump to the start of that function (line 2). The function calls `console.log`, which takes control, does its job, and then returns control to line 2. There it reaches the end of the `greet` function, so it returns to the place that called it, which is line 4. The line after that calls `console.log` again. After that returns, the program reaches its end. +A run through this program goes roughly like this: the call to `greet` causes control to jump to the start of that function (line 2). The function calls `console.log`, which takes control, does its job, and then returns control to line 2. There, it reaches the end of the `greet` function, so it returns to the place that called it—line 4. The line after that calls `console.log` again. After that returns, the program reaches its end. We could show the flow of control schematically like this: @@ -281,7 +276,7 @@ The place where the computer stores this context is the _((call stack))_. Every {{index "infinite loop", "stack overflow", recursion}} -Storing this stack requires space in the computer's memory. When the stack grows too big, the computer will fail with a message like "out of stack space" or "too much recursion". The following code illustrates this by asking the computer a really hard question that causes an infinite back-and-forth between two functions. Rather, it _would_ be infinite, if the computer had an infinite stack. As it is, we will run out of space, or "blow the stack". +Storing this stack requires space in the computer's memory. When the stack grows too big, the computer will fail with a message like "out of stack space" or "too much recursion". The following code illustrates this by asking the computer a really hard question that causes an infinite back-and-forth between two functions. Or rather, it _would_ be infinite, if the computer had an infinite stack. As it is, we will run out of space, or "blow the stack". ```{test: no} function chicken() { @@ -310,11 +305,9 @@ We defined `square` with only one ((parameter)). Yet when we call it with three, {{index undefined}} -JavaScript is extremely broad-minded about the number of arguments you pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value `undefined`. +JavaScript is extremely broad-minded about the number of arguments you can pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters are assigned the value `undefined`. -The downside of this is that it is possible—likely, even—that you'll accidentally pass the wrong number of arguments to functions. And no one will tell you about it. - -The upside is that this behavior can be used to allow a function to be called with different numbers of arguments. For example, this `minus` function tries to imitate the `-` operator by acting on either one or two arguments: +The downside of this is that it is possible—likely, even—that you'll accidentally pass the wrong number of arguments to functions. And no one will tell you about it. The upside is that you can use this behavior to allow a function to be called with different numbers of arguments. For example, this `minus` function tries to imitate the `-` operator by acting on either one or two arguments: ``` function minus(a, b) { @@ -329,13 +322,9 @@ console.log(minus(10, 5)); ``` {{id roundTo}} -{{index "optional argument", "default value", parameter, ["= operator", "for default value"]}} - -If you write an `=` operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given. +{{index "optional argument", "default value", parameter, ["= operator", "for default value"] "roundTo example"}} -{{index "roundTo example"}} - -For example, this version of `roundTo` makes its second argument optional. If you don't provide it or pass the value `undefined`, it will default to one. +If you write an `=` operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given. For example, this version of `roundTo` makes its second argument optional. If you don't provide it or pass the value `undefined`, it will default to one: ```{test: wrap} function roundTo(n, step = 1) { @@ -351,7 +340,7 @@ console.log(roundTo(4.5, 2)); {{index "console.log"}} -In the [next chapter](data#rest_parameters), we will see a way in which a function body can get at the whole list of arguments it was passed. This is helpful because it makes it possible for a function to accept any number of arguments. For example, `console.log` does this—it outputs all of the values it is given. +The [next chapter](data#rest_parameters) will introduce a way in which a function body can get at the whole list of arguments it was passed. This is helpful because it allows a function to accept any number of arguments. For example, `console.log` does this, outputting all the values it is given: ``` console.log("C", "O", 2); @@ -362,9 +351,9 @@ console.log("C", "O", 2); {{index "call stack", "local binding", [function, "as value"], scope}} -The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question. What happens to local bindings when the function call that created them is no longer active? +The ability to treat functions as values, combined with the fact that local bindings are recreated every time a function is called, brings up an interesting question: what happens to local bindings when the function call that created them is no longer active? -The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding. +The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding: ``` function wrapValue(n) { @@ -382,11 +371,11 @@ console.log(wrap2()); This is allowed and works as you'd hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls don't affect each other's local bindings. -This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about lifetimes of bindings but also makes it possible to use function values in some creative ways. +This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about the lifetimes of bindings but also makes it possible to use function values in some creative ways. {{index "multiplier function"}} -With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount. +With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount: ``` function multiplier(factor) { @@ -400,13 +389,13 @@ console.log(twice(5)); {{index [binding, "from parameter"]}} -The explicit `local` binding from the `wrapValue` example isn't really needed since a parameter is itself a local binding. +The explicit `local` binding from the `wrapValue` example isn't really needed, since a parameter is itself a local binding. {{index [function, "model of"]}} Thinking about programs like this takes some practice. A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called. -In the example, `multiplier` is called and creates an environment in which its `factor` parameter is bound to 2. The function value it returns, which is stored in `twice`, remembers this environment. So when that is called, it multiplies its argument by 2. +In the previous example, `multiplier` is called and creates an environment in which its `factor` parameter is bound to 2. The function value it returns, which is stored in `twice`, remembers this environment so that when that is called, it multiplies its argument by 2. ## Recursion @@ -433,11 +422,11 @@ This is rather close to the way mathematicians define exponentiation and arguabl {{index [function, application], efficiency}} -But this implementation has one problem: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. +This implementation has one problem, however: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. {{index optimization}} -The dilemma of speed versus ((elegance)) is an interesting one. You can see it as a kind of continuum between human-friendliness and machine-friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer has to decide on an appropriate balance. +The dilemma of speed versus ((elegance)) is an interesting one. You can see it as a kind of continuum between human-friendliness and machine-friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer has to find an appropriate balance. In the case of the `power` function, an inelegant (looping) version is still fairly simple and easy to read. It doesn't make much sense to replace it with a recursive function. Often, though, a program deals with such complex concepts that giving up some efficiency in order to make the program more straightforward is helpful. @@ -447,7 +436,7 @@ Worrying about efficiency can be a distraction. It's yet another factor that com {{index "premature optimization"}} -Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. +Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't, since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. {{index "branching recursion"}} @@ -456,9 +445,7 @@ Recursion is not always just an inefficient alternative to looping. Some problem {{id recursive_puzzle}} {{index recursion, "number puzzle example"}} -Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produces that number? - -For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all. +Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produces that number? For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all. Here is a recursive solution: @@ -483,9 +470,9 @@ console.log(findSolution(24)); Note that this program doesn't necessarily find the _shortest_ sequence of operations. It is satisfied when it finds any sequence at all. -It is okay if you don't see how it works right away. Let's work through it, since it makes for a great exercise in recursive thinking. +It's okay if you don't see how this code works right away. Let's work through it, since it makes for a great exercise in recursive thinking. -The inner function `find` does the actual recursing. It takes two ((argument))s: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If no solution can be found starting from this number, it returns `null`. +The inner function `find` does the actual recursing. It takes two ((argument))s: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If it can find no solution starting from this number, it returns `null`. {{index null, "?? operator", "short-circuit evaluation"}} @@ -493,7 +480,7 @@ To do this, the function performs one of three actions. If the current number is {{index "call stack"}} -To better understand how this function produces the effect we're looking for, let's look at all the calls to `find` that are made when searching for a solution for the number 13. +To better understand how this function produces the effect we're looking for, let's look at all the calls to `find` that are made when searching for a solution for the number 13: ```{lang: null} find(1, "1") @@ -511,7 +498,7 @@ find(1, "1") found! ``` -The indentation indicates the depth of the call stack. The first time `find` is called, it starts by calling itself to explore the solution that starts with `(1 + 5)`. That call will further recurse to explore _every_ continued solution that yields a number less than or equal to the target number. Since it doesn't find one that hits the target, it returns `null` back to the first call. There the `??` operator causes the call that explores `(1 * 3)` to happen. This search has more luck—its first recursive call, through yet _another_ recursive call, hits upon the target number. That innermost call returns a string, and each of the `??` operators in the intermediate calls passes that string along, ultimately returning the solution. +The indentation indicates the depth of the call stack. The first time `find` is called, the function starts by calling itself to explore the solution that starts with `(1 + 5)`. That call will further recurse to explore _every_ continued solution that yields a number less than or equal to the target number. Since it doesn't find one that hits the target, it returns `null` back to the first call. There the `??` operator causes the call that explores `(1 * 3)` to happen. This search has more luck—its first recursive call, through yet _another_ recursive call, hits upon the target number. That innermost call returns a string, and each of the `??` operators in the intermediate calls passes that string along, ultimately returning the solution. ## Growing functions @@ -521,9 +508,9 @@ There are two more or less natural ways for functions to be introduced into prog {{index repetition}} -The first is that you find yourself writing similar code multiple times. You'd prefer not to do that. Having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function. +The first occurs when you find yourself writing similar code multiple times. You'd prefer not to do that, as having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function. -The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You'll start by naming the function, and then you'll write its body. You might even start writing code that uses the function before you actually define the function itself. +The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You start by naming the function, then write its body. You might even start writing code that uses the function before you actually define the function itself. {{index [function, naming], [binding, naming]}} @@ -531,7 +518,7 @@ How difficult it is to find a good name for a function is a good indication of h {{index "farm example"}} -We want to write a program that prints two numbers: the numbers of cows and chickens on a farm, with the words `Cows` and `Chickens` after them and zeros padded before both numbers so that they are always three digits long. +We want to write a program that prints two numbers: the numbers of cows and chickens on a farm, with the words `Cows` and `Chickens` after them and zeros padded before both numbers so that they are always three digits long: ```{lang: null} 007 Cows @@ -590,7 +577,7 @@ It works! But that name, `printZeroPaddedWithLabel`, is a little awkward. It con {{index "zeroPad function"}} -Instead of lifting out the repeated part of our program wholesale, let's try to pick out a single _concept_. +Instead of lifting out the repeated part of our program wholesale, let's try to pick out a single _concept_: ``` function zeroPad(number, width) { @@ -612,20 +599,20 @@ printFarmInventory(7, 16, 3); {{index readability, "pure function"}} -A function with a nice, obvious name like `zeroPad` makes it easier for someone who reads the code to figure out what it does. And such a function is useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers. +A function with a nice, obvious name like `zeroPad` makes it easier for someone who reads the code to figure out what it does. Such a function is also useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers. {{index [interface, design]}} How smart and versatile _should_ our function be? We could write anything, from a terribly simple function that can only pad a number to be three characters wide to a complicated generalized number-formatting system that handles fractional numbers, negative numbers, alignment of decimal dots, padding with different characters, and so on. -A useful principle is to not add cleverness unless you are absolutely sure you're going to need it. It can be tempting to write general "((framework))s" for every bit of functionality you come across. Resist that urge. You won't get any real work done—you'll just be writing code that you never use. +A useful principle is to refrain from adding cleverness unless you are absolutely sure you're going to need it. It can be tempting to write general "((framework))s" for every bit of functionality you come across. Resist that urge. You won't get any real work done—you'll be too busy writing code that you never use. {{id pure}} ## Functions and side effects {{index "side effect", "pure function", [function, purity]}} -Functions can be roughly divided into those that are called for their side effects and those that are called for their return value. (Though it is definitely also possible to both have side effects and return a value.) +Functions can be roughly divided into those that are called for their side effects and those that are called for their return value (though it is also possible to both have side effects and return a value). {{index reuse}} @@ -637,7 +624,7 @@ A _pure_ function is a specific kind of value-producing function that not only h {{index optimization, "console.log"}} -Still, there's no need to feel bad when writing functions that are not pure. Side effects are often useful. There'd be no way to write a pure version of `console.log`, for example, and `console.log` is good to have. Some operations are also easier to express in an efficient way when we use side effects. +Still, there's no need to feel bad when writing functions that are not pure. Side effects are often useful. There's no way to write a pure version of `console.log`, for example, and `console.log` is good to have. Some operations are also easier to express in an efficient way when we use side effects. ## Summary @@ -658,7 +645,7 @@ function g(a, b) { let h = a => a % 3; ``` -A key aspect in understanding functions is understanding scopes. Each block creates a new scope. Parameters and bindings declared in a given scope are local and not visible from the outside. Bindings declared with `var` behave differently—they end up in the nearest function scope or the global scope. +A key part of understanding functions is understanding scopes. Each block creates a new scope. Parameters and bindings declared in a given scope are local and not visible from the outside. Bindings declared with `var` behave differently—they end up in the nearest function scope or the global scope. Separating the tasks your program performs into different functions is helpful. You won't have to repeat yourself as much, and functions can help organize a program by grouping code into pieces that do specific things. @@ -668,7 +655,7 @@ Separating the tasks your program performs into different functions is helpful. {{index "Math object", "minimum (exercise)", "Math.min function", minimum}} -The [previous chapter](program_structure#return_values) introduced the standard function `Math.min` that returns its smallest argument. We can build something like that now. Write a function `min` that takes two arguments and returns their minimum. +The [previous chapter](program_structure#return_values) introduced the standard function `Math.min` that returns its smallest argument. We can write a function like that ourselves now. Define the function `min` that takes two arguments and returns their minimum. {{if interactive @@ -698,7 +685,7 @@ hint}} {{index recursion, "isEven (exercise)", "even number"}} -We've seen that `%` (the remainder operator) can be used to test whether a number is even or odd by using `% 2` to see whether it's divisible by two. Here's another way to define whether a positive whole number is even or odd: +We've seen that we can use `%` (the remainder operator) to test whether a number is even or odd by using `% 2` to see whether it's divisible by two. Here's another way to define whether a positive whole number is even or odd: - Zero is even. @@ -743,11 +730,11 @@ hint}} {{index "bean counting (exercise)", [string, indexing], "zero-based counting", ["length property", "for string"]}} -You can get the Nth character, or letter, from a string by writing `[N]` after the string (for example `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. +You can get the Nth character, or letter, from a string by writing `[N]` after the string (for example, `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. -Write a function `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase "B" characters there are in the string. +Write a function `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase B characters there are in the string. -Next, write a function called `countChar` that behaves like `countBs`, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase "B" characters). Rewrite `countBs` to make use of this new function. +Next, write a function called `countChar` that behaves like `countBs`, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase B characters). Rewrite `countBs` to make use of this new function. {{if interactive From ea1f414f3e7739c89f5583b10228192735d83e36 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 23 Feb 2024 13:29:35 +0100 Subject: [PATCH 270/392] Italicize a variable name in chapter 3 --- 03_functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03_functions.md b/03_functions.md index a11bdd49c..ab8622ae5 100644 --- a/03_functions.md +++ b/03_functions.md @@ -730,7 +730,7 @@ hint}} {{index "bean counting (exercise)", [string, indexing], "zero-based counting", ["length property", "for string"]}} -You can get the Nth character, or letter, from a string by writing `[N]` after the string (for example, `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. +You can get the *N*th character, or letter, from a string by writing `[N]` after the string (for example, `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. Write a function `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase B characters there are in the string. From 182f51e12b158cce1699e9370a3b09651681d09e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 26 Feb 2024 10:07:54 +0100 Subject: [PATCH 271/392] Prepare chapter 17 for editing --- 17_canvas.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 643df27b6..4b5d64b57 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -72,7 +72,7 @@ A new canvas is empty, meaning it is entirely ((transparent)) and thus shows up {{index "2d (canvas context)", "webgl (canvas context)", OpenGL, [canvas, context], dimensions, [interface, canvas]}} -The `` tag is intended to allow different styles of ((drawing)). To get access to an actual drawing interface, we first need to create a _((context))_, an object whose methods provide the drawing interface. There are currently three widely supported drawing styles: `"2d"` for two-dimensional graphics and `"webgl"` for three-dimensional graphics through the OpenGL interface, and `"webgpu"`, which fills much the same role as WebGL, but provides a more modern interface. +The `` tag is intended to allow different styles of ((drawing)). To get access to an actual drawing interface, we first need to create a _((context))_, an object whose methods provide the drawing interface. There are currently three widely supported drawing styles: `"2d"` for two-dimensional graphics, `"webgl"` for three-dimensional graphics through the OpenGL interface, and `"webgpu"`, a more modern and flexible alternative to WebGL. {{index rendering, graphics, efficiency}} @@ -116,11 +116,11 @@ In the ((canvas)) interface, a shape can be _filled_, meaning its area is given {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method, `strokeRect`, draws the ((outline)) of a rectangle. +The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. {{index [state, "of canvas"]}} -Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method (as you might reasonably expect) but rather by properties of the context object. +Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method, as you might reasonably expect, but rather by properties of the context object. {{index filling, "fillStyle property"}} @@ -848,7 +848,7 @@ if}} ## Choosing a graphics interface -So when you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses. +Thus, when you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses. {{index "text wrapping"}} @@ -868,7 +868,7 @@ But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a {{index "ray tracer"}} -There are also effects, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a ((pixel))-based approach. +There are also effects, such as rendering a scene one ((pixel)) at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that are only practical with a canvas element. In some cases, you may want to combine several of these techniques. For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but show ((text))ual information by positioning an HTML element on top of the picture. @@ -967,7 +967,7 @@ hint}} {{index label, text, "pie chart example"}} -[Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave ample room for their labels. +[Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave enough room for their labels. You might need `Math.sin` and `Math.cos` again, which are described in [Chapter ?](dom#sin_cos). From f09049800c48602c8519b2ccdcbba9b7d347ea8e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 26 Feb 2024 11:29:47 +0100 Subject: [PATCH 272/392] Prepare chapter 18 for editing --- 18_http.md | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/18_http.md b/18_http.md index 30a1787ef..3903b960b 100644 --- a/18_http.md +++ b/18_http.md @@ -2,9 +2,9 @@ # HTTP and Forms -{{quote {author: "Roy Fielding", title: "Architectural Styles and the Design of Network-based Software Architectures", chapter: true} +{{quote {author: "Tim Berners-Lee", chapter: true} -Communication must be stateless in nature [...] such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. +What was often difficult for people to understand about the design was that there was nothing else beyond URLs, HTTP and HTML. There was no central computer "controlling" the Web, no single network on which these protocols worked, not even an organisation anywhere that "ran" the Web. The Web was not a physical "thing" that existed in a certain "place". It was a "space" in which information could exist. quote}} @@ -60,7 +60,7 @@ The part after the method name is the path of the _((resource))_ the request app After the resource path, the first line of the request mentions `HTTP/1.1` to indicate the ((version)) of the ((HTTP)) ((protocol)) it is using. -In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we'll focus on that. +In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we'll use that to illustrate the protocol. {{index "status code"}} @@ -100,7 +100,7 @@ After the headers, both requests and responses may include a blank line followed {{index HTTP, [file, resource]}} -As we saw in the example, a ((browser)) will make a request when we enter a ((URL)) in its ((address bar)). When the resulting HTML page references other files, such as ((image))s and JavaScript files, those are also retrieved. +As we saw, a ((browser)) will make a request when we enter a ((URL)) in its ((address bar)). When the resulting HTML page references other files, such as ((image))s and JavaScript files, it will retrieve those as well. {{index parallelism, "GET method"}} @@ -163,7 +163,7 @@ We'll come back to forms and how to interact with them from JavaScript [later in {{index "fetch function", "Promise class", [interface, module]}} -The interface through which browser JavaScript can make HTTP requests is called `fetch`. Since it is relatively new, it conveniently uses promises. +The interface through which browser JavaScript can make HTTP requests is called `fetch`. ```{test: no} fetch("example/data.txt").then(response => { @@ -178,7 +178,7 @@ fetch("example/data.txt").then(response => { Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. -Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It _can_ also be rejected if there is a network error or if the ((server)) that the request is addressed to can't be found. +Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It can also be rejected if there is a network error or if the ((server)) that the request is addressed to can't be found. {{index [path, URL], "relative URL"}} @@ -212,7 +212,7 @@ fetch("example/data.txt", {method: "DELETE"}).then(resp => { {{index "405 (HTTP status code)"}} -The 405 status code means "method not allowed", an HTTP server's way of saying "I can't do that". +The 405 status code means "method not allowed", an HTTP server's way of saying "I'm afraid I can't do that". {{index "Range header", "body property", "headers property"}} @@ -261,7 +261,7 @@ When thinking in terms of remote procedure calls, HTTP is just a vehicle for com Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/user/larry`), which again returns the document representing the resource. -This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around. +This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy of a resource on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around. ## Security and HTTPS @@ -379,7 +379,7 @@ Whenever the value of a form field changes, it will fire a `"change"` event. {{indexsee "keyboard focus", focus}} -Unlike most elements in HTML documents, form fields can get _keyboard ((focus))_. When clicked, move to with the [tab]{keyname} key, or activated in some other way, they become the currently active element and the recipient of keyboard ((input)). +Unlike most elements in HTML documents, form fields can get _keyboard ((focus))_. When clicked, moved to with the [tab]{keyname} key, or activated in some other way, they become the currently active element and the recipient of keyboard ((input)). {{index "option (HTML tag)", "select (HTML tag)"}} @@ -407,7 +407,7 @@ For some pages, the user is expected to want to interact with a form field immed {{index "tab key", keyboard, "tabindex attribute", "a (HTML tag)"}} -Browsers also allow the user to move the focus through the document by pressing the [tab]{keyname} key to move to the next focusable element, and [shift-tab]{keyname} to move back to the previous element. By default, elements are visited in the order they appear in the document. It is possible to use the `tabindex` attribute to change this order. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first: +Browsers allow the user to move the focus through the document by pressing the [tab]{keyname} key to move to the next focusable element, and [shift-tab]{keyname} to move back to the previous element. By default, elements are visited in the order they appear in the document. It is possible to use the `tabindex` attribute to change this order. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first: ```{lang: html, focus: true} (help) @@ -477,7 +477,7 @@ A button with a `type` attribute of `submit` will, when pressed, cause the form Submitting a ((form)) normally means that the ((browser)) navigates to the page indicated by the form's `action` attribute, using either a `GET` or a `POST` ((request)). But before that happens, a `"submit"` event is fired. You can handle this event with JavaScript and prevent this default behavior by calling `preventDefault` on the event object. ```{lang: html} - + Value: @@ -513,8 +513,7 @@ Imagine you are writing an article about Khasekhemwy but have some trouble spell ``` -{{index "getItem method", JSON, "|| operator", "default value"}} +{{index "getItem method", JSON, "?? operator", "default value"}} -The script gets its starting state from the `"Notes"` value stored in `localStorage` or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from `localStorage` will yield `null`. Passing `null` to `JSON.parse` will make it parse the string `"null"` and return `null`. Thus, the `||` operator can be used to provide a default value in a situation like this. +The script gets its starting state from the `"Notes"` value stored in `localStorage` or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from `localStorage` will yield `null`. Passing `null` to `JSON.parse` will make it parse the string `"null"` and return `null`. Thus, the `??` operator can be used to provide a default value in a situation like this. The `setState` method makes sure the DOM is showing a given state and stores the new state to `localStorage`. Event handlers call this function to move to a new state. From 451990628aa850dd1675e16af7e1b222f91d928e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 26 Feb 2024 12:08:00 +0100 Subject: [PATCH 273/392] Prepare chapter 19 for editing --- 19_paint.md | 14 +++++++------- code/solutions/19_3_circles.html | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/19_paint.md b/19_paint.md index 4570f74a8..b3fe33cb2 100644 --- a/19_paint.md +++ b/19_paint.md @@ -56,7 +56,7 @@ Updates to the state are represented as objects, which we'll call _((action))s_. {{index [DOM, components]}} -We're taking the messy task of running a ((user interface)) and applying some ((structure)) to it. Though the DOM-related pieces are still full of ((side effect))s, they are held up by a conceptually simple backbone: the state update cycle. The state determines what the DOM looks like, and the only way DOM events can change the state is by dispatching actions to the state. +We're taking the messy task of running a ((user interface)) and applying ((structure)) to it. Though the DOM-related pieces are still full of ((side effect))s, they are held up by a conceptually simple backbone: the state update cycle. The state determines what the DOM looks like, and the only way DOM events can change the state is by dispatching actions to the state. {{index "data flow"}} @@ -440,14 +440,16 @@ const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, function fill({x, y}, state, dispatch) { let targetColor = state.picture.pixel(x, y); let drawn = [{x, y, color: state.color}]; + let visited = new Set(); for (let done = 0; done < drawn.length; done++) { for (let {dx, dy} of around) { let x = drawn[done].x + dx, y = drawn[done].y + dy; if (x >= 0 && x < state.picture.width && y >= 0 && y < state.picture.height && - state.picture.pixel(x, y) == targetColor && - !drawn.some(p => p.x == x && p.y == y)) { + !visited.has(x + "," + y) && + state.picture.pixel(x, y) == targetColor) { drawn.push({x, y, color: state.color}); + visited.add(x + "," + y); } } } @@ -532,9 +534,7 @@ The `toDataURL` method on a canvas element creates a URL that starts with `data: {{index "a (HTML tag)", "download attribute"}} -To actually get the browser to download the picture, we then create a ((link)) element that points at this URL and has a `download` attribute. Such links, when clicked, make the browser show a file save dialog. We add that link to the document, simulate a click on it, and remove it again. - -You can do a lot with ((browser)) technology, but sometimes the way to do it is rather odd. +To actually get the browser to download the picture, we then create a ((link)) element that points at this URL and has a `download` attribute. Such links, when clicked, make the browser show a file save dialog. We add that link to the document, simulate a click on it, and remove it again. You can do a lot with ((browser)) technology, but sometimes the way to do it is rather odd. {{index "LoadButton class", control, [file, image]}} @@ -927,7 +927,7 @@ if}} You can take some inspiration from the `rectangle` tool. Like that tool, you'll want to keep drawing on the _starting_ picture, rather than the current picture, when the pointer moves. -To figure out which pixels to color, you can use the ((Pythagorean theorem)). First figure out the distance between the current pointer position and the start position by taking the square root (`Math.sqrt`) of the sum of the square (`Math.pow(x, 2)`) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the ((radius)), and color those that are within the circle's radius, again using the Pythagorean formula to figure out their ((distance)) from the center. +To figure out which pixels to color, you can use the ((Pythagorean theorem)). First figure out the distance between the current pointer position and the start position by taking the square root (`Math.sqrt`) of the sum of the square (`x ** 2`) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the ((radius)), and color those that are within the circle's radius, again using the Pythagorean formula to figure out their ((distance)) from the center. Make sure you don't try to color pixels that are outside of the picture's boundaries. diff --git a/code/solutions/19_3_circles.html b/code/solutions/19_3_circles.html index 04d96bb19..4fcab5bf4 100644 --- a/code/solutions/19_3_circles.html +++ b/code/solutions/19_3_circles.html @@ -7,13 +7,13 @@ \n", + start_code: "\n\n\n", include: ["code/draw_layout.js", "code/chapter/22_fast.js"], exercises: [ - {name: "Pathfinding", - file: "code/solutions/22_1_pathfinding.js", + {name: "Prime numbers", + file: "code/solutions/22_1_prime_numbers.js", number: 1, type: "js", + code: "function* primes() {\n for (let n = 2;; n++) {\n // ...\n }\n}\n\nfunction measurePrimes() {\n // ...\n}\n\nmeasurePrimes();\n", + solution: fs.readFileSync("code/solutions/22_1_prime_numbers.js", "utf8") + }, + {name: "Faster prime numbers", + file: "code/solutions/22_2_faster_prime_numbers.js", + number: 2, + type: "js", + code: "function* primes() {\n for (let n = 2;; n++) {\n // ...\n }\n}\n\nfunction measurePrimes() {\n // ...\n}\n\nmeasurePrimes();\n", + solution: fs.readFileSync("code/solutions/22_2_faster_prime_numbers.js", "utf8"), + }, + {name: "Pathfinding [3rd ed]", + file: "code/solutions/22_1_pathfinding.js", + number: "1[3]", + type: "js", code: "function findPath(a, b) {\n // Your code here...\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n", - solution: fs.readFileSync("code/solutions/22_1_pathfinding.js", "utf8") + solution: fs.readFileSync("code/solutions/22_1_pathfinding.js", "utf8"), + goto: "https://eloquentjavascript.net/3rd_edition/code/#22.1" }, - {name: "Timing", + {name: "Timing [3rd ed]", file: "code/solutions/22_2_timing.js", - number: 2, + number: "2[3]", type: "js", code: "", - solution: fs.readFileSync("code/solutions/22_2_timing.js", "utf8") + solution: fs.readFileSync("code/solutions/22_2_timing.js", "utf8"), + goto: "https://eloquentjavascript.net/3rd_edition/code/#22.2" }, - {name: "Optimizing", + {name: "Optimizing [3rd ed]", file: "code/solutions/22_3_optimizing.js", - number: 3, + number: "3[3]", type: "js", code: "", - solution: fs.readFileSync("code/solutions/22_3_optimizing.js", "utf8") + solution: fs.readFileSync("code/solutions/22_3_optimizing.js", "utf8"), + goto: "https://eloquentjavascript.net/3rd_edition/code/#22.3" } ] }); diff --git a/src/client/code.mjs b/src/client/code.mjs index c4e1d378c..32fadfc93 100644 --- a/src/client/code.mjs +++ b/src/client/code.mjs @@ -100,10 +100,12 @@ class CodeSandbox { if (chapter.start_code) code += "\n\n" + chapter.start_code this.setEditorState(code, {include: chapter.include}) visible = "box" - } else if (value == "11.1[3]") { - document.location = "https://eloquentjavascript.net/3rd_edition/code/#11.1" } else { let exercise = findExercise(value, chapter) + if (exercise.goto) { + document.location = exercise.goto + return + } this.setEditorState(exercise.code, { include: chapter.include, solution: exercise.solution, From 08cfb2a28d4d6576858e4432eb709a72e34f1c50 Mon Sep 17 00:00:00 2001 From: John Mckay Date: Thu, 7 Mar 2024 16:15:14 +0000 Subject: [PATCH 294/392] Fixed typo in chapter 11 async --- 11_async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11_async.md b/11_async.md index 3e7e472a7..4a3916b6c 100644 --- a/11_async.md +++ b/11_async.md @@ -159,7 +159,7 @@ The function returns the result of this chain of `then` calls. The initial promi In this code, the functions used in the first two `then` calls return a regular value, which will immediately be passed into the promise returned by `then` when the function returns. The last one returns a promise (`textFile(filename)`), making it an actual asynchronous step. -Tt would have also been possible to do all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, for example when you want to return a promise that produces a processed version of some asynchronous result. +It would have also been possible to do all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, for example when you want to return a promise that produces a processed version of some asynchronous result. ``` function jsonFile(filename) { From 7f663a14f18ad685da4f81566c0c0b3a9d1c294c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 7 Mar 2024 21:50:38 +0100 Subject: [PATCH 295/392] Fix unquoted attribute in EPUB output Closes https://github.com/marijnh/Eloquent-JavaScript/issues/571 --- src/render_html.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render_html.mjs b/src/render_html.mjs index 424ee0e34..7183f7e7b 100644 --- a/src/render_html.mjs +++ b/src/render_html.mjs @@ -94,7 +94,7 @@ let renderer = { fence(token) { let config = /\S/.test(token.info) ? PJSON.parse(token.info) : {} if (config.hidden) return ""; - let lang = config.lang || "javascript", tab = lang == "html" || lang == "javascript" ? " tabindex=0" : "" + let lang = config.lang || "javascript", tab = lang == "html" || lang == "javascript" ? " tabindex=\"0\"" : "" return `\n\n${anchor(token)}${highlight(lang, token.content.trimRight())}` }, From 34bbc3f7aec20792f9ca7d33594b2a9ddb5157a0 Mon Sep 17 00:00:00 2001 From: Joris Tirado <1958193+joristirado@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:33:44 -0800 Subject: [PATCH 296/392] Fix missing word --- 06_object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_object.md b/06_object.md index 094745049..99e685813 100644 --- a/06_object.md +++ b/06_object.md @@ -22,7 +22,7 @@ In programming culture, we have a thing called _((object-oriented programming))_ The main idea in object-oriented programming is to use objects, or rather _types_ of objects, as the unit of program organization. Setting up a program as a number of strictly separated object types provides a way to think about its structure and thus to enforce some kind of discipline, preventing everything from becoming entangled. -The way to do this to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). There's people who designed and assembled a mixer, and they have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell, so that the people who only want to mix pancake batter don't have to worry about all that—they only have to understand the few knobs that the mixer can be operated with. +The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). There's people who designed and assembled a mixer, and they have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell, so that the people who only want to mix pancake batter don't have to worry about all that—they only have to understand the few knobs that the mixer can be operated with. {{index "class"}} From 81d1afe933a6d4864bb5669d5426dbf01bb6ac41 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Thu, 7 Mar 2024 20:14:36 -0500 Subject: [PATCH 297/392] fix typos --- 11_async.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/11_async.md b/11_async.md index 4a3916b6c..b4da70d69 100644 --- a/11_async.md +++ b/11_async.md @@ -13,7 +13,7 @@ quote}} {{figure {url: "img/chapter_picture_11.jpg", alt: "Illustration showing two crows on a tree branch", chapter: framed}}} -The central part of a computer, the part that carries out the individual steps that make up our programs, is called the _((processor))_. The programs we have seen so far will keep the processor busy until they have finished their work. The speed at which something like a loop that manipulates numbers can be executed depends pretty much entirely on the speed of the computor's processor and memory. +The central part of a computer, the part that carries out the individual steps that make up our programs, is called the _((processor))_. The programs we have seen so far will keep the processor busy until they have finished their work. The speed at which something like a loop that manipulates numbers can be executed depends pretty much entirely on the speed of the computer's processor and memory. {{index [memory, speed], [network, speed]}} @@ -108,7 +108,7 @@ A slightly different way to build an asynchronous program is to have asynchronou {{index "Promise class", "asynchronous programming", "resolving (a promise)", "then method", "callback function"}} -This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provies a `then` method that allows you to register a function that should be called when the action it is waiting for finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible call `then` on a promise that has already resolved—your function will still be called. +This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provides a `then` method that allows you to register a function that should be called when the action it is waiting for finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible to call `then` on a promise that has already resolved—your function will still be called. {{index "Promise.resolve function"}} @@ -249,13 +249,13 @@ Much like an uncaught exception is handled by the environment, JavaScript enviro {{index "Carla the crow"}} -It's a sunny day in Berlin. The runway of the old, decomissioned airport is teeming with cyclist and inline skaters. In the grass near a garbage container a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. +It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting the people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight towards the roof of the hangar building, disappearing into an air vent. -Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snack, half a dozen smart phones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. +Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snacks, half a dozen smart phones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. This crow is known to her peers as “cāāw-krö”. But since those sounds are poorly suited for human vocal chords, we'll refer to her as Carla. @@ -267,7 +267,7 @@ Carla is a somewhat peculiar crow. In her youth, she was fascinated by human lan Carla loves the Internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. -Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a devices has to send along the correct 6-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending only a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not—when sending incorrect number, you immediately get a failure message. When sending the correct ones, the access point waits for more digits. +Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device has to send along the correct 6-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending only a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not—when sending incorrect number, you immediately get a failure message. When sending the correct ones, the access point waits for more digits. This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find second digit in the same way, and so on, until she knows the entire passcode. From ddad254a7d633d1627b4ccfb1dde12b626a4b754 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Thu, 7 Mar 2024 22:26:26 -0500 Subject: [PATCH 298/392] fix additional typos --- 06_object.md | 2 +- 14_dom.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/06_object.md b/06_object.md index 99e685813..fc060ede4 100644 --- a/06_object.md +++ b/06_object.md @@ -570,7 +570,7 @@ console.log([1, 2][length]); {{index [property, naming]}} -It is possible to include symbol properties in object expressions and classes by using ((square bracket))s around the property name. That causes the expression between the brackets to be evaluated to produce the property name, analoguous to the square bracket property access notation. +It is possible to include symbol properties in object expressions and classes by using ((square bracket))s around the property name. That causes the expression between the brackets to be evaluated to produce the property name, analogous to the square bracket property access notation. ``` let myTrip = { diff --git a/14_dom.md b/14_dom.md index d21f4f5b7..c1765ece8 100644 --- a/14_dom.md +++ b/14_dom.md @@ -436,7 +436,7 @@ The way an `` tag shows an image or an `` tag causes a link to be follow The second link will be green instead of the default link color. -{{figure {url: "img/colored-links.png", alt: "Rendered picture of a normal blue linke and a styled green link", width: "2.2cm"}}} +{{figure {url: "img/colored-links.png", alt: "Rendered picture of a normal blue link and a styled green link", width: "2.2cm"}}} if}} From 06a891e99c9c6763711160ec76e007ae721b290c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 9 Mar 2024 21:27:50 +0100 Subject: [PATCH 299/392] Fix typo in chapter 6 --- 06_object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_object.md b/06_object.md index fc060ede4..662666e03 100644 --- a/06_object.md +++ b/06_object.md @@ -220,7 +220,7 @@ This function cannot be called normally. Constructors, in JavaScript, are called let killerRabbit = new Rabbit("killer"); ``` -In fact, `class` was only introduce in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015 the way to define a class was to write a regular function and then manipulate its `prototype` property. +In fact, `class` was only introduced in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015 the way to define a class was to write a regular function and then manipulate its `prototype` property. ``` function ArchaicRabbit(type) { From 42311b329cfdc7dc38ea4a1a89c3ac507a52edc9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 10 Mar 2024 12:18:30 +0100 Subject: [PATCH 300/392] Fix a swapped word in chapter 10 --- 10_modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10_modules.md b/10_modules.md index ae371435a..b6ba82db3 100644 --- a/10_modules.md +++ b/10_modules.md @@ -202,7 +202,7 @@ This style of modules provides ((isolation)), to a certain degree, but it does n {{index "CommonJS modules"}} -If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used it from the start (though it now also knows how to load ES modules) it and is the module system used by many packages on ((NPM)). +If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used it from the start (though it now also knows how to load ES modules) and it is the module system used by many packages on ((NPM)). {{index "require function", [interface, module], "exports object"}} From 5aff33a7ce70e3d17b458d651117ff4fa02eaf56 Mon Sep 17 00:00:00 2001 From: Eda <58330360+rivea0@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:28:52 +0300 Subject: [PATCH 301/392] Fix typo in chapter 6 --- 06_object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_object.md b/06_object.md index 662666e03..737684463 100644 --- a/06_object.md +++ b/06_object.md @@ -722,7 +722,7 @@ The use of the word `extends` indicates that this class shouldn't be directly ba To initialize a `LengthList` instance, the constructor calls the constructor of its superclass through the `super` keyword. This is necessary because if this new object is to behave (roughly) like a `List`, it is going to need the instance properties that lists have. -The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet, since `#length` hasn't been filled in yet. We can using `super.something` to call methods and getters on the superclass's prototype, which is often useful. +The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet, since `#length` hasn't been filled in yet. We can use `super.something` to call methods and getters on the superclass's prototype, which is often useful. Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial. From e53a20a13facdf55f44eab9c801527ea9ccbfd37 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 13 Mar 2024 14:18:56 +0100 Subject: [PATCH 302/392] Integrate copyediting for chapter 6 --- 06_object.md | 90 ++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/06_object.md b/06_object.md index 737684463..090384858 100644 --- a/06_object.md +++ b/06_object.md @@ -12,9 +12,9 @@ quote}} {{figure {url: "img/chapter_picture_6.jpg", alt: "Illustration of a rabbit next to its prototype, a schematic representation of a rabbit", chapter: framed}}} -[Chapter ?](data) introduced JavaScript's objects, as containers that hold other data. +[Chapter ?](data) introduced JavaScript's objects as containers that hold other data. -In programming culture, we have a thing called _((object-oriented programming))_, a set of techniques that use objects as the central principle of program organization. Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter describes the way these ideas can be applied in JavaScript. +In programming culture, _((object-oriented programming))_ is a set of techniques that use objects as the central principle of program organization. Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter describes the way these ideas can be applied in JavaScript. ## Abstract Data Types @@ -22,22 +22,20 @@ In programming culture, we have a thing called _((object-oriented programming))_ The main idea in object-oriented programming is to use objects, or rather _types_ of objects, as the unit of program organization. Setting up a program as a number of strictly separated object types provides a way to think about its structure and thus to enforce some kind of discipline, preventing everything from becoming entangled. -The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). There's people who designed and assembled a mixer, and they have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell, so that the people who only want to mix pancake batter don't have to worry about all that—they only have to understand the few knobs that the mixer can be operated with. +The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). The people who design and assemble a mixer have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell so that the people who only want to mix pancake batter don't have to worry about all that—they only have to understand the few knobs that the mixer can be operated with. {{index "class"}} -Similarly, an abstract data type, or object class, is a subprogram that may contain arbitrarily complicated code, but exposes a limited set of methods and properties that people who are working with it are supposed to use. This allows large programs to be built up out of a number of appliance types, limiting the degree to which these different parts are entangled by requiring them to only interact with each other in specific ways. +Similarly, an _abstract data type_, or _object class_, is a subprogram that may contain arbitrarily complicated code but exposes a limited set of methods and properties that people working with it are supposed to use. This allows large programs to be built up out of a number of appliance types, limiting the degree to which these different parts are entangled by requiring them to only interact with each other in specific ways. {{index encapsulation, isolation, modularity}} -If a problem is found in one such object class, it can often be repaired, or even completely rewritten, without impacting the rest of the program. - -Even better, it may be possible to use object classes in multiple different programs, avoiding the need to recreate their functionality from scratch. You can think of JavaScript's built-in data structures, such as arrays and strings, as such reusable abstract data types. +If a problem is found in one such object class, it can often be repaired or even completely rewritten without impacting the rest of the program. Even better, it may be possible to use object classes in multiple different programs, avoiding the need to recreate their functionality from scratch. You can think of JavaScript's built-in data structures, such as arrays and strings, as such reusable abstract data types. {{id interface}} {{index [interface, object]}} -Each abstract data type has an _interface_, which is the collection of operations that external code can perform on it. Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate, since often useful pieces of functionality involve a group of different object classes working closely together. +Each abstract data type has an _interface_, the collection of operations that external code can perform on it. Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate, since useful pieces of functionality often involve a group of different object classes working closely together. {{id obj_methods}} @@ -45,7 +43,7 @@ Each abstract data type has an _interface_, which is the collection of operation {{index "rabbit example", method, [property, access]}} -In JavaScript methods are nothing more than properties that hold function values. This is a simple method: +In JavaScript, methods are nothing more than properties that hold function values. This is a simple method: ```{includeCode: "top_lines:6"} function speak(line) { @@ -62,7 +60,7 @@ hungryRabbit.speak("Got any carrots?"); {{index "this binding", "method call"}} -Typically a method needs to do something with the object it was called on. When a function is called as a method—looked up as a property and immediately called, as in `object.method()`—the binding called `this` in its body automatically points at the object that it was called on. +Typically a method needs to do something with the object on which it was called. When a function is called as a method—looked up as a property and immediately called, as in `object.method()`—the binding called `this` in its body automatically points at the object on which it was called. {{id call_method}} @@ -75,7 +73,7 @@ speak.call(whiteRabbit, "Hurry"); // → The white rabbit says 'Hurry' ``` -Since each function has its own `this` binding, whose value depends on the way it is called, you cannot refer to the `this` of the wrapping scope in a regular function defined with the `function` keyword. +Since each function has its own `this` binding whose value depends on the way it is called, you cannot refer to the `this` of the wrapping scope in a regular function defined with the `function` keyword. {{index "this binding", "arrow function"}} @@ -100,7 +98,7 @@ If I had written the argument to `some` using the `function` keyword, this code ## Prototypes -So one way to create an abstract rabbit type with a `speak` method would be to create a helper function that has a rabbit type as parameter, and returns an object holding that as its `type` property and our speak function in its `speak` property. +One way to create a rabbit object type with a `speak` method would be to create a helper function that has a rabbit type as parameter and returns an object holding that as its `type` property and our speak function in its `speak` property. All rabbits share that same method. Especially for types with many methods, it would be nice if there was a way to keep a type's methods in a single place, rather than adding them to each object individually. @@ -118,9 +116,9 @@ console.log(empty.toString()); // → [object Object] ``` -It looks like we just pulled a property out of an empty object. But in fact `toString` is a method stored in `Object.prototype`, meaning it is available in most objects. +It looks like we just pulled a property out of an empty object. But in fact, `toString` is a method stored in `Object.prototype`, meaning it is available in most objects. -When an object gets a request for a property that it does not have, its prototype will be searched for the property. If that doesn't have it, _its_ prototype is searched, and so on until an object is reached has no prototype (`Object.prototype` is such an object). +When an object gets a request for a property that it doesn't have, its prototype will be searched for the property. If that doesn't have it, the _prototype's_ prototype is searched, and so on until an object without prototype is reached (`Object.prototype` is such an object). ``` console.log(Object.getPrototypeOf({}) == Object.prototype); @@ -135,7 +133,7 @@ As you'd guess, `Object.getPrototypeOf` returns the prototype of an object. {{index inheritance, "Function prototype", "Array prototype", "Object prototype"}} -Many objects don't directly have `Object.prototype` as their ((prototype)) but instead have another object that provides a different set of default properties. Functions derive from `Function.prototype`, and arrays derive from `Array.prototype`. +Many objects don't directly have `Object.prototype` as their ((prototype)) but instead have another object that provides a different set of default properties. Functions derive from `Function.prototype` and arrays derive from `Array.prototype`. ``` console.log(Object.getPrototypeOf(Math.max) == @@ -151,7 +149,7 @@ Such a prototype object will itself have a prototype, often `Object.prototype`, {{index "rabbit example", "Object.create function"}} -You can use `Object.create` to create an object with a specific ((prototype)). +You can use `Object.create` to create an object with a specific ((prototype)): ```{includeCode: "top_lines: 7"} let protoRabbit = { @@ -167,7 +165,7 @@ blackRabbit.speak("I am fear and darkness"); {{index "shared property"}} -The "proto" rabbit acts as a container for the properties that are shared by all rabbits. An individual rabbit object, like the black rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype. +The "proto" rabbit acts as a container for the properties shared by all rabbits. An individual rabbit object, like the black rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype. {{id classes}} @@ -175,7 +173,7 @@ The "proto" rabbit acts as a container for the properties that are shared by all {{index "object-oriented programming", "abstract data type"}} -JavaScript's ((prototype)) system can be interpreted as a somewhat free-form take on abstract data types or ((class))es. A class defines the shape of a type of object—what methods and properties it has. Such an object is called an _((instance))_ of the class. +JavaScript's ((prototype)) system can be interpreted as a somewhat free-form take on abstract data types or ((class))es. A _class_ defines the shape of a type of object—what methods and properties it has. Such an object is called an _((instance))_ of the class. {{index [property, inheritance]}} @@ -183,7 +181,7 @@ Prototypes are useful for defining properties for which all instances of a class {{id constructors}} -So to create an instance of a given class, you have to make an object that derives from the proper prototype, but you _also_ have to make sure it, itself, has the properties that instances of this class are supposed to have. This is what a _((constructor))_ function does. +To create an instance of a given class, you have to make an object that derives from the proper prototype, but you _also_ have to make sure it itself has the properties that instances of this class are supposed to have. This is what a _((constructor))_ function does. ``` function makeRabbit(type) { @@ -210,17 +208,17 @@ class Rabbit { {{index "prototype property", [braces, class]}} -The `class` keyword starts a ((class declaration)), which allows us to define a constructor and a set of methods together. Any number of methods may be written inside the declaration's braces. This code has the effect of defining a binding called `Rabbit`, which holds a function that runs the code in `constructor`, and has a `prototype` property which holds the `speak` method. +The `class` keyword starts a ((class declaration)), which allows us to define a constructor and a set of methods together. Any number of methods may be written inside the declaration's braces. This code has the effect of defining a binding called `Rabbit`, which holds a function that runs the code in `constructor` and has a `prototype` property which holds the `speak` method. {{index "new operator", "this binding", [object, creation]}} -This function cannot be called normally. Constructors, in JavaScript, are called by putting the keyword `new` in front of them. Doing so creates a fresh object with the object held in the function's `prototype` property as prototype, then runs the function with `this` bound to the new object, and finally returns the object. +This function cannot be called like a normal function. Constructors, in JavaScript, are called by putting the keyword `new` in front of them. Doing so creates a fresh instance object whose prototype is the object from the function's `prototype` property, then runs the function with `this` bound to the new object, and finally returns the object. ```{includeCode: true} let killerRabbit = new Rabbit("killer"); ``` -In fact, `class` was only introduced in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015 the way to define a class was to write a regular function and then manipulate its `prototype` property. +In fact, `class` was only introduced in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015, the way to define a class was to write a regular function and then manipulate its `prototype` property. ``` function ArchaicRabbit(type) { @@ -240,7 +238,7 @@ By convention, the names of constructors are capitalized so that they can easily {{index "prototype property", "getPrototypeOf function"}} -It is important to understand the distinction between the way a prototype is associated with a constructor (through its `prototype` _property_) and the way objects _have_ a prototype (which can be found with `Object.getPrototypeOf`). The actual prototype of a constructor is `Function.prototype` since constructors are functions. Its `prototype` _property_ holds the prototype used for instances created through it. +It is important to understand the distinction between the way a prototype is associated with a constructor (through its `prototype` property) and the way objects _have_ a prototype (which can be found with `Object.getPrototypeOf`). The actual prototype of a constructor is `Function.prototype` since constructors are functions. The constructor function's `prototype` _property_ holds the prototype used for instances created through it. ``` console.log(Object.getPrototypeOf(Rabbit) == @@ -277,7 +275,7 @@ console.log(object.getWord()); {{index [property, private], [property, public], "class declaration"}} -It is common for classes to define some properties and ((method))s for internal use, which are not part of their ((interface)). These are called _private_ properties, as opposed to _public_ ones, which are part of the object's external interface. +It is common for classes to define some properties and ((method))s for internal use that are not part of their ((interface)). These are called _private_ properties, as opposed to _public_ ones, which are part of the object's external interface. {{index [method, private]}} @@ -295,6 +293,8 @@ class SecretiveObject { } ``` +When a class does not declare a constructor, it will automatically get an empty one. + If you try to call `#getSecret` from outside the class, you get an error. Its existence is entirely hidden inside the class declaration. To use private instance properties, you must declare them. Regular properties can be created by just assigning to them, but private properties _must_ be declared in the class declaration to be available at all. @@ -340,7 +340,7 @@ The following diagram sketches the situation after this code has run. The `Rabbi {{index "shared property"}} -Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects, while letting the nonexceptional objects take a standard value from their prototype. +Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects while letting the nonexceptional objects take a standard value from their prototype. {{index "toString method", "Array prototype", "Function prototype"}} @@ -367,7 +367,7 @@ console.log(Object.prototype.toString.call([1, 2])); {{index "map method"}} -We saw the word _map_ used in the [previous chapter](higher_order#map) for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is also used for a related but rather different thing. +We saw the word _map_ used in the [previous chapter](higher_order#map) for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is used for a related but rather different thing. {{index "map (data structure)", "ages example", ["data structure", map]}} @@ -390,11 +390,11 @@ console.log("Is toString's age known?", "toString" in ages); {{index "Object.prototype", "toString method"}} -Here, the object's property names are the people's names, and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet, because plain objects derive from `Object.prototype`, it looks like the property is there. +Here, the object's property names are the people's names and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet, because plain objects derive from `Object.prototype`, it looks like the property is there. {{index "Object.create function", prototype}} -As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, it is possible to create objects with _no_ prototype. If you pass `null` to `Object.create`, the resulting object will not derive from `Object.prototype` and can safely be used as a map. +As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, you can create objects with _no_ prototype. If you pass `null` to `Object.create`, the resulting object will not derive from `Object.prototype` and can safely be used as a map. ``` console.log("toString" in Object.create(null)); @@ -455,7 +455,7 @@ console.log(String(killerRabbit)); {{index "object-oriented programming", [interface, object]}} -This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a `toString` method—any kind of object that happens to support this interface can be plugged into the code, and it will be able to work with it. +This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a `toString` method—any kind of object that happens to support this interface can be plugged into the code and will be able to work with it. This technique is called _polymorphism_. Polymorphic code can work with values of different shapes, as long as they support the interface it expects. @@ -479,7 +479,7 @@ Array.prototype.forEach.call({ Interfaces often contain plain properties, not just methods. For example, `Map` objects have a `size` property that tells you how many keys are stored in them. -It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_, and they are defined by writing `get` in front of the method name in an object expression or class declaration. +It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_ and are defined by writing `get` in front of the method name in an object expression or class declaration. ```{test: no} let varyingSize = { @@ -529,7 +529,13 @@ The `Temperature` class allows you to read and write the temperature in either d Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. -Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. So the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit. +Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. For example, the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit: + +``` +let boil = Temperature.fromFahrenheit(212); +console.log(boil.celsius); +// → 100 +``` ## Symbols @@ -537,13 +543,13 @@ Inside a class declaration, methods or properties that have `static` written bef I mentioned in [Chapter ?](data#for_of_loop) that a `for`/`of` loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to our own objects! But before we can do that, we need to briefly take a look at the symbol type. -It is possible for multiple interfaces to use the same property name for different things. For example, on array-like objects, `length` refers to the amount of elements in the collection. But an object interface describing a hiking route could use `length` to provide the length of the route in meters. It would not be possible for an object to conform to both these interfaces. +It is possible for multiple interfaces to use the same property name for different things. For example, on array-like objects, `length` refers to the number of elements in the collection. But an object interface describing a hiking route could use `length` to provide the length of the route in meters. It would not be possible for an object to conform to both these interfaces. -An object trying to be a route and array-like (maybe to enumerate its waypoints) is somewhat far-fetched, and this kind of problem isn't that common in practice. But for things like the iteration protocol, the language designers needed a type of property that _really_ doesn't conflict with any others. So in 2015, _((symbol))s_ were added to the language. +An object trying to be a route and array-like (maybe to enumerate its waypoints) is somewhat far-fetched, and this kind of problem isn't that common in practice. For things like the iteration protocol, though, the language designers needed a type of property that _really_ doesn't conflict with any others. So in 2015, _((symbol))s_ were added to the language. {{index "Symbol function", [property, naming]}} -Most properties, including all the properties we have seen so far, are named with strings. But it is also possible to use symbols as property names. Symbols are values created with the `Symbol` function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice. +Most properties, including all those we have seen so far, are named with strings. But it is also possible to use symbols as property names. Symbols are values created with the `Symbol` function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice. ``` let sym = Symbol("name"); @@ -632,7 +638,7 @@ class List { } ``` -Note that `this`, in a static method, points at the constructor of the class, not an instance—there is no instance around, when a static method is called. +Note that `this`, in a static method, points at the constructor of the class, not an instance—there is no instance around when a static method is called. Iterating over a list should return all the list's elements from start to end. We'll write a separate class for the iterator. @@ -655,7 +661,7 @@ class ListIterator { } ``` -The class tracks the progress of iterating through the list by updating its `list` property to move to the next list object whenever a value is returned, and reports that it is done when that list is empty (null). +The class tracks the progress of iterating through the list by updating its `list` property to move to the next list object whenever a value is returned and reports that it is done when that list is empty (null). Let's set up the `List` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead. @@ -681,7 +687,7 @@ for (let element of list) { {{index spread}} -The `...` syntax in array notation and function calls similarly works with any iterable object. So for example, you can use `[...value]` to create an array containing the elements in an arbitrary iterable object. +The `...` syntax in array notation and function calls similarly works with any iterable object. For example, you can use `[...value]` to create an array containing the elements in an arbitrary iterable object. ``` console.log([..."PCI"]); @@ -692,11 +698,11 @@ console.log([..."PCI"]); {{index inheritance, "linked list", "object-oriented programming", "LengthList class"}} -Imagine we needed a list type, much like the `List` class we saw before, but because we will be asking for its length all the time, we don't want it to have to scan through its `rest` every time, and instead want to store the length in every instance for efficient access. +Imagine we need a list type much like the `List` class we saw before, but because we will be asking for its length all the time, we don't want it to have to scan through its `rest` every time. Instead, we want to store the length in every instance for efficient access. {{index overriding, prototype}} -JavaScript's prototype system makes it possible to create a _new_ class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `length` getter. +JavaScript's prototype system makes it possible to create a _new_ class, much like the old class but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `length` getter. In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class. @@ -728,7 +734,7 @@ Inheritance allows us to build slightly different data types from existing data {{index complexity, reuse, "class hierarchy"}} -Whereas ((encapsulation)) and polymorphism can be used to _separate_ pieces of code from each other, reducing the tangledness of the overall program, ((inheritance)) fundamentally ties classes together, creating _more_ tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool to make some types of programs more succinct, but it shouldn't be the first tool you reach for, and you probably shouldn't actively go looking for opportunities to construct class hierarchies (family trees of classes). +Whereas ((encapsulation)) and polymorphism can be used to _separate_ pieces of code from one another, reducing the tangledness of the overall program, ((inheritance)) fundamentally ties classes together, creating _more_ tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool to make some types of programs more succinct, but it shouldn't be the first tool you reach for, and you probably shouldn't actively go looking for opportunities to construct class hierarchies (family trees of classes). ## The instanceof operator @@ -758,7 +764,7 @@ Objects do more than just hold their own properties. They have prototypes, which Constructors, which are functions whose names usually start with a capital letter, can be used with the `new` operator to create new objects. The new object's prototype will be the object found in the `prototype` property of the constructor. You can make good use of this by putting the properties that all values of a given type share into their prototype. There's a `class` notation that provides a clear way to define a constructor and its prototype. -You can define getters and setters to secretly call methods every time an object's property is accessed. Static methods are methods stored in a class's constructor, rather than its prototype. +You can define getters and setters to secretly call methods every time an object's property is accessed. Static methods are methods stored in a class's constructor rather than its prototype. The `instanceof` operator can, given an object and a constructor, tell you whether that object is an instance of that constructor. @@ -776,7 +782,7 @@ When implementing multiple classes that differ in only some details, it can be h {{index dimensions, "Vec class", coordinates, "vector (exercise)"}} -Write a ((class)) `Vec` that represents a vector in two-dimensional space. It takes `x` and `y` parameters (numbers), which it should save to properties of the same name. +Write a ((class)) `Vec` that represents a vector in two-dimensional space. It takes `x` and `y` parameters (numbers), that it saves to properties of the same name. {{index addition, subtraction}} From 5a7f8812f7c441acaa638e30eb245d01d33e9060 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 13 Mar 2024 15:32:20 +0100 Subject: [PATCH 303/392] Integrate copyediting for chapter 7 --- 07_robot.md | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/07_robot.md b/07_robot.md index 9499341f4..4ba488402 100644 --- a/07_robot.md +++ b/07_robot.md @@ -4,7 +4,7 @@ {{quote {author: "Edsger Dijkstra", title: "The Threats to Computing Science", chapter: true} -[...] the question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim. +The question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim. quote}} @@ -64,15 +64,13 @@ function buildGraph(edges) { const roadGraph = buildGraph(roads); ``` -Given an array of edges, `buildGraph` creates a map object that, for each node, stores an array of connected nodes. - {{index "split method"}} -It uses the `split` method to go from the road strings, which have the form `"Start-End"`, to two-element arrays containing the start and end as separate strings. +Given an array of edges, `buildGraph` creates a map object that, for each node, stores an array of connected nodes. It uses the `split` method to go from the road strings—which have the form `"Start-End"`)—to two-element arrays containing the start and end as separate strings. ## The task -Our ((robot)) will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes to them and delivers them when it arrives at their destinations. +Our ((robot)) will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes across them and delivers them when it arrives at their destinations. The automaton must decide, at each point, where to go next. It has finished its task when all parcels have been delivered. @@ -92,7 +90,7 @@ Instead, let's condense the village's state down to the minimal set of values th {{index "VillageState class", "persistent data structure"}} -And while we're at it, let's make it so that we don't _change_ this state when the robot moves but rather compute a _new_ state for the situation after the move. +While we're at it, let's make it so that we don't _change_ this state when the robot moves but rather compute a _new_ state for the situation after the move. ```{includeCode: true} class VillageState { @@ -119,7 +117,7 @@ The `move` method is where the action happens. It first checks whether there is {{index "map method", "filter method"}} -Then it creates a new state with the destination as the robot's new place. But it also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot's current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to `map` takes care of the moving, and the call to `filter` does the delivering. +Next, the method creates a new state with the destination as the robot's new place. It also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot's current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to `map` takes care of the moving, and the call to `filter` does the delivering. Parcel objects aren't changed when they are moved but re-created. The `move` method gives us a new village state but leaves the old one entirely intact. @@ -138,7 +136,7 @@ console.log(first.place); // → Post Office ``` -The move causes the parcel to be delivered, and this is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered. +The move causes the parcel to be delivered, which is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered. ## Persistent data @@ -146,7 +144,7 @@ The move causes the parcel to be delivered, and this is reflected in the next st Data structures that don't change are called _((immutable))_ or _persistent_. They behave a lot like strings and numbers in that they are who they are and stay that way, rather than containing different things at different times. -In JavaScript, just about everything _can_ be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called `Object.freeze` that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren't changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. So I usually prefer to just tell people that a given object shouldn't be messed with and hope they remember it. +In JavaScript, just about everything _can_ be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called `Object.freeze` that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren't changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. I usually prefer to just tell people that a given object shouldn't be messed with and hope they remember it. ``` let object = Object.freeze({value: 5}); @@ -155,9 +153,7 @@ console.log(object.value); // → 5 ``` -Why am I going out of my way to not change objects when the language is obviously expecting me to? - -Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice's house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning. +Why am I going out of my way to not change objects when the language is obviously expecting me to? Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice's house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning. For a small system like the one we are building in this chapter, we could handle that bit of extra complexity. But the most important limit on what kind of systems we can build is how much we can understand. Anything that makes your code easier to understand makes it possible to build a more ambitious system. @@ -171,7 +167,7 @@ A delivery ((robot)) looks at the world and decides in which direction it wants {{index "runRobot function"}} -Because we want robots to be able to remember things, so that they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called. +Because we want robots to be able to remember things so they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called. ```{includeCode: true} function runRobot(state, robot, memory) { @@ -188,7 +184,7 @@ function runRobot(state, robot, memory) { } ``` -Consider what a robot has to do to "solve" a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location that a parcel is addressed to, but only after picking up the parcel. +Consider what a robot has to do to "solve" a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location to which a parcel is addressed, but only after picking up the parcel. What is the dumbest strategy that could possibly work? The robot could just walk in a random direction every turn. That means, with great likelihood, it will eventually run into all parcels and then also at some point reach the place where they should be delivered. @@ -232,7 +228,7 @@ VillageState.random = function(parcelCount = 5) { {{index "do loop"}} -We don't want any parcels that are sent from the same place that they are addressed to. For this reason, the `do` loop keeps picking new places when it gets one that's equal to the address. +We don't want any parcels to be sent from the same place that they are addressed to. For this reason, the `do` loop keeps picking new places when it gets one that's equal to the address. Let's start up a virtual world. @@ -304,11 +300,11 @@ Still, I wouldn't really call blindly following a fixed route intelligent behavi To do that, it has to be able to deliberately move toward a given parcel or toward the location where a parcel has to be delivered. Doing that, even when the goal is more than one move away, will require some kind of route-finding function. -The problem of finding a route through a ((graph)) is a typical _((search problem))_. We can tell whether a given solution (a route) is a valid solution, but we can't directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works. +The problem of finding a route through a ((graph)) is a typical _((search problem))_. We can tell whether a given solution (a route) is valid, but we can't directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works. The number of possible routes through a graph is infinite. But when searching for a route from _A_ to _B_, we are interested only in the ones that start at _A_. We also don't care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider. -In fact, we are mostly interested in the _shortest_ route. So we want to make sure we look at short routes before we look at longer ones. A good approach would be to "grow" routes from the starting point, exploring every reachable place that hasn't been visited yet, until a route reaches the goal. That way, we'll only explore routes that are potentially interesting, and we know that the first route we find is the shortest route (or one of the shortest routes, if there are more than one). +In fact, since we are mostly interested in the _shortest_ route, we want to make sure we look at short routes before we look at longer ones. A good approach would be to "grow" routes from the starting point, exploring every reachable place that hasn't been visited yet until a route reaches the goal. That way, we'll only explore routes that are potentially interesting, and we know that the first route we find is the shortest route (or one of the shortest routes, if there are more than one). {{index "findRoute function"}} @@ -335,9 +331,9 @@ The exploring has to be done in the right order—the places that were reached f Therefore, the function keeps a _((work list))_. This is an array of places that should be explored next, along with the route that got us there. It starts with just the start position and an empty route. -The search then operates by taking the next item in the list and exploring that, which means all roads going from that place are looked at. If one of them is the goal, a finished route can be returned. Otherwise, if we haven't looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we've found either a longer route to that place or one precisely as long as the existing one, and we don't need to explore it. +The search then operates by taking the next item in the list and exploring that, which means it looks at all roads going from that place. If one of them is the goal, a finished route can be returned. Otherwise, if we haven't looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we've found either a longer route to that place or one precisely as long as the existing one, and we don't need to explore it. -You can visually imagine this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route. +You can visualize this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route. {{index "connected graph"}} @@ -372,7 +368,7 @@ runRobotAnimation(VillageState.random(), if}} -This robot usually finishes the task of delivering 5 parcels in about 16 turns. That's slightly better than `routeRobot` but still definitely not optimal. +This robot usually finishes the task of delivering 5 parcels in about 16 turns. That's slightly better than `routeRobot` but still definitely not optimal. We'll continue refining it in the exercises. ## Exercises @@ -382,7 +378,7 @@ This robot usually finishes the task of delivering 5 parcels in about 16 turns. It's hard to objectively compare ((robot))s by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn't. -Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task. +Write a function `compareRobots` that takes two robots (and their starting memory). It generates 100 tasks and lets each of the robots solve each of these tasks. When done, it outputs the average number of steps each robot took per task. For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot. @@ -441,11 +437,9 @@ hint}} Most data structures provided in a standard JavaScript environment aren't very well suited for persistent use. Arrays have `slice` and `concat` methods, which allow us to easily create new arrays without damaging the old one. But `Set`, for example, has no methods for creating a new set with an item added or removed. -Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. - -Its `add` method, however, should return a _new_ `PGroup` instance with the given member added and leave the old one unchanged. Similarly, `delete` creates a new instance without a given member. +Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. Its `add` method, however, returns a _new_ `PGroup` instance with the given member added and leaves the old one unchanged. Similarly, `delete` creates a new instance without a given member. -The class should work for values of any type, not just strings. It does _not_ have to be efficient when used with large amounts of values. +The class should work for values of any type, not just strings. It does _not_ have to be efficient when used with large numbers of values. {{index [interface, object]}} From 2ef3f89c7237d946e1d3837ebdfd1ac5a5b35563 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 13 Mar 2024 16:11:27 +0100 Subject: [PATCH 304/392] Integrate copyediting for Chapter 8 --- 08_error.md | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/08_error.md b/08_error.md index 35a3bc3e6..dfb889b1b 100644 --- a/08_error.md +++ b/08_error.md @@ -14,13 +14,13 @@ quote}} Flaws in computer programs are usually called _((bug))s_. It makes programmers feel good to imagine them as little things that just happen to crawl into our work. In reality, of course, we put them there ourselves. -If a program is crystallized thought, you can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter. +If a program is crystallized thought, we can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter. ## Language {{index parsing, analysis}} -Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we're trying to do. But here JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. And even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. +Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we're trying to do. But here JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. Even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. {{index [syntax, error], [property, access]}} @@ -28,7 +28,7 @@ There are some things that JavaScript does complain about. Writing a program tha {{index NaN, error}} -But often, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. +Often, however, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. The process of finding mistakes—bugs—in programs is called _((debugging))_. @@ -38,7 +38,7 @@ The process of finding mistakes—bugs—in programs is called _((debugging))_. {{indexsee "use strict", "strict mode"}} -JavaScript can be made a _little_ stricter by enabling _strict mode_. This is done by putting the string `"use strict"` at the top of a file or a function body. Here's an example: +JavaScript can be made a _little_ stricter by enabling _strict mode_. This can done by putting the string `"use strict"` at the top of a file or a function body. Here's an example: ```{test: "error \"ReferenceError: counter is not defined\""} function canYouSpotTheProblem() { @@ -52,6 +52,10 @@ canYouSpotTheProblem(); // → ReferenceError: counter is not defined ``` +{{index ECMAScript, compatibility}} + +Code inside classes and modules (which we will discuss in [Chapter ?](modules)) is automatically strict. The old non-strict behavior still exists only because some old code might depend on it, and the language designers work hard to avoid breaking any existing programs. + {{index "let keyword", [binding, global]}} Normally, when you forget to put `let` in front of your binding, as with `counter` in the example, JavaScript quietly creates a global binding and uses that. In strict mode, an ((error)) is reported instead. This is very helpful. It should be noted, though, that this doesn't work when the binding in question already exists somewhere in scope. In that case, the loop will still quietly overwrite the value of the binding. @@ -71,7 +75,7 @@ console.log(name); {{index error}} -So the bogus call to `Person` succeeded but returned an undefined value and created the global binding `name`. In strict mode, the result is different. +The bogus call to `Person` succeeded but returned an undefined value and created the global binding `name`. In strict mode, the result is different. ```{test: "error \"TypeError: Cannot set properties of undefined (setting 'name')\""} "use strict"; @@ -196,7 +200,7 @@ Even if you see the problem already, pretend for a moment that you don't. We kno {{index "trial and error"}} -This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, _think_. Analyze what is happening and come up with a ((theory)) of why it might be happening. Then, make additional observations to test this theory—or, if you don't yet have a theory, make additional observations to help you come up with one. +This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, _think_. Analyze what is happening and come up with a ((theory)) of why it might be happening. Then make additional observations to test this theory—or, if you don't yet have a theory, make additional observations to help you come up with one. {{index "console.log", output, debugging, logging}} @@ -219,7 +223,7 @@ _Right_. Dividing 13 by 10 does not produce a whole number. Instead of `n /= bas An alternative to using `console.log` to peek into the program's behavior is to use the _debugger_ capabilities of your browser. Browsers come with the ability to set a _((breakpoint))_ on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won't go into details, as debuggers differ from browser to browser, but look in your browser's ((developer tools)) or search the Web for instructions. -Another way to set a breakpoint is to include a `debugger` statement (consisting of simply that keyword) in your program. If the ((developer tools)) of your browser are active, the program will pause whenever it reaches such a statement. +Another way to set a breakpoint is to include a `debugger` statement (consisting simply of that keyword) in your program. If the ((developer tools)) of your browser are active, the program will pause whenever it reaches such a statement. ## Error propagation @@ -229,7 +233,7 @@ Not all problems can be prevented by the programmer, unfortunately. If your prog {{index "error recovery"}} -If you're programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. But in either situation, the program has to actively do something in response to the problem. +If you're programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. In either situation the program has to actively do something in response to the problem. {{index "promptNumber function", validation}} @@ -237,7 +241,7 @@ Say you have a function `promptNumber` that asks the user for a number and retur {{index null, undefined, "return value", "special return value"}} -One option is to make it return a special value. Common choices for such values are `null`, `undefined`, or -1. +One option is to make it return a special value. Common choices for such values are `null`, `undefined`, or `-1`. ```{test: no} function promptNumber(question) { @@ -267,7 +271,7 @@ function lastElement(array) { {{index "special return value", readability}} -The second issue with returning special values is that it can lead to awkward code. If a piece of code calls `promptNumber` 10 times, it has to check 10 times whether `null` was returned. And if its response to finding `null` is to simply return `null` itself, callers of the function will in turn have to check for it, and so on. +The second issue with returning special values is that it can lead to awkward code. If a piece of code calls `promptNumber` 10 times, it has to check 10 times whether `null` was returned. If its response to finding `null` is to simply return `null` itself, callers of the function will in turn have to check for it, and so on. ## Exceptions @@ -277,7 +281,7 @@ When a function cannot proceed normally, what we would often _like_ to do is jus {{index ["control flow", exceptions], "raising (exception)", "throw keyword", "call stack"}} -Exceptions are a mechanism that makes it possible for code that runs into a problem to _raise_ (or _throw_) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called _((unwinding the stack))_. You may remember the stack of function calls that was mentioned in [Chapter ?](functions#stack). An exception zooms down this stack, throwing away all the call contexts it encounters. +Exceptions are a mechanism that makes it possible for code that runs into a problem to _raise_ (or _throw_) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called _((unwinding the stack))_. You may remember the stack of function calls mentioned in [Chapter ?](functions#stack). An exception zooms down this stack, throwing away all the call contexts it encounters. {{index "error handling", [syntax, statement], "catch keyword"}} @@ -333,7 +337,7 @@ This means when code has several side effects, even if its "regular" control flo {{index "banking example"}} -Here is some really bad banking code. +Here is some really bad banking code: ```{includeCode: true} const accounts = { @@ -367,7 +371,7 @@ One way to address this is to use fewer side effects. Again, a programming style {{index block, "try keyword", "finally keyword"}} -But that isn't always practical. So there is another feature that `try` statements have. They may be followed by a `finally` block either instead of or in addition to a `catch` block. A `finally` block says "no matter _what_ happens, run this code after trying to run the code in the `try` block." +Since that isn't always practical, `try` statements have another feature: they may be followed by a `finally` block either instead of or in addition to a `catch` block. A `finally` block says "no matter _what_ happens, run this code after trying to run the code in the `try` block." ```{includeCode: true} function transfer(from, amount) { @@ -438,13 +442,13 @@ for (;;) { {{index "infinite loop", "for loop", "catch keyword", debugging}} -The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. _But_ we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it "buries" the useful error message about the misspelled binding. +The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. Unfortunately, we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it "buries" the useful error message about the misspelled binding. As a general rule, don't blanket-catch exceptions unless it is for the purpose of "routing" them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information. {{index "exception handling"}} -So we want to catch a _specific_ kind of exception. We can do this by checking in the `catch` block whether the exception we got is the one we are interested in and rethrowing it otherwise. But how do we recognize an exception? +We want to catch a _specific_ kind of exception. We can do this by checking in the `catch` block whether the exception we got is the one we are interested in, and if not, rethrow it. But how do we recognize an exception? We could compare its `message` property against the ((error)) message we happen to expect. But that's a shaky way to write code—we'd be using information that's intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working. From fe6b2f78ca1dc6dddf6403cf561eed11733ee51e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 18 Mar 2024 10:17:41 +0100 Subject: [PATCH 305/392] Link Spanish translation Closes https://github.com/marijnh/Eloquent-JavaScript/issues/577 --- html/index.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/html/index.html b/html/index.html index 89fc5f1be..f5ca4eb00 100644 --- a/html/index.html +++ b/html/index.html @@ -116,6 +116,12 @@

Other pages

Translations

+

Fourth Edition

+ +
+

Third Edition

    From 47caf0751e9eca2d56661a236d6bc4c921ea9f85 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 20 Mar 2024 10:05:49 +0100 Subject: [PATCH 306/392] Integrate copyediting for chapter 9 --- 09_regexp.md | 90 +++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/09_regexp.md b/09_regexp.md index 064de87b6..4610562c9 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -30,7 +30,7 @@ In this chapter, I will discuss one such tool, _((regular expression))s_. Regula {{index [interface, design]}} -Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic, and the programming interface JavaScript provides for them is clumsy. But they are a powerful ((tool)) for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer. +Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic and the programming interface JavaScript provides for them is clumsy. But they are a powerful ((tool)) for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer. ## Creating a regular expression @@ -111,7 +111,7 @@ A number of common character groups have their own built-in shortcuts. Digits ar | `\S` | A nonwhitespace character | `.` | Any character except for newline -So you could match a ((date)) and ((time)) format like 01-30-2003 15:20 with the following expression: +You could match a ((date)) and ((time)) format like 01-30-2003 15:20 with the following expression: ``` let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/; @@ -123,11 +123,11 @@ console.log(dateTime.test("30-jan-2003 15:20")); {{index ["backslash character", "in regular expressions"]}} -That looks completely awful, doesn't it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual ((pattern)) expressed. We'll see a slightly improved version of this expression [later](regexp#date_regexp_counted). +That regular expression looks completely awful, doesn't it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual ((pattern)) expressed. We'll see a slightly improved version of this expression [later](regexp#date_regexp_counted). {{index [escaping, "in regexps"], "regular expression", set}} -These backslash codes can also be used inside ((square brackets)). For example, `[\d.]` means any digit or a period character. But the period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as `+`. +These backslash codes can also be used inside ((square brackets)). For example, `[\d.]` means any digit or a period character. The period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as the plus sign (`+`). {{index "square brackets", inversion, "caret character"}} @@ -207,7 +207,7 @@ The star (`*`) has a similar meaning but also allows the pattern to match zero t {{index "British English", "American English", "question mark"}} -A question mark makes a part of a pattern _((optional))_, meaning it may occur zero times or one time. In the following example, the _u_ character is allowed to occur, but the pattern also matches when it is missing. +A question mark (`?`) makes a part of a pattern _((optional))_, meaning it may occur zero times or one time. In the following example, the _u_ character is allowed to occur, but the pattern also matches when it is missing: ``` let neighbor = /neighbou?r/; @@ -231,13 +231,13 @@ console.log(dateTime.test("1-30-2003 8:45")); // → true ``` -You can also specify open-ended ((range))s when using braces by omitting the number after the comma. So, `{5,}` means five or more times. +You can also specify open-ended ((range))s when using braces by omitting the number after the comma. For example, `{5,}` means five or more times. ## Grouping subexpressions {{index ["regular expression", grouping], grouping, [parentheses, "in regular expressions"]}} -To use an operator like `*` or `+` on more than one element at a time, you have to use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned. +To use an operator like `*` or `+` on more than one element at a time, you must use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned. ``` let cartoonCrying = /boo+(hoo+)+/i; @@ -247,11 +247,11 @@ console.log(cartoonCrying.test("Boohoooohoohooo")); {{index crying}} -The first and second `+` characters apply only to the second _o_ in _boo_ and _hoo_, respectively. The third `+` applies to the whole group `(hoo+)`, matching one or more sequences like that. +The first and second `+` characters apply only to the second `o` in `boo` and `hoo`, respectively. The third `+` applies to the whole group `(hoo+)`, matching one or more sequences like that. {{index "case sensitivity", capitalization, ["regular expression", flags]}} -The `i` at the end of the expression in the example makes this regular expression case insensitive, allowing it to match the uppercase _B_ in the input string, even though the pattern is itself all lowercase. +The `i` at the end of the expression in the example makes this regular expression case-insensitive, allowing it to match the uppercase _B_ in the input string, even though the pattern is itself all lowercase. ## Matches and groups @@ -292,7 +292,7 @@ console.log(quotedText.exec("she said 'hello'")); {{index "capture group"}} -When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold `undefined`. And when a group is matched multiple times (for example when it is followed by a `+`), only the last match ends up in the array. +When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold `undefined`. When a group is matched multiple times (for example, when followed by a `+`), only the last match ends up in the array. ``` console.log(/bad(ly)?/.exec("bad")); @@ -312,13 +312,13 @@ console.log(/(?:na)+/.exec("banana")); Groups can be useful for extracting parts of a string. If we don't just want to verify whether a string contains a ((date)) but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of `exec`. -But first we'll take a brief detour, in which we discuss the built-in way to represent date and ((time)) values in JavaScript. +But first we'll take a brief detour to discuss the built-in way to represent date and ((time)) values in JavaScript. ## The Date class {{index constructor, "Date class"}} -JavaScript has a standard class for representing ((date))s—or, rather, points in ((time)). It is called `Date`. If you simply create a date object using `new`, you get the current date and time. +JavaScript has a standard `Date` class for representing ((date))s, or rather, points in ((time)). If you simply create a date object using `new`, you get the current date and time. ```{test: no} console.log(new Date()); @@ -377,7 +377,7 @@ console.log(getDate("1-30-2003")); {{index destructuring, "underscore character"}} -The `_` (underscore) binding is ignored and used only to skip the full match element in the array returned by `exec`. +The underscore (`_`) binding is ignored and used only to skip the full match element in the array returned by `exec`. ## Boundaries and look-ahead @@ -387,17 +387,17 @@ Unfortunately, `getDate` will also happily extract a date from the string `"100- {{index boundary, "caret character", "dollar sign"}} -If we want to enforce that the match must span the whole string, we can add the markers `^` and `$`. The caret matches the start of the input string, whereas the dollar sign matches the end. So, `/^\d+$/` matches a string consisting entirely of one or more digits, `/^!/` matches any string that starts with an exclamation mark, and `/x^/` does not match any string (there cannot be an _x_ before the start of the string). +If we want to enforce that the match must span the whole string, we can add the markers `^` and `$`. The caret matches the start of the input string, whereas the dollar sign matches the end. Thus `/^\d+$/` matches a string consisting entirely of one or more digits, `/^!/` matches any string that starts with an exclamation mark, and `/x^/` does not match any string (there cannot be an `x` before the start of the string). {{index "word boundary", "word character"}} -There is also a `\b` marker, which matches “word boundaries”, positions that have a word character one side, and a non-word character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w`, and are therefore not very reliable. +There is also a `\b` marker that matches _word boundaries_, positions that have a word character one side, and a non-word character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w`, and are therefore not very reliable. -Note that these markers don't match any actual characters. They just enforces that a given condition holds at the place where they appears in the pattern. +Note that these boundary markers don't match any actual characters. They just enforce that a given condition holds at the place where they appears in the pattern. {{index "look-ahead"}} -_Look-ahead_ tests do something similar. They provide a pattern, and will make the match fail if the input doesn't match that pattern, but don't actually move the match position forward. They are written between `(?=` and `)`. +_Look-ahead_ tests do something similar. They provide a pattern and will make the match fail if the input doesn't match that pattern, but don't actually move the match position forward. They are written between `(?=` and `)`. ``` console.log(/a(?=e)/.exec("braeburn")); @@ -406,7 +406,7 @@ console.log(/a(?! )/.exec("a b")); // → null ``` -Note how the `e` in the first example is necessary to match, but is not part of the matched string. The `(?! )` notation expresses a _negative_ look-ahead. This only matches if the pattern in the parentheses _doesn't_ match, causing the second example to only match “a” characters that don't have a space after them. +The `e` in the first example is necessary to match, but is not part of the matched string. The `(?! )` notation expresses a _negative_ look-ahead. This only matches if the pattern in the parentheses _doesn't_ match, causing the second example to only match `a` characters that don't have a space after them. ## Choice patterns @@ -414,7 +414,7 @@ Note how the `e` in the first example is necessary to match, but is not part of Say we want to know whether a piece of text contains not only a number but a number followed by one of the words _pig_, _cow_, or _chicken_, or any of their plural forms. -We could write three regular expressions and test them in turn, but there is a nicer way. The ((pipe character)) (`|`) denotes a ((choice)) between the pattern to its left and the pattern to its right. So I can say this: +We could write three regular expressions and test them in turn, but there is a nicer way. The ((pipe character)) (`|`) denotes a ((choice)) between the pattern to its left and the pattern to its right. We can use it in expressions like this: ``` let animalCount = /\d+ (pig|cow|chicken)s?/; @@ -426,13 +426,13 @@ console.log(animalCount.test("15 pugs")); {{index [parentheses, "in regular expressions"]}} -Parentheses can be used to limit the part of the pattern that the pipe operator applies to, and you can put multiple such operators next to each other to express a choice between more than two alternatives. +Parentheses can be used to limit the part of the pattern to which the pipe operator applies, and you can put multiple such operators next to each other to express a choice between more than two alternatives. ## The mechanics of matching {{index ["regular expression", matching], [matching, algorithm], "search problem"}} -Conceptually, when you use `exec` or `test`, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on, until it finds a match or reaches the end of the string. It'll either return the first match that can be found or fail to find any match at all. +Conceptually, when you use `exec` or `test`, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on until it finds a match or reaches the end of the string. It'll either return the first match that can be found or fail to find any match at all. {{index ["regular expression", matching], [matching, algorithm]}} @@ -442,7 +442,7 @@ To do the actual matching, the engine treats a regular expression something like {{index traversal}} -Our expression matches if we can find a path from the left side of the diagram to the right side. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box. +If we can find a path from the left side of the diagram to the right side, our expression matches. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box. {{id backtracking}} @@ -450,23 +450,23 @@ Our expression matches if we can find a path from the left side of the diagram t {{index ["regular expression", backtracking], "binary number", "decimal number", "hexadecimal number", "flow diagram", [matching, algorithm], backtracking}} -The regular expression `/^([01]+b|[\da-f]+h|\d+)$/` matches either a binary number followed by a _b_, a hexadecimal number (that is, base 16, with the letters _a_ to _f_ standing for the digits 10 to 15) followed by an _h_, or a regular decimal number with no suffix character. This is the corresponding diagram: +The regular expression `/^([01]+b|[\da-f]+h|\d+)$/` matches either a binary number followed by a `b`, a hexadecimal number (that is, base 16, with the letters `a` to `f` standing for the digits 10 to 15) followed by an `h`, or a regular decimal number with no suffix character. This is the corresponding diagram: {{figure {url: "img/re_number.svg", alt: "Railroad diagram for the regular expression '^([01]+b|\\d+|[\\da-f]+h)$'"}}} {{index branching}} -When matching this expression, it will often happen that the top (binary) branch is entered even though the input does not actually contain a binary number. When matching the string `"103"`, for example, it becomes clear only at the 3 that we are in the wrong branch. The string _does_ match the expression, just not the branch we are currently in. +When matching this expression, the top (binary) branch will often be entered even though the input does not actually contain a binary number. When matching the string `"103"`, for example, it becomes clear only at the `3` that we are in the wrong branch. The string _does_ match the expression, just not the branch we are currently in. {{index backtracking, "search problem"}} -So the matcher _backtracks_. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string `"103"`, after encountering the 3 character, it will start trying the branch for hexadecimal numbers, which fails again because there is no _h_ after the number. So it tries the decimal number branch. This one fits, and a match is reported after all. +So the matcher _backtracks_. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string `"103"`, after encountering the `3` character, the matcher starts trying the branch for hexadecimal numbers, which fails again because there is no `h` after the number. It then tries the decimal number branch. This one fits, and a match is reported after all. {{index [matching, algorithm]}} The matcher stops as soon as it finds a full match. This means that if multiple branches could potentially match a string, only the first one (ordered by where the branches appear in the regular expression) is used. -Backtracking also happens for ((repetition)) operators like + and `*`. If you match `/^.*x/` against `"abcxe"`, the `.*` part will first try to consume the whole string. The engine will then realize that it needs an _x_ to match the pattern. Since there is no _x_ past the end of the string, the star operator tries to match one character less. But the matcher doesn't find an _x_ after `abcx` either, so it backtracks again, matching the star operator to just `abc`. _Now_ it finds an _x_ where it needs it and reports a successful match from positions 0 to 4. +Backtracking also happens for ((repetition)) operators like + and `*`. If you match `/^.*x/` against `"abcxe"`, the `.*` part will first try to consume the whole string. The engine will then realize that it needs an `x` to match the pattern. Since there is no `x` past the end of the string, the star operator tries to match one character less. But the matcher doesn't find an `x` after `abcx` either, so it backtracks again, matching the star operator to just `abc`. _Now_ it finds an `x` where it needs it and reports a successful match from positions 0 to 4. {{index performance, complexity}} @@ -536,15 +536,15 @@ console.log(stock.replace(/(\d+) (\p{L}+)/gu, minusOne)); // → no lemon, 1 cabbage, and 100 eggs ``` -This takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string that has one less of every such quantity. +This code takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string that has one less of every such quantity. -The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works since it matched `\d+`—and makes some adjustments in case there is only one or zero left. +The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works since it matched `\d+` earlier—and makes some adjustments in case there is only one or zero left. ## Greed {{index greed, "regular expression"}} -It is possible to use `replace` to write a function that removes all ((comment))s from a piece of JavaScript ((code)). Here is a first attempt: +We can use `replace` to write a function that removes all ((comment))s from a piece of JavaScript ((code)). Here is a first attempt: ```{test: wrap} function stripComments(code) { @@ -560,7 +560,7 @@ console.log(stripComments("1 /* a */+/* b */ 1")); {{index "period character", "slash character", "newline character", "empty set", "block comment", "line comment"}} -The part before the _or_ operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use `[^]` (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters. +The part before the `|` operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use `[^]` (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters. But the output for the last line appears to have gone wrong. Why? @@ -586,7 +586,7 @@ A lot of ((bug))s in ((regular expression)) programs can be traced to unintentio {{index ["regular expression", creation], "underscore character", "RegExp class"}} -There are cases where you might not know the exact ((pattern)) you need to match against when you are writing your code. Say you want to test for the user's name in a piece of text. You can build up a string and use the `RegExp` ((constructor)) on that. Here's an example: +In some cases you may not know the exact ((pattern)) you need to match against when you are writing your code. Say you want to test for the user's name in a piece of text. You can build up a string and use the `RegExp` ((constructor)) on that. ``` let name = "harry"; @@ -597,7 +597,7 @@ console.log(regexp.test("Harry is a dodgy character.")); {{index ["regular expression", flags], ["backslash character", "in regular expressions"]}} -When creating the `\s` part of the string, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the `RegExp` constructor contains the options for the regular expression—in this case, `"gi"` for global and case insensitive. +When creating the `\s` part of the string, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the `RegExp` constructor contains the options for the regular expression—in this case, `"gi"` for global and case-insensitive. But what if the name is `"dea+hl[]rd"` because our user is a ((nerd))y teenager? That would result in a nonsensical regular expression that won't actually match the user's name. @@ -619,7 +619,7 @@ console.log(regexp.test(text)); {{index ["regular expression", methods], "indexOf method", "search method"}} -The `indexOf` method on strings cannot be called with a regular expression. But there is another method, `search`, that does expect a regular expression. Like `indexOf`, it returns the first index on which the expression was found, or -1 when it wasn't found. +While the `indexOf` method on strings cannot be called with a regular expression, there is another method, `search`, that does expect a regular expression. Like `indexOf`, it returns the first index on which the expression was found, or -1 when it wasn't found. ``` console.log(" word".search(/\S/)); @@ -658,7 +658,7 @@ console.log(pattern.lastIndex); If the match was successful, the call to `exec` automatically updates the `lastIndex` property to point after the match. If no match was found, `lastIndex` is set back to zero, which is also the value it has in a newly constructed regular expression object. -The difference between the global and the sticky options is that, when sticky is enabled, the match will succeed only if it starts directly at `lastIndex`, whereas with global, it will search ahead for a position where a match can start. +The difference between the global and the sticky options is that when sticky is enabled, the match will succeed only if it starts directly at `lastIndex`, whereas with global, it will search ahead for a position where a match can start. ``` let global = /abc/g; @@ -671,7 +671,7 @@ console.log(sticky.exec("xyz abc")); {{index bug}} -When using a shared regular expression value for multiple `exec` calls, these automatic updates to the `lastIndex` property can cause problems. Your regular expression might be accidentally starting at an index that was left over from a previous call. +When using a shared regular expression value for multiple `exec` calls, these automatic updates to the `lastIndex` property can cause problems. Your regular expression might be accidentally starting at an index left over from a previous call. ``` let digit = /\d/g; @@ -690,9 +690,7 @@ console.log("Banana".match(/an/g)); // → ["an", "an"] ``` -So be cautious with global regular expressions. The cases where they are necessary—calls to `replace` and places where you want to explicitly use `lastIndex`—are typically the only places where you want to use them. - -### Getting all matches +So be cautious with global regular expressions. The cases where they are necessary—calls to `replace` and places where you want to explicitly use `lastIndex`—are typically the situations where you want to use them. {{index "lastIndex property", "exec method", loop}} @@ -711,7 +709,7 @@ for (let match of matches) { {{index ["regular expression", global]}} -This method returns an array of match arrays. The regular expression given it _must_ have `g` enabled. +This method returns an array of match arrays. The regular expression given to `matchAll` _must_ have `g` enabled. {{id ini}} ## Parsing an INI file @@ -739,7 +737,7 @@ outputdir=/home/marijn/enemies/davaeorn {{index grammar}} -The exact rules for this format (which is a widely used format, usually called an _INI_ file) are as follows: +The exact rules for this format—which is a widely used file format, usually called an _INI_ file—are as follows: - Blank lines and lines starting with semicolons are ignored. @@ -800,7 +798,7 @@ If a line is not a section header or a property, the function checks whether it ## Code units and characters -Another design mistake that's been standardized, in JavaScript regular expressions, is that by default, operator like `.` or `?` work on code units, as discussed in [Chapter ?](higher_order#code_units), not actual characters. This means characters that are composed of two code units behave strangely. +Another design mistake that's been standardized in JavaScript regular expressions is that by default, operators like `.` or `?` work on code units, as discussed in [Chapter ?](higher_order#code_units), not actual characters. This means characters that are composed of two code units behave strangely. ``` console.log(/🍎{3}/.test("🍎🍎🍎")); @@ -811,7 +809,7 @@ console.log(/<.>/u.test("<🌹>")); // → true ``` -The problem is that the 🍎 in the first line is treated as two code units, and the `{3}` part is applied only to the second one. Similarly, the dot matches a single code unit, not the two that make up the rose ((emoji)). +The problem is that the 🍎 in the first line is treated as two code units, and `{3}` is applied only to the second unit. Similarly, the dot matches a single code unit, not the two that make up the rose ((emoji)). You must add the `u` (Unicode) option to your regular expression to make it treat such characters properly. @@ -852,9 +850,9 @@ A regular expression has a method `test` to test whether a given string matches Strings have a `match` method to match them against a regular expression and a `search` method to search for one, returning only the starting position of the match. Their `replace` method can replace matches of a pattern with a replacement string or function. -Regular expressions can have options, which are written after the closing slash. The `i` option makes the match case insensitive. The `g` option makes the expression _global_, which, among other things, causes the `replace` method to replace all instances instead of just the first. The `y` option makes it sticky, which means that it will not search ahead and skip part of the string when looking for a match. The `u` option turns on Unicode mode, which enables `\p` syntax and fixes a number of problems around the handling of characters that take up two code units. +Regular expressions can have options, which are written after the closing slash. The `i` option makes the match case insensitive. The `g` option makes the expression _global_, which, among other things, causes the `replace` method to replace all instances instead of just the first. The `y` option makes and expression sticky, which means that it will not search ahead and skip part of the string when looking for a match. The `u` option turns on Unicode mode, which enables `\p` syntax and fixes a number of problems around the handling of characters that take up two code units. -Regular expressions are a sharp ((tool)) with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things that they cannot cleanly express into them. +Regular expressions are a sharp ((tool)) with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things into them that they cannot cleanly express. ## Exercises @@ -866,7 +864,7 @@ It is almost unavoidable that, in the course of working on these exercises, you {{index "program size", "code golf", "regexp golf (exercise)"}} -_Code golf_ is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, _regexp golf_ is the practice of writing as tiny a regular expression as possible to match a given pattern, and _only_ that pattern. +_Code golf_ is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, _regexp golf_ is the practice of writing as tiny a regular expression as possible to match a given pattern and _only_ that pattern. {{index boundary, matching}} @@ -964,7 +962,7 @@ hint}} {{index sign, "fractional number", [syntax, number], minus, "plus character", exponent, "scientific notation", "period character"}} -Write an expression that matches only JavaScript-style ((number))s. It must support an optional minus _or_ plus sign in front of the number, the decimal dot, and exponent notation—`5e-3` or `1E10`—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, `.5` and `5.` are valid JavaScript numbers, but a lone dot _isn't_. +Write an expression that matches only JavaScript-style ((number))s. It must support an optional minus _or_ plus sign in front of the number, the decimal dot, and exponent notation—`5e-3` or `1E10`—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, `.5` and `5.` are valid JavaScript numbers, but a lone dot isn't. {{if interactive ```{test: no} From 5741f22e91d34d278335acd28005307a06911b14 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 21 Mar 2024 10:34:22 +0100 Subject: [PATCH 307/392] Add missing semicolon in chapter 21 --- 21_skillsharing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/21_skillsharing.md b/21_skillsharing.md index 63d1d9d0c..c9f028638 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -324,7 +324,7 @@ If the data looks valid, the handler stores an object that represents the new ta To read the body from the request stream, we will use the `json` function from `"node:stream/consumers"`, which collects the data in the stream and then parses it as JSON. There are similar exports called `text` (to read the content as a string) and `buffer` (to read it as binary data) in this package. Since `json` is a very generic name, the import renames it to `readJSON` to avoid confusion. ```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} -import {json as readJSON} from "node:stream/consumers" +import {json as readJSON} from "node:stream/consumers"; router.add("PUT", talkPath, async (server, title, request) => { From 2ffc67b8edd9898beb828e53ff5ef8ee11f53e42 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 21 Mar 2024 10:34:54 +0100 Subject: [PATCH 308/392] Fix test for chapter 6 --- 06_object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_object.md b/06_object.md index 090384858..f1d9e228c 100644 --- a/06_object.md +++ b/06_object.md @@ -498,7 +498,7 @@ console.log(varyingSize.size); Whenever someone reads from this object's `size` property, the associated method is called. You can do a similar thing when a property is written to, using a _((setter))_. -```{test: no, startCode: true} +```{startCode: true, includeCode: "top_lines: 16"} class Temperature { constructor(celsius) { this.celsius = celsius; From cb9e3e815f61597ee4c539c8a71cdc051675e4a3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 21 Mar 2024 10:57:54 +0100 Subject: [PATCH 309/392] Integrate copyediting for chapter 10 --- 10_modules.md | 66 +++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/10_modules.md b/10_modules.md index b6ba82db3..db4e7c516 100644 --- a/10_modules.md +++ b/10_modules.md @@ -18,7 +18,7 @@ Ideally, a program has a clear, straightforward structure. The way it works is e {{index "organic growth"}} -In practice, programs grow organically. Pieces of functionality are added as the programmer identifies new needs. Keeping such a program well-structured requires constant attention and work. This is work that will pay off only in the future, the _next_ time someone works on the program. So it is tempting to neglect it and allow the various parts of the program to become deeply entangled. +In practice, programs grow organically. Pieces of functionality are added as the programmer identifies new needs. Keeping such a program well-structured requires constant attention and work. This is work that will pay off only in the future, the _next_ time someone works on the program, so it's tempting to neglect it and allow the various parts of the program to become deeply entangled. {{index readability, reuse, isolation}} @@ -38,9 +38,9 @@ Module interfaces have a lot in common with object interfaces, as we saw them in {{index dependency}} -But the interface that a module provides for others to use is only half the story. A good module system also requires modules to specify which code _they_ use from other modules. These relations are called _dependencies_. If module A uses functionality from module B, it is said to _depend_ on it. When these are clearly specified in the module itself, they can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies. +But the interface that a module provides for others to use is only half the story. A good module system also requires modules to specify which code _they_ use from other modules. These relations are called _dependencies_. If module A uses functionality from module B, it is said to _depend_ on that module. When these are clearly specified in the module itself, they can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies. -When the ways in which modules interact with each other are explicit, a system becomes more like ((LEGO)), where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything. +When the ways in which modules interact with each other are explicit, a system becomes more like ((LEGO)), where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything else. {{id es}} @@ -52,13 +52,13 @@ The original JavaScript language did not have any concept of a module. All scrip {{index "ES modules"}} -Since ECMAScript 2015, JavaScript supports two different types of programs. _Scripts_ behave in the old way: their bindings are defined in the global scope, and they have no way to directly reference other scripts. _Modules_ get their own separate scope, and support the `import` and `export` keywords, which aren't available in scripts, to declare their dependencies and interface. This module system is usually called _ES modules_ (where “ES” stands for “ECMAScript”). +Since ECMAScript 2015, JavaScript supports two different types of programs. _Scripts_ behave in the old way: their bindings are defined in the global scope, and they have no way to directly reference other scripts. _Modules_ get their own separate scope and support the `import` and `export` keywords, which aren't available in scripts, to declare their dependencies and interface. This module system is usually called _ES modules_ (where _ES_ stands for ECMAScript). A modular program is composed of a number of such modules, wired together via their imports and exports. {{index "Date class", "weekDay module"}} -This example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant which is not part of its interface, and two functions which are. It has no dependencies. +The following example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant which is not part of its interface, and two functions which are. It has no dependencies. ``` const names = ["Sunday", "Monday", "Tuesday", "Wednesday", @@ -87,11 +87,11 @@ The `import` keyword, followed by a list of binding names in braces, makes bindi {{index [module, resolution], resolution}} -How such a module name is resolved to an actual program differs by platform. The browser treats them as Web addresses, whereas Node.js resolves them to files. To run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. +How such a module name is resolved to an actual program differs by platform. The browser treats them as Web addresses, whereas Node.js resolves them to files. When you run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. -Import and export declarations cannot appear inside of functions, loops, or other blocks. They are immediately resolved when the module is loaded, regardless of how the code in the module executes, and to reflect this they must appear only in the outer module body. +Import and export declarations cannot appear inside of functions, loops, or other blocks. They are immediately resolved when the module is loaded, regardless of how the code in the module executes. To reflect this, they must appear only in the outer module body. -So a module's interface consists of a collection of named bindings, which other modules that depend on them have access to. Imported bindings can be renamed to give them a new local name using `as` after their name. +A module's interface thus consists of a collection of named bindings, which other modules that depend on the module can access. Imported bindings can be renamed to give them a new local name using `as` after their name. ``` import {dayName as nomDeJour} from "./dayname.js"; @@ -99,7 +99,7 @@ console.log(nomDeJour(3)); // → Wednesday ``` -It is also possible for a module to have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. +M module may also have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. ``` export default ["Winter", "Spring", "Summer", "Autumn"]; @@ -111,11 +111,19 @@ Such a binding is imported by omitting the braces around the name of the import. import seasonNames from "./seasonname.js"; ``` +To import all bindings from a module at the same time, you can use `import *`. You provide a name, and that name will be bound to an object holding all the module's exports. This can be useful when you are using a lot of different exports. + +``` +import * as dayName from "./dayname.js"; +console.log(dayName.dayName(3)); +// → Wednesday +``` + ## Packages {{index bug, dependency, structure, reuse}} -One of the advantages of building a program out of separate pieces, and being able to run some of those pieces on their own, is that you might be able to apply the same piece in different programs. +One of the advantages of building a program out of separate pieces and being able to run some of those pieces on their own is that you might be able to use the same piece in different programs. {{index "parseINI function"}} @@ -123,9 +131,7 @@ But how do you set this up? Say I want to use the `parseINI` function from [Chap {{index duplication, "copy-paste programming"}} -Once you start duplicating code, you'll quickly find yourself wasting time and energy moving copies around and keeping them up-to-date. - -That's where _((package))s_ come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn't write it might still be able to use it. +Once you start duplicating code, you'll quickly find yourself wasting time and energy moving copies around and keeping them up to date. That's where _((package))s_ come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn't write it might still be able to use it. When a problem is found in a package or a new feature is added, the package is updated. Now the programs that depend on it (which may also be packages) can copy the new ((version)) to get the improvements that were made to the code. @@ -135,7 +141,7 @@ When a problem is found in a package or a new feature is added, the package is u Working in this way requires ((infrastructure)). We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by ((NPM)) ([_https://npmjs.org_](https://npmjs.org)). -NPM is two things: an online service where you can download (and upload) packages and a program (bundled with Node.js) that helps you install and manage them. +NPM is two things: an online service where you can download (and upload) packages, and a program (bundled with Node.js) that helps you install and manage them. {{index "ini package"}} @@ -149,11 +155,11 @@ Having quality packages available for download is extremely valuable. It means t {{index maintenance}} -Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. But writing it in the first place _is_ work, and responding to people who have found problems in the code, or who want to propose new features, is even more work. +Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. Writing it in the first place _is_ work, though, and responding to people who have found problems in the code or who want to propose new features is even more work. By default, you own the ((copyright)) to the code you write, and other people may use it only with your permission. But because some people are just nice and because publishing good software can help make you a little bit famous among programmers, many packages are published under a ((license)) that explicitly allows other people to use it. -Most code on ((NPM)) is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, just requiring that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people's packages, make sure you are aware of their license. +Most code on ((NPM)) is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, requiring only that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people's packages, make sure you are aware of their licenses. {{id modules_ini}} @@ -172,14 +178,14 @@ console.log(parse("x = 10\ny = 20")); ## CommonJS modules -Before 2015, when the JavaScript language had no actual built-in module system, people were already building large systems in JavaScript. To make that workable, they _needed_ ((module))s. +Before 2015, when the JavaScript language had no built-in module system, people were already building large systems in JavaScript. To make that workable, they _needed_ ((module))s. {{index [function, scope], [interface, module], [object, as module]}} The community designed its own improvised ((module system))s on top of the language. These use functions to create a local scope for the modules and regular objects to represent module interfaces. Initially, people just manually wrapped their entire module in an “((immediately invoked function -expression))” to create the module's scope, and assigned their interface objects to a single global +expression))” to create the module's scope and assigned their interface objects to a single global variable. ``` @@ -202,7 +208,7 @@ This style of modules provides ((isolation)), to a certain degree, but it does n {{index "CommonJS modules"}} -If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used it from the start (though it now also knows how to load ES modules) and it is the module system used by many packages on ((NPM)). +If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used this module system from the start (though it now also knows how to load ES modules), and it is the module system used by many packages on ((NPM)). {{index "require function", [interface, module], "exports object"}} @@ -212,7 +218,7 @@ A CommonJS module looks like a regular script, but it has access to two bindings This CommonJS example module provides a date-formatting function. It uses two ((package))s from NPM—`ordinal` to convert numbers to strings like `"1st"` and `"2nd"`, and `date-names` to get the English names for weekdays and months. It exports a single function, `formatDate`, which takes a `Date` object and a ((template)) string. -The template string may contain codes that direct the format, such as `YYYY` for the full year and `Do` for the ordinal day of the month. You could give it a string like `"MMMM Do YYYY"` to get output like "November 22nd 2017". +The template string may contain codes that direct the format, such as `YYYY` for the full year and `Do` for the ordinal day of the month. You could give it a string like `"MMMM Do YYYY"` to get output like `November 22nd 2017`. ``` const ordinal = require("ordinal"); @@ -273,7 +279,7 @@ require.cache = Object.create(null); {{index [file, access]}} -Standard JavaScript provides no such function as `readFile`—but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that `readFile` exists. +Standard JavaScript provides no such function as `readFile`, but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that `readFile` exists. To avoid loading the same module multiple times, `require` keeps a store (cache) of already loaded modules. When called, it first checks if the requested module has been loaded and, if not, loads it. This involves reading the module's code, wrapping it in a function, and calling it. @@ -283,15 +289,13 @@ By defining `require`, `exports` as ((parameter))s for the generated wrapper fun An important difference between this system and ES modules is that ES module imports happen before a module's script starts running, whereas `require` is a normal function, invoked when the module is already running. Unlike `import` declarations, `require` calls _can_ appear inside functions, and the name of the dependency can be any expression that evaluates to a string, whereas `import` only allows plain quoted strings. -The transition of the JavaScript community from CommonJS style to ES modules has been a slow and somewhat rough one. But fortunately we are now at a point where most of the popular packages on NPM provide their code as ES modules, and Node.js allows ES modules to import from CommonJS modules. So while CommonJS code is still something you will run across, there is no real reason to write new programs in this style anymore. +The transition of the JavaScript community from CommonJS style to ES modules has been a slow and somewhat rough one. Fortunately we are now at a point where most of the popular packages on NPM provide their code as ES modules, and Node.js allows ES modules to import from CommonJS modules. While CommonJS code is still something you will run across, there is no real reason to write new programs in this style anymore. ## Building and bundling {{index compilation, "type checking"}} -Many JavaScript packages aren't, technically, written in JavaScript. There are extensions, such as TypeScript, the type checking ((dialect)) mentioned in [Chapter ?](error#typing), that are widely used. People also often start using planned extensions to the language long before they have been added to the platforms that actually run JavaScript. - -To make this possible, they _compile_ their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that ((browsers)) can run it. +Many JavaScript packages aren't technically written in JavaScript. Language extensions such as TypeScript, the type checking ((dialect)) mentioned in [Chapter ?](error#typing), are widely used. People also often start using planned new language features long before they have been added to the platforms that actually run JavaScript. To make this possible, they _compile_ their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that ((browsers)) can run it. {{index latency, performance, [file, access], [network, speed]}} @@ -303,7 +307,7 @@ And we can go further. Apart from the number of files, the _size_ of the files a {{index pipeline, tool}} -So it is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through _multiple_ stages of transformation—converted from modern JavaScript to historic JavaScript, then combining the modules into a single file, and minifying the code. We won't go into the details of these tools in this book since there are many of them, and which one is popular changes regularly. Just be aware that such things exist, and look them up when you need them. +It is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through _multiple_ stages of transformation—converting from modern JavaScript to historic JavaScript, combining the modules into a single file, and minifying the code. We won't go into the details of these tools in this book since there are many of them, and which one is popular changes regularly. Just be aware that such things exist, and look them up when you need them. ## Module design @@ -319,7 +323,7 @@ One aspect of module design is ease of use. If you are designing something that {{index "ini package", JSON}} -That may mean following existing conventions. A good example is the `ini` package. This module imitates the standard `JSON` object by providing `parse` and `stringify` (to write an INI file) functions, and, like `JSON`, converts between strings and plain objects. So the interface is small and familiar, and after you've worked with it once, you're likely to remember how to use it. +That may mean following existing conventions. A good example is the `ini` package. This module imitates the standard `JSON` object by providing `parse` and `stringify` (to write an INI file) functions, and, like `JSON`, converts between strings and plain objects. The interface is small and familiar, and after you've worked with it once, you're likely to remember how to use it. {{index "side effect", "hard disk", composability}} @@ -333,7 +337,7 @@ This points to another helpful aspect of module design—the ease with which som Relatedly, stateful objects are sometimes useful or even necessary, but if something can be done with a function, use a function. Several of the INI file readers on NPM provide an interface style that requires you to first create an object, then load the file into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it's terrible. Instead of making a single function call and moving on, you have to perform the ritual of moving your object through its various states. And because the data is now wrapped in a specialized object type, all code that interacts with it has to know about that type, creating unnecessary interdependencies. -Often defining new data structures can't be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array. +Often, defining new data structures can't be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array. An example of a slightly more complex data structure is the graph from [Chapter ?](robot). There is no single obvious way to represent a ((graph)) in JavaScript. In that chapter, we used an object whose properties hold arrays of strings—the other nodes reachable from that node. @@ -343,7 +347,7 @@ There are several different pathfinding packages on ((NPM)), but none of them us For example, there's the `dijkstrajs` package. A well-known approach to pathfinding, quite similar to our `findRoute` function, is called _Dijkstra's algorithm_, after Edsger Dijkstra, who first wrote it down. The `js` suffix is often added to package names to indicate the fact that they are written in JavaScript. This `dijkstrajs` package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges. -So if we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn). +If we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn). ``` const {find_path} = require("dijkstrajs"); @@ -368,7 +372,7 @@ Designing a fitting module structure for a program can be difficult. In the phas ## Summary -Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that's visible to other modules, and the dependencies are the other modules that it makes use of. +Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that's visible to other modules, and the dependencies are the other modules it makes use of. Because JavaScript historically did not provide a module system, the CommonJS system was built on top of it. Then at some point it _did_ get a built-in system, which now coexists uneasily with the CommonJS system. @@ -434,7 +438,7 @@ hint}} {{index "roads module (exercise)"}} -Write an ES module, based on the example from [Chapter ?](robot), that contains the array of roads and exports the graph data structure representing them as `roadGraph`. It should depend on a module `./graph.js`, which exports a function `buildGraph` that is used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads). +Write an ES module based on the example from [Chapter ?](robot) that contains the array of roads and exports the graph data structure representing them as `roadGraph`. It depends on a module `./graph.js` that exports a function `buildGraph`, used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads). {{if interactive From 4142888858229db4d3b409eed8336d565748213e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 29 Mar 2024 23:01:01 +0100 Subject: [PATCH 310/392] Integrate copyediting for chapter 11 --- 11_async.md | 92 ++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/11_async.md b/11_async.md index b4da70d69..cf18415c0 100644 --- a/11_async.md +++ b/11_async.md @@ -31,7 +31,7 @@ In a _synchronous_ programming model, things happen one at a time. When you call An _asynchronous_ model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk). -We can compare synchronous and asynchronous programming using a small example: a program that makes two requests over the ((network)) and then combines the result. +We can compare synchronous and asynchronous programming using a small example: a program that makes two requests over the ((network)) and then combines the results. {{index "synchronous programming"}} @@ -49,7 +49,7 @@ In the following diagram, the thick lines represent time the program spends runn {{index ["control flow", asynchronous], "asynchronous programming", verbosity, performance}} -Another way to describe the difference is that waiting for actions to finish is _implicit_ in the synchronous model, while it is _explicit_, under our control, in the asynchronous one. +Another way to describe the difference is that waiting for actions to finish is _implicit_ in the synchronous model, while it is _explicit_—under our control—in the asynchronous one. Asynchronicity cuts both ways. It makes expressing programs that do not fit the straight-line model of control easier, but it can also make expressing programs that do follow a straight line more awkward. We'll see some ways to reduce this awkwardness later in the chapter. @@ -59,21 +59,21 @@ Both prominent JavaScript programming platforms—((browser))s and ((Node.js)) {{indexsee [function, callback], "callback function"}} -One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. The asynchronous a function starts some process, sets things up so that the callback function is called when the process finishes, and then returns. +One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. The asynchronous function starts a process, sets things up so that the callback function is called when the process finishes, and then returns. {{index "setTimeout function", waiting}} -As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function. +As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds, then calls a function. ```{test: no} setTimeout(() => console.log("Tick"), 500); ``` -Waiting is not generally a very important type of work, but it can be very useful when you need to arrange for something to happen at a certain time or check whether some other action is taking longer than expected. +Waiting is not generally important work, but it can be very useful when you need to arrange for something to happen at a certain time or check whether some action is taking longer than expected. {{index "readTextFile function"}} -Another example of a common asynchronous operation is reading a file from a device's storage. Imagine you have a function `readTextFile`, which reads a file's content as a string and passes it to a callback function. +Another example of a common asynchronous operation is reading a file from a device's storage. Imagine you have a function `readTextFile` that reads a file's content as a string and passes it to a callback function. ``` readTextFile("shopping_list.txt", content => { @@ -86,7 +86,7 @@ readTextFile("shopping_list.txt", content => { The `readTextFile` function is not part of standard JavaScript. We will see how to read files in the browser and in Node.js in later chapters. -Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the ((continuation)) of the computation after the actions. This is what an asynchronous function that compares two files and produces a boolean indicating whether their content is the same might look like. +Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the ((continuation)) of the computation after the actions. An asynchronous function that compares two files and produces a boolean indicating whether their content is the same might look like this: ``` function compareFiles(fileA, fileB, callback) { @@ -108,11 +108,11 @@ A slightly different way to build an asynchronous program is to have asynchronou {{index "Promise class", "asynchronous programming", "resolving (a promise)", "then method", "callback function"}} -This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provides a `then` method that allows you to register a function that should be called when the action it is waiting for finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible to call `then` on a promise that has already resolved—your function will still be called. +This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provides a `then` method that allows you to register a function that should be called when the action for which it is waiting finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible to call `then` on a promise that has already resolved—your function will still be called. {{index "Promise.resolve function"}} -The easiest way to create a promise is by calling `Promise.resolve`. This function ensures that the value you give it is wrapped in a promise. If it's already a promise, it is simply returned—otherwise, you get a new promise that immediately resolves with your value as its result. +The easiest way to create a promise is by calling `Promise.resolve`. This function ensures that the value you give it is wrapped in a promise. If it's already a promise, it is simply returned. Otherwise, you get a new promise that immediately resolves with your value as its result. ``` let fifteen = Promise.resolve(15); @@ -122,9 +122,9 @@ fifteen.then(value => console.log(`Got ${value}`)); {{index "Promise class"}} -To create a promise that does not immediately resolve, you can use `Promise` as a constructor. It has a somewhat odd interface—the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. +To create a promise that does not immediately resolve, you can use `Promise` as a constructor. It has a somewhat odd interface: the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. -This is how you could create a promise-based interface for the `readTextFile` function: +For example, this is how you could create a promise-based interface for the `readTextFile` function: {{index "textFile function"}} @@ -138,13 +138,13 @@ function textFile(filename) { textFile("plans.txt").then(console.log); ``` -Note how this asynchronous function returns a meaningful value—a promise to give you the content of the file at some point in the future. +Note how, in contrast to callback-style functions, this asynchronous function returns a meaningful value—a promise to give you the contents of the file at some point in the future. {{index "then method"}} -A useful thing about the `then` method is that it itself returns another promise that resolves to the value returned by the callback function or, if that function returns a promise, to the value that promise resolves to. Thus, you can “chain” multiple calls to `then` together to set up a sequence of asynchronous actions. +A useful thing about the `then` method is that it itself returns another promise. This one resolves to the value returned by the callback function or, if that returned value is a promise, to the value that promise resolves to. Thus, you can “chain” multiple calls to `then` together to set up a sequence of asynchronous actions. -This function, which reads a file full of filenames, and returns the content of a random file in that list, shows this kind of asynchronous promise pipeline. +This function, which reads a file full of filenames, and returns the content of a random file in that list, shows this kind of asynchronous promise pipeline: ``` function randomFile(listFile) { @@ -157,9 +157,9 @@ function randomFile(listFile) { The function returns the result of this chain of `then` calls. The initial promise fetches the list of files as a string. The first `then` call transforms that string into an array of lines, producing a new promise. The second `then` call picks a random line from that, producing a third promise that yields a single filename. The final `then` call reads this file, so that the result of the function as a whole is a promise that returns the content of a random file. -In this code, the functions used in the first two `then` calls return a regular value, which will immediately be passed into the promise returned by `then` when the function returns. The last one returns a promise (`textFile(filename)`), making it an actual asynchronous step. +In this code, the functions used in the first two `then` calls return a regular value that will immediately be passed into the promise returned by `then` when the function returns. The last `then` call returns a promise (`textFile(filename)`), making it an actual asynchronous step. -It would have also been possible to do all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, for example when you want to return a promise that produces a processed version of some asynchronous result. +It would also have been possible to perform all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, such as when you want to return a promise that produces a processed version of some asynchronous result. ``` function jsonFile(filename) { @@ -169,7 +169,7 @@ function jsonFile(filename) { jsonFile("package.json").then(console.log); ``` -Generally, it is useful to think of promises as a device that lets code ignore the question of when a value is going to arrive. A normal value has to actually exist before we can reference it. A promised value is a value that _might_ already be there or might appear at some point in the future. Computations defined in terms of promises, by wiring them together with `then` calls, are executed asynchronously as their inputs become available. +Generally, it is useful to think of a promise as a device that lets code ignore the question of when a value is going to arrive. A normal value has to actually exist before we can reference it. A promised value is a value that _might_ already be there or might appear at some point in the future. Computations defined in terms of promises, by wiring them together with `then` calls, are executed asynchronously as their inputs become available. ## Failure @@ -179,9 +179,9 @@ Regular JavaScript computations can fail by throwing an exception. Asynchronous {{index "callback function", error}} -One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to make sure failures are properly reported to the callbacks. +One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to ensure failures are properly reported to the callbacks. -A widely used convention is that the first argument to the callback is used to indicate that the action failed, and the second contains the value produced by the action when it was successful. +A common convention is to use the first argument to the callback to indicate that the action failed, and the second to pass the value produced by the action when it was successful. ``` someAsyncFunction((error, value) => { @@ -194,11 +194,11 @@ Such callback functions must always check whether they received an exception and {{index "rejecting (a promise)", "resolving (a promise)", "then method"}} -Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with `then`) are called only when the action is successful, and rejections are propagated to the new promise that is returned by `then`. When a handler throws an exception, this automatically causes the promise produced by its `then` call to be rejected. So if any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed. +Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with `then`) are called only when the action is successful, and rejections are propagated to the new promise returned by `then`. When a handler throws an exception, this automatically causes the promise produced by its `then` call to be rejected. If any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed. {{index "Promise.reject function", "Promise class"}} -Much like resolving a promise provides a value, rejecting one also provides one, usually called the _reason_ of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There's a `Promise.reject` function that creates a new, immediately rejected promise. +Much like resolving a promise provides a value, rejecting one also provides a value, usually called the _reason_ of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There's a `Promise.reject` function that creates a new, immediately rejected promise. {{index "catch method"}} @@ -206,13 +206,13 @@ To explicitly handle such rejections, promises have a `catch` method that regist {{index "then method"}} -As a shorthand, `then` also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call. +As a shorthand, `then` also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call: `.then(acceptHandler, rejectHandler)`. A function passed to the `Promise` constructor receives a second argument, alongside the resolve function, which it can use to reject the new promise. {{index "textFile function"}} -When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually look at that argument, so that a failure causes the promise it returns to be rejected. +When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually check that argument, so that a failure causes the promise it returns to be rejected. ```{includeCode: true} function textFile(filename) { @@ -225,7 +225,7 @@ function textFile(filename) { } ``` -The chains of promise values created by calls to `then` and `catch` thus form a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don't match the type of outcome (success or failure) are ignored. But those that do match are called, and their outcome determines what kind of value comes next—success when it returns a non-promise value, rejection when it throws an exception, and the outcome of the promise when it returns a promise. +The chains of promise values created by calls to `then` and `catch` thus form a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don't match the type of outcome (success or failure) are ignored. Handlers that do match are called, and their outcome determines what kind of value comes next—success when they return a non-promise value, rejection when they throw an exception, and the outcome of the promise when they return a promise. ```{test: no} new Promise((_, reject) => reject(new Error("Fail"))) @@ -239,7 +239,7 @@ new Promise((_, reject) => reject(new Error("Fail"))) // → Handler 2: nothing ``` -The first regular handler function isn't called, because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second handler function. +The first `then` handler function isn't called, because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second `then` handler function. {{index "uncaught exception", "exception handling"}} @@ -251,15 +251,15 @@ Much like an uncaught exception is handled by the environment, JavaScript enviro It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. -One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting the people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. +One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight towards the roof of the hangar building, disappearing into an air vent. -Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snacks, half a dozen smart phones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. +Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snacks, half a dozen smartphones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. This crow is known to her peers as “cāāw-krö”. But since those sounds are poorly suited for human vocal chords, we'll refer to her as Carla. -Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she had a good grasp of what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of JavaScript code. +Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she had a good grasp of what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of asynchronous JavaScript code. ## Breaking In @@ -267,11 +267,11 @@ Carla is a somewhat peculiar crow. In her youth, she was fascinated by human lan Carla loves the Internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. -Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device has to send along the correct 6-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending only a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not—when sending incorrect number, you immediately get a failure message. When sending the correct ones, the access point waits for more digits. +Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device must send along the correct six-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not. Sending incorrect numbers immediately returns a failure message. When sending the correct ones, the access point waits for more digits. -This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find second digit in the same way, and so on, until she knows the entire passcode. +This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find the second digit in the same way, and so on, until she knows the entire passcode. -Assume we have a `joinWifi` function. Given the network name and the passcode (as a string), it tries to join the network, returning a promise that resolves if successful, and rejects if the authentication failed. The first thing we need is to a way to wrap a promise so that it automatically rejects after it takes too much time, so that we can quickly move on if the access point doesn't respond. +Assume Carla has a `joinWifi` function. Given the network name and the passcode (as a string), the function tries to join the network, returning a promise that resolves if successful and rejects if the authentication failed. The first thing she needs is a way to wrap a promise so that it automatically rejects after it takes too much time, to allow the program to quickly move on if the access point doesn't respond. ```{includeCode: true} function withTimeout(promise, time) { @@ -282,11 +282,11 @@ function withTimeout(promise, time) { } ``` -This makes use of the fact that a promise can only be resolved or rejected once—if the promise given as argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored. +This makes use of the fact that a promise can only be resolved or rejected once. If the promise given as argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored. -To find the whole passcode, we need to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong, and must try the next digit. If the request times out, we have found another correct digit, and must continue by adding another digit. +To find the whole passcode, the program needs to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong, and must try the next digit. If the request times out, we have found another correct digit, and must continue by adding another digit. -Because you cannot wait for a promise inside a `for` loop, Carla uses a recursive function to drive this process. On each call, it gets the code as we know it so far, as well as the next digit to try. Depending on what happens, it may return a finished code, or call through to itself, to either start cracking the next position in the code, or to try again with another digit. +Because you cannot wait for a promise inside a `for` loop, Carla uses a recursive function to drive this process. On each call, this function gets the code as we know it so far, as well as the next digit to try. Depending on what happens, it may return a finished code or call through to itself, to either start cracking the next position in the code or to try again with another digit. ```{includeCode: true} function crackPasscode(networkID) { @@ -321,7 +321,7 @@ Carla tilts her head and sighs. This would have been more satisfying if the code {{index "Promise class", recursion}} -Even with promises, this kind of asynchronous code is annoying to write. Promises often need to be tied together in verbose, arbitrary-looking ways. And we were forced to introduce a recursive function to just to create a loop. +Even with promises, this kind of asynchronous code is annoying to write. Promises often need to be tied together in verbose, arbitrary-looking ways. To create an asynchronous loop, Carla was forced to introduce a recursive function. {{index "synchronous programming", "asynchronous programming"}} @@ -329,7 +329,7 @@ The thing the cracking function actually does is completely linear—it always w {{index "async function", "await keyword"}} -The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An `async` function is a function that implicitly returns a promise and that can, in its body, `await` other promises in a way that _looks_ synchronous. +The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An `async` function implicitly returns a promise and can, in its body, `await` other promises in a way that _looks_ synchronous. {{index "findInStorage function"}} @@ -366,9 +366,9 @@ An `async` function is marked by the word `async` before the `function` keyword. Inside an `async` function, the word `await` can be put in front of an expression to wait for a promise to resolve and only then continue the execution of the function. If the promise rejects, an exception is raised at the point of the `await`. -Such a function no longer, like a regular JavaScript function, runs from start to completion in one go. Instead, it can be _frozen_ at any point that has an `await`, and can be resumed at a later time. +Such a function no longer runs from start to completion in one go like a regular JavaScript function. Instead, it can be _frozen_ at any point that has an `await` and can be resumed at a later time. -For most asynchronous code, this notation is more convenient than directly using promises. You do still need an understanding of promises, since in many cases you still interact with them directly. But when wiring them together, `async` functions are generally more pleasant to write than chains of `then` calls. +For most asynchronous code, this notation is more convenient than directly using promises. You do still need an understanding of promises, since in many cases you'll still interact with them directly. But when wiring them together, `async` functions are generally more pleasant to write than chains of `then` calls. {{id generator}} @@ -433,7 +433,7 @@ An `async` function is a special type of generator. It produces a promise when c {{index "Carla the crow"}} -This morning, Carla woke up to unfamiliar noise from the tarmac outside of her hangar. Hopping onto the edge of the roof, she sees the humans are setting up for something. There's a lot of electric cabling, a stage, and some kind of big black wall being built up. +One morning, Carla wakes up to unfamiliar noise from the tarmac outside of her hangar. Hopping onto the edge of the roof, she sees the humans are setting up for something. There's a lot of electric cabling, a stage, and some kind of big black wall being built up. Being a curious crow, Carla takes a closer look at the wall. It appears to consist of a number of large glass-fronted devices wired up to cables. On the back, the devices say “LedTec SIG-5030”. @@ -474,7 +474,7 @@ const screenAddresses = [ Now this opens up possibilities for all kinds of shenanigans. She could show “crows rule, humans drool” on the wall in giant letters. But that feels a bit crude. Instead, she plans to show a video of a flying crow covering all of the screens at night. -Carla finds a fitting video clip, in which a second and a half of footage can be repeated to create a looping video showing a crow's wingbeat. To fit the nine screens (each of which can show 50 by 30 pixels), Carla cuts and resizes the videos to get a series of 150-by-90 images, ten per second. Those are then each cut into nine rectangles, and processed so that the dark spots on the video (where the crow is) show a bright light, and the light spots (no crow) are left dark, which should create the effect of an amber crow flying against a black background. +Carla finds a fitting video clip, in which a second and a half of footage can be repeated to create a looping video showing a crow's wingbeat. To fit the nine screens (each of which can show 50×30 pixels), Carla cuts and resizes the videos to get a series of 150×90 images, ten per second. Those are then each cut into nine rectangles, and processed so that the dark spots on the video (where the crow is) show a bright light, and the light spots (no crow) are left dark, which should create the effect of an amber crow flying against a black background. She has set up the `clipImages` variable to hold an array of frames, where each frame is represented with an array of nine sets of pixels—one for each screen—in the format that the signs expect. @@ -623,7 +623,7 @@ The `async fileName =>` part shows how ((arrow function))s can also be made `asy The code doesn't immediately look suspicious... it maps the `async` arrow function over the array of names, creating an array of promises, and then uses `Promise.all` to wait for all of these before returning the list they build up. -But it is entirely broken. It'll always return only a single line of output, listing the file that took the longest to read. +But this program is entirely broken. It'll always return only a single line of output, listing the file that took the longest to read. {{if interactive @@ -674,11 +674,11 @@ Programming asynchronously is made easier by promises, objects that represent ac {{index "quiet times (exercise)", "security camera", "Carla the crow", "async function"}} -There's a security camera near Carla's lab that's activated by a motion sensor. It is connected to the network and starts sending out a video stream when it is active. Because she'd prefer not to be discovered, Carla has set up a system that notices this kind of wireless network traffic and turns on a light in her lair whenever there is activity outside, so that she knows when to keep quiet. +There's a security camera near Carla's lab that's activated by a motion sensor. It is connected to the network and starts sending out a video stream when it is active. Because she'd rather not be discovered, Carla has set up a system that notices this kind of wireless network traffic and turns on a light in her lair whenever there is activity outside, so she knows when to keep quiet. {{index "Date class", "Date.now function", timestamp}} -She's also been logging the times at which the camera is tripped for a while, and wants to use this information to visualize which times, in an average week, tend to be quiet, and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line. +She's also been logging the times at which the camera is tripped for a while and wants to use this information to visualize which times, in an average week, tend to be quiet, and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line. ```{lang: null} 1695709940692 @@ -686,13 +686,13 @@ She's also been logging the times at which the camera is tripped for a while, an 1695701189163 ``` -The `"camera_logs.txt"` file holds a list of log files. Write an asynchronous function `activityTable(day)` that for a given day of the week returns an array of 24 numbers, one for each hour of the day, that hold the amount of camera network traffic observations seen in that hour of the day. Days are identified by number using the system used by `Date.getDay`, where Sunday is 0 and Saturday is 6. +The `"camera_logs.txt"` file holds a list of log files. Write an asynchronous function `activityTable(day)` that for a given day of the week returns an array of 24 numbers, one for each hour of the day, that hold the number of camera network traffic observations seen in that hour of the day. Days are identified by number using the system used by `Date.getDay`, where Sunday is 0 and Saturday is 6. The `activityGraph` function, provided by the sandbox, summarizes such a table into a string. {{index "textFile function"}} -Use the `textFile` function defined earlier—given a filename, it returns a promise that resolves to the file's content. Remember that `new Date(timestamp)` creates a `Date` object for that time, which has `getDay` and `getHours` methods returning the day of the week and the hour of the day. +To read the files, use the `textFile` function defined earlier—given a filename, it returns a promise that resolves to the file's content. Remember that `new Date(timestamp)` creates a `Date` object for that time, which has `getDay` and `getHours` methods returning the day of the week and the hour of the day. Both types of files—the list of log files and the log files themselves—have each piece of data on its own line, separated by newline (`"\n"`) characters. @@ -801,7 +801,7 @@ hint}} {{index "Promise class", "Promise.all function", "building Promise.all (exercise)"}} -As we saw, given an array of ((promise))s, `Promise.all` returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by `all` fails too, with the failure reason from the failing promise. +As we saw, given an array of ((promise))s, `Promise.all` returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by `all` fails too, passing on the failure reason from the failing promise. Implement something like this yourself as a regular function called `Promise_all`. From 63ef9fe736393ac4559cf0b864d098ccde4faa47 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 4 Apr 2024 15:09:56 +0200 Subject: [PATCH 311/392] Integrate copyediting for chapter 12 --- 12_language.md | 52 ++++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/12_language.md b/12_language.md index 7723036fa..a49935394 100644 --- a/12_language.md +++ b/12_language.md @@ -14,7 +14,7 @@ quote}} Building your own ((programming language)) is surprisingly easy (as long as you do not aim too high) and very enlightening. -The main thing I want to show in this chapter is that there is no ((magic)) involved in building a programming language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane. +The main thing I want to show in this chapter is that there's no ((magic)) involved in building a programming language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane. {{index "Egg language", [abstraction, "in Egg"]}} @@ -49,7 +49,7 @@ do(define(x, 10), {{index block, [syntax, "of Egg"]}} -The ((uniformity)) of the ((Egg language)) means that things that are ((operator))s in JavaScript (such as `>`) are normal bindings in this language, applied just like other ((function))s. And since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence. +The ((uniformity)) of the ((Egg language)) means that things that are ((operator))s in JavaScript (such as `>`) are normal bindings in this language, applied just like other ((function))s. Since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence. {{index "type property", parsing, ["data structure", tree]}} @@ -74,7 +74,7 @@ The `>(x, 5)` part of the previous program would be represented like this: {{indexsee "abstract syntax tree", "syntax tree", ["data structure", tree]}} -Such a data structure is called a _((syntax tree))_. If you imagine the objects as dots and the links between them as lines between those dots, it has a ((tree))like shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again. +Such a data structure is called a _((syntax tree))_. If you imagine the objects as dots and the links between them as lines between those dots, as shown in the following diagram, the structure has a ((tree))like shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again. {{figure {url: "img/syntax_tree.svg", alt: "A diagram showing the structure of the syntax tree for the example program. The root is labeled 'do' and has two children, one labeled 'define' and one labeled 'if'. Those in turn have more children, describing their content.", width: "5cm"}}} @@ -92,7 +92,7 @@ Fortunately, this problem can be solved very well by writing a parser function t {{index "parseExpression function", "syntax tree"}} -We define a function `parseExpression`, which takes a string as input and returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments. +We define a function `parseExpression` that takes a string as input. It returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments. This is the first part of the parser: @@ -122,11 +122,11 @@ function skipSpace(string) { {{index "skipSpace function", [whitespace, syntax]}} -Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. That is what the `skipSpace` function helps with. +Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. The `skipSpace` function helps with this. {{index "literal expression", "SyntaxError type"}} -After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use the `SyntaxError` constructor here. This is an exception class defined by the standard, like `Error`, but more specific. +After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which expression matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use the `SyntaxError` constructor here. This is an exception class defined by the standard, like `Error`, but more specific. {{index "parseApply function"}} @@ -155,13 +155,9 @@ function parseApply(expr, program) { } ``` -{{index parsing}} - -If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` returns the expression it was given. +{{index parsing, recursion}} -{{index recursion}} - -Otherwise, it skips the opening parenthesis and creates the ((syntax tree)) object for this application expression. It then recursively calls `parseExpression` to parse each argument until a closing parenthesis is found. The recursion is indirect, through `parseApply` and `parseExpression` calling each other. +If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` returns the expression it was given. Otherwise, it skips the opening parenthesis and creates the ((syntax tree)) object for this application expression. It then recursively calls `parseExpression` to parse each argument until a closing parenthesis is found. The recursion is indirect, through `parseApply` and `parseExpression` calling each other. Because an application expression can itself be applied (such as in `multiplier(2)(1)`), `parseApply` must, after it has parsed an application, call itself again to check whether another pair of parentheses follows. @@ -227,21 +223,21 @@ function evaluate(expr, scope) { {{index "literal expression", scope}} -The evaluator has code for each of the ((expression)) types. A literal value expression produces its value. (For example, the expression `100` just evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding's value. +The evaluator has code for each of the ((expression)) types. A literal value expression produces its value. (For example, the expression `100` evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding's value. {{index [function, application]}} -Applications are more involved. If they are a ((special form)), like `if`, we do not evaluate anything and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments. +Applications are more involved. If they are a ((special form)), like `if`, we do not evaluate anything—we just and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments. -We use plain JavaScript function values to represent Egg's function values. We will come back to this [later](language#egg_fun), when the special form called `fun` is defined. +We use plain JavaScript function values to represent Egg's function values. We will come back to this [later](language#egg_fun), when the special form `fun` is defined. {{index readability, "evaluate function", recursion, parsing}} -The recursive structure of `evaluate` resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to combine the parser and the evaluator into one function, and evaluate during parsing. But splitting them up this way makes the program clearer and more flexible. +The recursive structure of `evaluate` resembles the structure of the parser, and both mirror the structure of the language itself. It would also be possible to combine the parser and the evaluator into one function and evaluate during parsing, but splitting them up this way makes the program clearer and more flexible. {{index "Egg language", interpretation}} -This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the ((environment)), you can't do much with this language yet. +This is really all that's needed to interpret Egg. It's that simple. But without defining a few special forms and adding some useful values to the ((environment)), you can't do much with this language yet. ## Special forms @@ -267,11 +263,11 @@ Egg's `if` construct expects exactly three arguments. It will evaluate the first {{index Boolean}} -Egg also differs from JavaScript in how it handles the condition value to `if`. It will not treat things like zero or the empty string as false, only the precise value `false`. +Egg also differs from JavaScript in how it handles the condition value to `if`. It will treat only the value `false` as false, not things like zero or the empty string. {{index "short-circuit evaluation"}} -The reason we need to represent `if` as a special form, rather than a regular function, is that all arguments to functions are evaluated before the function is called, whereas `if` should evaluate only _either_ its second or its third argument, depending on the value of the first. +The reason we need to represent `if` as a special form rather than a regular function is that all arguments to functions are evaluated before the function is called, whereas `if` should evaluate only _either_ its second or its third argument, depending on the value of the first. The `while` form is similar. @@ -342,7 +338,7 @@ console.log(evaluate(prog, topScope)); {{index arithmetic, "Function constructor"}} -To supply basic ((arithmetic)) and ((comparison)) ((operator))s, we will also add some function values to the ((scope)). In the interest of keeping the code short, we'll use `Function` to synthesize a bunch of operator functions in a loop, instead of defining them individually. +To supply basic ((arithmetic)) and ((comparison)) ((operator))s, we will also add some function values to the ((scope)). In the interest of keeping the code short, we'll use `Function` to synthesize a bunch of operator functions in a loop instead of defining them individually. ```{includeCode: true} for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { @@ -350,7 +346,7 @@ for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { } ``` -A way to ((output)) values is also useful, so we'll wrap `console.log` in a function and call it `print`. +It is also useful to have a way to ((output)) values, so we'll wrap `console.log` in a function and call it `print`. ```{includeCode: true} topScope.print = value => { @@ -387,7 +383,7 @@ do(define(total, 0), {{index "summing example", "Egg language"}} -This is the program we've seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 ((lines of code)). +This is the program we've seen several times before that computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 ((lines of code)). {{id egg_fun}} @@ -395,9 +391,7 @@ This is the program we've seen several times before, which computes the sum of t {{index function, "Egg language"}} -A programming language without functions is a poor programming language indeed. - -Fortunately, it isn't hard to add a `fun` construct, which treats its last argument as the function's body and uses all arguments before that as the names of the function's parameters. +A programming language without functions is a poor programming language indeed. Fortunately, it isn't hard to add a `fun` construct, which treats its last argument as the function's body and uses all arguments before that as the names of the function's parameters. ```{includeCode: true} specialForms.fun = (args, scope) => { @@ -460,7 +454,7 @@ Traditionally, ((compilation)) involves converting the program to ((machine code {{index simplicity, "Function constructor", transpilation}} -It would be possible to write an alternative ((evaluation)) strategy for Egg, one that first converts the program to a JavaScript program, uses `Function` to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement. +It would be possible to write an alternative ((evaluation)) strategy for Egg, one that first converts the program to a JavaScript program, uses `Function` to invoke the JavaScript compiler on it, and runs the result. When done right, this would make Egg run very fast while still being quite simple to implement. If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise. @@ -468,7 +462,7 @@ If you are interested in this topic and willing to spend some time on it, I enco {{index "Egg language"}} -When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. Bridging the gap to a more primitive system, such as the machine code the processor understands, is more effort—but the way it works resembles what we are doing here. +When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. Bridging the gap to a more primitive system, such as the machine code the processor understands, takes more effort—but the way it works resembles what we are doing here. Though the toy language in this chapter doesn't do anything that couldn't be done better in JavaScript, there _are_ situations where writing small languages helps get real work done. @@ -492,7 +486,7 @@ application = expr '(' (expr (',' expr)*)? ')' {{index expressivity}} -This is what is usually called a _((domain-specific language))_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain, and nothing else. +This is what is usually called a _((domain-specific language))_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain and nothing else. ## Exercises @@ -500,7 +494,7 @@ This is what is usually called a _((domain-specific language))_, a language tail {{index "Egg language", "arrays in egg (exercise)", [array, "in Egg"]}} -Add support for arrays to Egg by adding the following three functions to the top scope: `array(...values)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the n^th^ element from an array. +Add support for arrays to Egg by adding the following three functions to the top scope: `array(...values)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the *n*th element from an array. {{if interactive From b89e1c35f61b166d40f65d204e47e34304ad9c53 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 4 Apr 2024 15:55:38 +0200 Subject: [PATCH 312/392] Integrate copyediting for chapter 13 --- 13_browser.md | 54 ++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/13_browser.md b/13_browser.md index 16b402d5b..aeb8e1158 100644 --- a/13_browser.md +++ b/13_browser.md @@ -10,19 +10,19 @@ quote}} {{figure {url: "img/chapter_picture_13.jpg", alt: "Illustration showing a telephone switchboard", chapter: "framed"}}} -The next chapters of this book will talk about web browsers. Without web ((browser))s, there would be no JavaScript. Or even if there were, no one would ever have paid any attention to it. +The next chapters of this book will discuss web browsers. Without ((browser))s, there would be no JavaScript—or if there were, no one would ever have paid any attention to it. {{index decentralization, compatibility}} -Web technology has been decentralized from the start, not just technically but also in the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which then, sometimes, ended up being adopted by others—and finally set down as in ((standards)). +Web technology has been decentralized from the start, not just technically but also in terms of the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which were then—sometimes—adopted by others—and finally set down as in ((standards)). -This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. +This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally, open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. ## Networks and the Internet Computer ((network))s have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things. -And if connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((Internet))_. It has lived up to its promise. +If connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((Internet))_. It has lived up to its promise. A computer can use this network to shoot bits at another computer. For any effective ((communication)) to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the ((encoding)) mechanism used. @@ -38,11 +38,11 @@ The _Hypertext Transfer Protocol_ (((HTTP))) is a protocol for retrieving named GET /index.html HTTP/1.1 ``` -There are a lot more rules about the way the requester can include more information in the ((request)) and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter ?](http). +There are many more rules about the way the requester can include more information in the ((request)) and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter ?](http). {{index layering, stream, ordering}} -Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Providing those guarantees that on top of the primitive data-sending that the network gives you is already a rather tricky problem. +Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Providing those guarantees on top of the primitive data-sending that the network gives you is already a rather tricky problem. {{index TCP}} @@ -64,7 +64,7 @@ Such a connection acts as a two-way ((pipe)) through which bits can flow—the m ## The Web -The _((World Wide Web))_ (not to be confused with the ((Internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. The "Web" part in the name refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. +The _((World Wide Web))_ (not to be confused with the ((Internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. "Web" refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. To become part of the Web, all you need to do is connect a machine to the ((Internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. @@ -82,9 +82,9 @@ Each ((document)) on the Web is named by a _Uniform Resource Locator_ (URL), whi {{index HTTPS}} -The first part tells us that this URL uses the HTTP ((protocol)) (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which ((server)) we are requesting the document from. Last is a path string that identifies the specific document (or _((resource))_) we are interested in. +The first part tells us that this URL uses the HTTP ((protocol)) (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which ((server)) we are requesting the document from. Last is a path string that identifies the document (or _((resource))_) we are interested in. -Machines connected to the Internet get an _((IP address))_, which is a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a _((domain)) name_ for a specific address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. +Machines connected to the Internet get an _((IP address))_, a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. Since lists of more or less random numbers are hard to remember and awkward to type, you can instead register a _((domain)) name_ for an address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. {{index browser}} @@ -96,7 +96,7 @@ If you type this URL into your browser's ((address bar)), the browser will try t {{indexsee "Hypertext Markup Language", HTML}} -HTML, which stands for _Hypertext Markup Language_, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. +_HTML_, which stands for _Hypertext Markup Language_, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. A short HTML document might look like this: @@ -130,7 +130,7 @@ The tags, wrapped in ((angle brackets)) (`<` and `>`, the symbols for _less than {{index doctype, version}} -The document starts with ``, which tells the browser to interpret the page as _modern_ HTML, as opposed to obsolete styles that were used in the past. +The document starts with ``, which tells the browser to interpret the page as _modern_ HTML, as opposed to obsolete styles used in the past. {{index "head (HTML tag)", "body (HTML tag)", "title (HTML tag)", "h1 (HTML tag)", "p (HTML tag)"}} @@ -146,15 +146,15 @@ Some kinds of ((tag))s do not enclose anything and thus do not need to be closed {{index [escaping, "in HTML"]}} -To be able to include ((angle brackets)) in the text of a document, even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` ("less than"), and a closing bracket is written as `>` ("greater than"). In HTML, an ampersand (`&`) character followed by a name or character code and a semicolon (`;`) is called an _((entity))_ and will be replaced by the character it encodes. +To be able to include ((angle brackets)) in the text of a document even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` ("less than"), and a closing bracket is written as `>` ("greater than"). In HTML, an ampersand (`&`) character followed by a name or character code and a semicolon (`;`) is called an _((entity))_ and will be replaced by the character it encodes. {{index ["backslash character", "in strings"], "ampersand character", "double-quote character"}} -This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning too, they need to be escaped as `&`. Inside attribute values, which are wrapped in double quotes, `"` can be used to insert an actual quote character. +This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning too, they need to be escaped as `&`. Inside attribute values, which are wrapped in double quotes, `"` can be used to insert a literal quote character. {{index "error tolerance", parsing}} -HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser automatically adds them. The way in which this is done has been standardized, and you can rely on all modern browsers to do it in the same way. +HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser automatically adds them. The way this is done has been standardized, and you can rely on all modern browsers to do it in the same way. The following document will be treated just like the one shown previously: @@ -172,13 +172,13 @@ The following document will be treated just like the one shown previously: {{index "title (HTML tag)", "head (HTML tag)", "body (HTML tag)", "html (HTML tag)"}} -The ``, ``, and `` tags are gone completely. The browser knows that `` and `` belong in the head and that `<h1>` means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone. +The `<html>`, `<head>`, and `<body>` tags are completely gone. The browser knows that `<meta>` and `<title>` belong in the head and that `<h1>` means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone. -This book will usually omit the `<html>`, `<head>`, and `<body>` tags from examples to keep them short and free of clutter. But I _will_ close tags and include quotes around attributes. +This book will usually omit the `<html>`, `<head>`, and `<body>` tags from examples to keep them short and free of clutter. I _will_ close tags and include quotes around attributes, though. {{index browser}} -I will also usually omit the ((doctype)) and `charset` declaration. This is not to be taken as an encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. You should consider the doctype and the `charset` metadata to be implicitly present in examples, even when they are not actually shown in the text. +I will also usually omit the ((doctype)) and `charset` declaration. Don't take this as encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. Consider the doctype and the `charset` metadata to be implicitly present in examples, even when they are not actually shown in the text. {{id script_tag}} @@ -186,7 +186,7 @@ I will also usually omit the ((doctype)) and `charset` declaration. This is not {{index [JavaScript, "in HTML"], "script (HTML tag)"}} -In the context of this book, the most important HTML tag is `<script>`. This tag allows us to include a piece of JavaScript in a document. +In the context of this book, the most important HTML tag is `<script>`, which allows us to include a piece of JavaScript in a document. ```{lang: "html"} <h1>Testing alert</h1> @@ -206,7 +206,7 @@ Including large programs directly in HTML documents is often impractical. The `< <script src="code/hello.js"></script> ``` -The _code/hello.js_ file included here contains the same program—`alert("hello!")`. When an HTML page references other URLs as part of itself—for example, an image file or a script—web browsers will retrieve them immediately and include them in the page. +The _code/hello.js_ file included here contains the same program—`alert("hello!")`. When an HTML page references other URLs as part of itself, such as an image file or a script, web browsers will retrieve them immediately and include them in the page. {{index "script (HTML tag)", "closing tag"}} @@ -226,13 +226,13 @@ Some attributes can also contain a JavaScript program. The `<button>` tag (which {{index "single-quote character", [escaping, "in HTML"]}} -Note that I had to use single quotes for the string in the `onclick` attribute because double quotes are already used to quote the whole attribute. I could also have used `"`. +Note that I had to use single quotes for the string in the `onclick` attribute because double quotes are already used to quote the whole attribute. I could also have used `"` to escape the inner quotes. ## In the sandbox {{index "malicious script", "World Wide Web", browser, website, security}} -Running programs downloaded from the ((Internet)) is potentially dangerous. You do not know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by people who do not mean well is how you get your computer infected by ((virus))es, your data stolen, and your accounts hacked. +Running programs downloaded from the ((Internet)) is potentially dangerous. You don't know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by malicious actors is how you get your computer infected by ((virus))es, your data stolen, and your accounts hacked. Yet the attraction of the Web is that you can browse it without necessarily ((trust))ing all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can't look at the files on your computer or modify anything not related to the web page it was embedded in. @@ -240,26 +240,28 @@ Yet the attraction of the Web is that you can browse it without necessarily ((tr Isolating a programming environment in this way is called _((sandbox))ing_, the idea being that the program is harmlessly playing in a sandbox. But you should imagine this particular kind of sandbox as having a cage of thick steel bars over it so that the programs playing in it can't actually get out. -The hard part of sandboxing is allowing the programs enough room to be useful yet at the same time restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste ((clipboard)), can also be used to do problematic, ((privacy))-invading things. +The hard part of sandboxing is allowing programs enough room to be useful while restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste ((clipboard)), can also be used for problematic, ((privacy))-invading purposes. {{index leak, exploit, security}} -Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or criminal organization. +Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine on which the browser is running. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or criminal organization. ## Compatibility and the browser wars {{index Microsoft, "World Wide Web"}} -In the early stages of the Web, a browser called ((Mosaic)) dominated the market. After a few years, the balance shifted to ((Netscape)), which was then, in turn, largely supplanted by Microsoft's ((Internet Explorer)). At any point where a single ((browser)) was dominant, that browser's vendor would feel entitled to unilaterally invent new features for the Web. Since most users used the most popular browser, ((website))s would simply start using those features—never mind the other browsers. +In the early stages of the Web, a browser called ((Mosaic)) dominated the market. After a few years, the balance shifted to ((Netscape)), which was, in turn, largely supplanted by Microsoft's ((Internet Explorer)). At any point where a single ((browser)) was dominant, that browser's vendor would feel entitled to unilaterally invent new features for the Web. Since most users used the most popular browser, ((website))s would simply start using those features—never mind the other browsers. This was the dark age of ((compatibility)), often called the _((browser wars))_. Web developers were left with not one unified Web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of ((bug))s, and of course the bugs were different for each ((browser)). Life was hard for people writing web pages. {{index Apple, "Internet Explorer", Mozilla}} -Mozilla ((Firefox)), a not-for-profit offshoot of ((Netscape)), challenged Internet Explorer's position in the late 2000s. Because ((Microsoft)) was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, ((Google)) introduced its ((Chrome)) browser, and Apple's ((Safari)) browser gained popularity, leading to a situation where there were four major players, rather than one. +Mozilla ((Firefox)), a not-for-profit offshoot of ((Netscape)), challenged Internet Explorer's position in the late 2000s. Because ((Microsoft)) was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, ((Google)) introduced its ((Chrome)) browser and Apple's ((Safari)) browser gained popularity, leading to a situation where there were four major players, rather than one. {{index compatibility}} -The new players had a more serious attitude toward ((standards)) and better ((engineering)) practices, giving us less incompatibility and fewer ((bug))s. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaces Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs. +The new players had a more serious attitude toward ((standards)) and better ((engineering)) practices, giving us less incompatibility and fewer ((bug))s. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaced Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs. Unfortunately, with Firefox's market share getting ever smaller, and Edge becoming just a wrapper around Chrome's core in 2018, this uniformity might once again take the form of a single vendor—Google, this time—having enough control over the browser market to push its idea of what the Web should look like onto the rest of the world. + +For what it is worth, this long chain of historical events and accidents has produced the web platform that we have today. In the next chapters, we are going to write programs for it. \ No newline at end of file From efe7616bc445b99c2450273be6a7bf0c1e1cfc55 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 4 Apr 2024 17:22:22 +0200 Subject: [PATCH 313/392] Integrate copyediting for chapter 14 --- 14_dom.md | 62 ++++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/14_dom.md b/14_dom.md index c1765ece8..ed5d80805 100644 --- a/14_dom.md +++ b/14_dom.md @@ -10,7 +10,7 @@ quote}} {{index drawing, parsing}} -When you open a web page, your browser retrieves the page's ((HTML)) text and parses it, much like the way our parser from [Chapter ?](language#parsing) parsed programs. The browser builds up a model of the document's ((structure)) and uses this model to draw the page on the screen. +When you open a web page, your browser retrieves the page's ((HTML)) text and parses it, much like our parser from [Chapter ?](language#parsing) parsed programs. The browser builds up a model of the document's ((structure)) and uses this model to draw the page on the screen. {{index "live data structure"}} @@ -43,11 +43,11 @@ This page has the following structure: {{indexsee "Document Object Model", DOM}} -The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the _Document Object Model_, or ((DOM)) for short. +The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the _Document Object Model_, or _((DOM))_ for short. {{index "documentElement property", "head property", "body property", "html (HTML tag)", "body (HTML tag)", "head (HTML tag)"}} -The global binding `document` gives us access to these objects. Its `documentElement` property refers to the object representing the `<html>` tag. Since every HTML document has a head and a body, it also has `head` and `body` properties, pointing at those elements. +The global binding `document` gives us access to these objects. Its `documentElement` property refers to the object representing the `<html>` tag. Since every HTML document has a head and a body, it also has `head` and `body` properties pointing at those elements. ## Trees @@ -57,7 +57,7 @@ Think back to the ((syntax tree))s from [Chapter ?](language#parsing) for a mome {{index "documentElement property", [DOM, tree]}} -We call a data structure a _((tree))_ when it has a branching structure, has no ((cycle))s (a node may not contain itself, directly or indirectly), and has a single, well-defined _((root))_. In the case of the DOM, `document.documentElement` serves as the root. +We call a data structure a _((tree))_ when it has a branching structure, no ((cycle))s (a node may not contain itself, directly or indirectly), and a single, well-defined _((root))_. In the case of the DOM, `document.documentElement` serves as the root. {{index sorting, ["data structure", "tree"], "syntax tree"}} @@ -87,23 +87,23 @@ The leaves are text nodes, and the arrows indicate parent-child relationships be {{index "programming language", [interface, design], [DOM, interface]}} -Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we'll see that other parts of the DOM interface also feel cumbersome and alien. The reason for this is that the DOM interface wasn't designed for just JavaScript. Rather, it tries to be a language-neutral interface that can be used in other systems as well—not just for HTML but also for ((XML)), which is a generic ((data format)) with an HTML-like syntax. +Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we'll see that other parts of the DOM interface also feel cumbersome and alien. This is because the DOM interface wasn't designed for JavaScript alone. Rather, it tries to be a language-neutral interface that can be used in other systems as well—not just for HTML but also for ((XML)), which is a generic ((data format)) with an HTML-like syntax. {{index consistency, integration}} -This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn't all that compelling. Having an interface that is properly integrated with the language you are using will save you more time than having a familiar interface across languages. +This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn't all that compelling. Having an interface that is properly integrated with the language you're using will save you more time than having a familiar interface across languages. {{index "array-like object", "NodeList type"}} -As an example of this poor integration, consider the `childNodes` property that element nodes in the DOM have. This property holds an array-like object, with a `length` property and properties labeled by numbers to access the child nodes. But it is an instance of the `NodeList` type, not a real array, so it does not have methods such as `slice` and `map`. +As an example of this poor integration, consider the `childNodes` property that element nodes in the DOM have. This property holds an array-like object with a `length` property and properties labeled by numbers to access the child nodes. But it is an instance of the `NodeList` type, not a real array, so it does not have methods such as `slice` and `map`. {{index [interface, design], [DOM, construction], "side effect"}} -Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or ((attribute))s to it. Instead, you have to first create it and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly. +Then there are issues that are simply caused by poor design. For example, there is no way to create a new node and immediately add children or ((attribute))s to it. Instead, you have to first create it and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly. {{index library}} -But these flaws aren't fatal. Since JavaScript allows us to create our own ((abstraction))s, it is possible to design improved ways to express the operations you are performing. Many libraries intended for browser programming come with such tools. +But these flaws aren't fatal. Since JavaScript allows us to create our own ((abstraction))s, it is possible to design improved ways to express the operations we are performing. Many libraries intended for browser programming come with such tools. ## Moving through the tree @@ -157,11 +157,11 @@ The `nodeValue` property of a text node holds the string of text that it represe {{index [DOM, querying], "body property", "hard-coding", [whitespace, "in HTML"]}} -Navigating these ((link))s among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at `document.body` and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document's `<body>` tag does not have just three children (`<h1>` and two `<p>` elements) but actually has seven: those three, plus the spaces before, after, and between them. +Navigating these ((link))s among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at `document.body` and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document's `<body>` tag has not just three children (`<h1>` and two `<p>` elements), but seven: those three, plus the spaces before, after, and between them. {{index "search problem", "href attribute", "getElementsByTagName method"}} -So if we want to get the `href` attribute of the link in that document, we don't want to say something like "Get the second child of the sixth child of the document body". It'd be better if we could say "Get the first link in the document". And we can. +If we want to get the `href` attribute of the link in that document, we don't want to say something like "Get the second child of the sixth child of the document body". It'd be better if we could say "Get the first link in the document". And we can. ```{sandbox: "homepage"} let link = document.body.getElementsByTagName("a")[0]; @@ -215,13 +215,9 @@ The `replaceChild` method is used to replace a child node with another one. It t ## Creating nodes -{{index "alt attribute", "img (HTML tag)"}} +{{index "alt attribute", "img (HTML tag)", "createTextNode method"}} -Say we want to write a script that replaces all ((image))s (`<img>` tags) in the document with the text held in their `alt` attributes, which specifies an alternative textual representation of the image. - -{{index "createTextNode method"}} - -This involves not only removing the images but adding a new text node to replace them. +Say we want to write a script that replaces all ((image))s (`<img>` tags) in the document with the text held in their `alt` attributes, which specifies an alternative textual representation of the image. This involves not only removing the images but adding a new text node to replace them. ```{lang: html} <p>The <img src="img/cat.png" alt="Cat"> in the @@ -378,11 +374,11 @@ if}} {{id boundingRect}} -The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, which you can find in the `pageXOffset` and `pageYOffset` bindings. +The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want pixel positions relative to the whole document, you must add the current scroll position, which you can find in the `pageXOffset` and `pageYOffset` bindings. {{index "offsetHeight property", "getBoundingClientRect method", drawing, laziness, performance, efficiency}} -Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing that information also requires computing a ((layout)). +Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can before doing so. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing that information also requires computing a ((layout)). {{index "side effect", optimization, benchmark}} @@ -434,7 +430,7 @@ The way an `<img>` tag shows an image or an `<a>` tag causes a link to be follow {{if book -The second link will be green instead of the default link color. +The second link will be green instead of the default link color: {{figure {url: "img/colored-links.png", alt: "Rendered picture of a normal blue link and a styled green link", width: "2.2cm"}}} @@ -491,7 +487,7 @@ Some style property names contain hyphens, such as `font-family`. Because such p {{indexsee "Cascading Style Sheets", CSS}} {{indexsee "style sheet", CSS}} -The styling system for HTML is called ((CSS)), for _Cascading Style Sheets_. A _style sheet_ is a set of rules for how to style elements in a document. It can be given inside a `<style>` tag. +The styling system for HTML is called _((CSS))_, for _Cascading Style Sheets_. A _style sheet_ is a set of rules for how to style elements in a document. It can be given inside a `<style>` tag. ```{lang: html} <style> @@ -509,7 +505,7 @@ The _((cascading))_ in the name refers to the fact that multiple such rules are {{index "style (HTML tag)", "style attribute"}} -When multiple rules define a value for the same property, the most recently read rule gets a higher ((precedence)) and wins. So if the rule in the `<style>` tag included `font-weight: normal`, contradicting the default `font-weight` rule, the text would be normal, _not_ bold. Styles in a `style` attribute applied directly to the node have the highest precedence and always win. +When multiple rules define a value for the same property, the most recently read rule gets a higher ((precedence)) and wins. For example, if the rule in the `<style>` tag included `font-weight: normal`, contradicting the default `font-weight` rule, the text would be normal, _not_ bold. Styles in a `style` attribute applied directly to the node have the highest precedence and always win. {{index uniqueness, "class attribute", "id attribute"}} @@ -540,13 +536,9 @@ The notation `p > a {…}` applies the given styles to all `<a>` tags that are d ## Query selectors -{{index complexity, CSS}} - -We won't be using style sheets all that much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book. - -{{index "domain-specific language", [DOM, querying]}} +{{index complexity, CSS, "domain-specific language", [DOM, querying]}} -The main reason I introduced _((selector))_ syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements. +We won't be using style sheets very much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book. The main reason I introduced _((selector))_ syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements. {{index "querySelectorAll method", "NodeList type"}} @@ -581,7 +573,7 @@ Unlike methods such as `getElementsByTagName`, the object returned by `querySele {{index "querySelector method"}} -The `querySelector` method (without the `All` part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null when no element matches. +The `querySelector` method (without the `All` part) works in a similar way. This one is useful if you want a specific single element. It will return only the first matching element or null when no element matches. {{id animation}} @@ -589,7 +581,7 @@ The `querySelector` method (without the `All` part) works in a similar way. This {{index "position (CSS)", "relative positioning", "top (CSS)", "left (CSS)", "absolute positioning"}} -The `position` style property influences layout in a powerful way. By default it has a value of `static`, meaning the element sits in its normal place in the document. When it is set to `relative`, the element still takes up space in the document, but now the `top` and `left` style properties can be used to move it relative to that normal place. When `position` is set to `absolute`, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Also, its `top` and `left` properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose `position` property isn't `static`, or relative to the document if no such enclosing element exists. +The `position` style property influences layout in a powerful way. It has a default value of `static`, meaning the element sits in its normal place in the document. When it is set to `relative`, the element still takes up space in the document, but now the `top` and `left` style properties can be used to move it relative to that normal place. When `position` is set to `absolute`, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Its `top` and `left` properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose `position` property isn't `static`, or relative to the document if no such enclosing element exists. {{index [animation, "spinning cat"]}} @@ -638,7 +630,7 @@ If we just updated the DOM in a loop, the page would freeze, and nothing would s {{index "smooth animation"}} -The animation function is passed the current ((time)) as an argument. To ensure that the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter if, for example, another heavy task running on the same computer were to prevent the function from running for a fraction of a second. +The animation function is passed the current ((time)) as an argument. To ensure that the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter when, for example, another heavy task running on the same computer prevented the function from running for a fraction of a second. {{index "Math.cos function", "Math.sin function", cosine, sine, trigonometry}} @@ -648,7 +640,7 @@ Moving in ((circle))s is done using the trigonometry functions `Math.cos` and `M {{index coordinates, pi}} -`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0,0) with a radius of one. Both functions interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position, and `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same ((angle)) as _a_. +`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0,0) with a radius of 1. Both functions interpret their argument as the position on this circle, with 0 denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position, and `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same ((angle)) as _a_. {{index "PI constant"}} @@ -668,7 +660,7 @@ Note that styles usually need _units_. In this case, we have to append `"px"` to JavaScript programs may inspect and interfere with the document that the browser is displaying through a data structure called the DOM. This data structure represents the browser's model of the document, and a JavaScript program can modify it to change the visible document. -The DOM is organized like a tree, in which elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as `parentNode` and `childNodes`, which can be used to navigate through this tree. +The DOM is organized like a tree, where elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as `parentNode` and `childNodes`, which can be used to navigate through this tree. The way a document is displayed can be influenced by _styling_, both by attaching styles to nodes directly and by defining rules that match certain nodes. There are many different style properties, such as `color` or `display`. JavaScript code can manipulate an element's style directly through its `style` property. @@ -701,7 +693,7 @@ An HTML table is built with the following tag structure: For each _((row))_, the `<table>` tag contains a `<tr>` tag. Inside of these `<tr>` tags, we can put cell elements: either heading cells (`<th>`) or regular cells (`<td>`). -Given a data set of mountains, an array of objects with `name`, `height`, and `place` properties, generate the DOM structure for a table that enumerates the objects. It should have one column per key and one row per object, plus a header row with `<th>` elements at the top, listing the column names. +Given a data set of mountains, an array of objects with `name`, `height`, and `place` properties, generate the DOM structure for a table that enumerates the objects. It has one column per key and one row per object, plus a header row with `<th>` elements at the top, listing the column names. Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data. @@ -810,7 +802,7 @@ Or make the hat circle around the cat. Or alter the animation in some other inte {{index "absolute positioning", "top (CSS)", "left (CSS)", "position (CSS)"}} -To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that `top` and `left` are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values. +To make positioning multiple objects easier, you'll probably want to switch to absolute positioning. This means that `top` and `left` are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values. {{if interactive From b0be0a0e6ac1b1c6daa004a2f463151bad4e6579 Mon Sep 17 00:00:00 2001 From: Gary Corrall <4834451+gcorrall@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:31:53 +0100 Subject: [PATCH 314/392] Fix typo in chapter 13/index --- 13_browser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/13_browser.md b/13_browser.md index aeb8e1158..7700d0bd9 100644 --- a/13_browser.md +++ b/13_browser.md @@ -56,7 +56,7 @@ A TCP ((connection)) works as follows: one computer must be waiting, or _listeni Another computer can then establish a ((connection)) by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the _((server))_, and the connecting computer is called the _((client))_. -{{index [abtraction, "of the network"]}} +{{index [abstraction, "of the network"]}} Such a connection acts as a two-way ((pipe)) through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that ((TCP)) provides an abstraction of the network. From 87da55c3e011157fdb9f46bd798cb89aa3868f6e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 9 Apr 2024 21:31:35 +0200 Subject: [PATCH 315/392] Add missed word in chapter 4 --- 04_data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04_data.md b/04_data.md index dc21c98e9..54e19c813 100644 --- a/04_data.md +++ b/04_data.md @@ -905,7 +905,7 @@ console.log(city({name: "Vera"})); // → undefined ``` -The expression `a?.b` means the same `a.b` when `a` isn't null or undefined. When it is, it evaluates to undefined. This can be convenient when, as in the example, you aren't sure that a given property exists or when a variable might hold an undefined value. +The expression `a?.b` means the same as `a.b` when `a` isn't null or undefined. When it is, it evaluates to undefined. This can be convenient when, as in the example, you aren't sure that a given property exists or when a variable might hold an undefined value. A similar notation can be used with square bracket access, and even with function calls, by putting `?.` in front of the parentheses or brackets: From 3c784412f05368ff4436b9fb0cc16d7e6f6e2993 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 11 Apr 2024 11:28:20 +0200 Subject: [PATCH 316/392] Integrate copyediting for chapter 15 --- 15_event.md | 88 +++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/15_event.md b/15_event.md index 7629596f4..1157ef309 100644 --- a/15_event.md +++ b/15_event.md @@ -16,17 +16,17 @@ Some programs work with direct user input, such as mouse and keyboard actions. T {{index polling, button, "real-time"}} -Imagine an interface where the only way to find out whether a key on the ((keyboard)) is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key's state so that you'd catch it before it's released again. It would be dangerous to perform other time-intensive computations since you might miss a keypress. +Imagine an interface where the only way to find out whether a key on the ((keyboard)) is being pressed was to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key's state to catch it before it was released again. It would be dangerous to perform other time-intensive computations, since you might miss a keypress. -Some primitive machines do handle input like that. A step up from this would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there. +Some primitive machines handle input like this. A step up from this is for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there. {{index responsiveness, "user experience"}} -Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _((polling))_. Most programmers prefer to avoid it. +Of course, the program has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _((polling))_. Most programmers prefer to avoid it. {{index "callback function", "event handling"}} -A better mechanism is for the system to actively notify our code when an event occurs. Browsers do this by allowing us to register functions as _handlers_ for specific events. +A better mechanism is for the system to actively notify the code when an event occurs. Browsers do this by allowing us to register functions as _handlers_ for specific events. ```{lang: html} <p>Click this document to activate the handler.</p> @@ -45,7 +45,7 @@ The `window` binding refers to a built-in object provided by the browser. It rep {{index "addEventListener method", "event handling", "window object", browser, [DOM, events]}} -Each browser event handler is registered in a context. In the previous example we called `addEventListener` on the `window` object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object they are registered on. +Each browser event handler is registered in a context. In the previous example, we called `addEventListener` on the `window` object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object on which they are registered. ```{lang: html} <button>Click me</button> @@ -66,7 +66,7 @@ That example attaches a handler to the button node. Clicks on the button cause t Giving a node an `onclick` attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is the event name with `on` in front of it. -But a node can have only one `onclick` attribute, so you can register only one handler per node that way. The `addEventListener` method allows you to add any number of handlers so that it is safe to add handlers even if there is already another handler on the element. +But a node can have only one `onclick` attribute, so you can register only one handler per node that way. The `addEventListener` method allows you to add any number of handlers meaning it's safe to add handlers even if there is already another handler on the element. {{index "removeEventListener method"}} @@ -86,7 +86,7 @@ The `removeEventListener` method, called with arguments similar to `addEventList {{index [function, "as value"]}} -The function given to `removeEventListener` has to be the same function value that was given to `addEventListener`. So, to unregister a handler, you'll want to give the function a name (`once`, in the example) to be able to pass the same function value to both methods. +The function given to `removeEventListener` has to be the same function value given to `addEventListener`. When you need to unregister a handler, you'll want to give the handler function a name (`once`, in the example) to be able to pass the same function value to both methods. ## Event objects @@ -112,7 +112,7 @@ Though we have ignored it so far, event handler functions are passed an argument {{index "event type", "type property"}} -The information stored in an event object differs per type of event. We'll discuss different types later in the chapter. The object's `type` property always holds a string identifying the event (such as `"click"` or `"mousedown"`). +The information stored in an event object differs per type of event. (We'll discuss different types later in the chapter.) The object's `type` property always holds a string identifying the event (such as `"click"` or `"mousedown"`). ## Propagation @@ -126,7 +126,7 @@ For most event types, handlers registered on nodes with children will also recei {{index "event handling"}} -But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to _propagate_ outward, from the node where it happened to that node's parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole ((window)) get a chance to respond to the event. +But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to _propagate_ outward from the node where it happened to that node's parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole ((window)) get a chance to respond to the event. {{index "stopPropagation method", "click event"}} @@ -155,7 +155,7 @@ The following example registers `"mousedown"` handlers on both a button and the Most event objects have a `target` property that refers to the node where they originated. You can use this property to ensure that you're not accidentally handling something that propagated up from a node you do not want to handle. -It is also possible to use the `target` property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the `target` property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons. +It is also possible to use the `target` property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the `target` property to figure out whether a button was clicked, rather than registering individual handlers on all of the buttons. ```{lang: html} <button>A</button> @@ -174,7 +174,7 @@ It is also possible to use the `target` property to cast a wide net for a specif {{index scrolling, "default behavior", "event handling"}} -Many events have a default action associated with them. If you click a ((link)), you will be taken to the link's target. If you press the down arrow, the browser will scroll the page down. If you right-click, you'll get a context menu. And so on. +Many events have a default action. If you click a ((link)), you will be taken to the link's target. If you press the down arrow, the browser will scroll the page down. If you right-click, you'll get a context menu. And so on. {{index "preventDefault method"}} @@ -197,7 +197,7 @@ This can be used to implement your own ((keyboard)) shortcuts or ((context menu) {{index usability}} -Try not to do such things unless you have a really good reason to. It'll be unpleasant for people who use your page when expected behavior is broken. +Try not to do such things without a really good reason. It'll be unpleasant for people who use your page when expected behavior is broken. Depending on the browser, some events can't be intercepted at all. On Chrome, for example, the ((keyboard)) shortcut to close the current tab ([control]{keyname}-W or [command]{keyname}-W) cannot be handled by JavaScript. @@ -229,11 +229,11 @@ Despite its name, `"keydown"` fires not only when the key is physically pushed d {{index "key property"}} -The example looked at the `key` property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys such as [enter]{keyname}, it holds a string that names the key (`"Enter"`, in this case). If you hold [shift]{keyname} while pressing a key, that might also influence the name of the key—`"v"` becomes `"V"`, and `"1"` may become `"!"`, if that is what pressing [shift]{keyname}-1 produces on your keyboard. +The previous example looks at the `key` property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys such as [enter]{keyname}, it holds a string that names the key (`"Enter"`, in this case). If you hold [shift]{keyname} while pressing a key, that might also influence the name of the key—`"v"` becomes `"V"`, and `"1"` may become `"!"`, if that is what pressing [shift]{keyname}-1 produces on your keyboard. {{index "modifier key", "shift key", "control key", "alt key", "meta key", "command key", "ctrlKey property", "shiftKey property", "altKey property", "metaKey property"}} -Modifier keys such as [shift]{keyname}, [control]{keyname}, [alt]{keyname}, and [meta]{keyname} ([command]{keyname} on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events. +Modifier keys such as [shift]{keyname}, [control]{keyname}, [alt]{keyname}, and [meta]{keyname} ([command]{keyname} on Mac) generate key events just like normal keys. When looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events. ```{lang: html, focus: true} <p>Press Control-Space to continue.</p> @@ -250,9 +250,9 @@ Modifier keys such as [shift]{keyname}, [control]{keyname}, [alt]{keyname}, and The DOM node where a key event originates depends on the element that has ((focus)) when the key is pressed. Most nodes cannot have focus unless you give them a `tabindex` attribute, but things like ((link))s, buttons, and form fields can. We'll come back to form ((field))s in [Chapter ?](http#forms). When nothing in particular has focus, `document.body` acts as the target node of key events. -When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the ((virtual keyboard)) on ((Android)) ((phone))s, don't fire key events. But even when you have an old-fashioned keyboard, some types of text input don't match key presses in a straightforward way, such as _input method editor_ (((IME))) software used by people whose scripts don't fit on a keyboard, where multiple key strokes are combined to create characters. +When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the ((virtual keyboard)) on ((Android)) ((phone))s, don't fire key events. But even when you have an old-fashioned keyboard, some types of text input don't match key presses in a straightforward way, such as _input method editor_ (_((IME))_) software used by people whose scripts don't fit on a keyboard, where multiple key strokes are combined to create characters. -To notice when something was typed, elements that you can type into, such as the `<input>` and `<textarea>` tags, fire `"input"` events whenever the user changes their content. To get the actual content that was typed, it is best to directly read it from the focused field. [Chapter ?](http#forms) will show how. +To notice when something was typed, elements that you can type into, such as the `<input>` and `<textarea>` tags, fire `"input"` events whenever the user changes their content. To get the actual content that was typed, it is best to directly read it from the focused field, which we discuss in [Chapter ?](http#forms). ## Pointer events @@ -280,7 +280,7 @@ To get precise information about the place where a mouse event happened, you can {{id mouse_drawing}} -The following program implements a primitive drawing application. Every time you click the document, it adds a dot under your mouse pointer. See [Chapter ?](paint) for a less primitive drawing application. +The following program implements a primitive drawing application. Every time you click the document, it adds a dot under your mouse pointer. ```{lang: html} <style> @@ -306,11 +306,13 @@ The following program implements a primitive drawing application. Every time you </script> ``` +We'll create a less primitive drawing application in [Chapter ?](paint). + ### Mouse motion {{index "mousemove event"}} -Every time the mouse pointer moves, a `"mousemove"` event is fired. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-((dragging)) functionality. +Every time the mouse pointer moves, a `"mousemove"` event fires. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-((dragging)) functionality. {{index "draggable bar example"}} @@ -354,13 +356,13 @@ if}} {{index "mouseup event", "mousemove event"}} -Note that the `"mousemove"` handler is registered on the whole ((window)). Even if the mouse goes outside of the bar during resizing, as long as the button is held we still want to update its size. +Note that the `"mousemove"` handler is registered on the whole ((window)). Even if the mouse goes outside of the bar during resizing, as long as the button is held, we still want to update its size. {{index "buttons property", "button property", "bitfield"}} -We must stop resizing the bar when the mouse button is released. For that, we can use the `buttons` property (note the plural), which tells us about the buttons that are currently held down. When this is zero, no buttons are down. When buttons are held, its value is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. With the left and right buttons held, for example, the value of `buttons` will be 3. +We must stop resizing the bar when the mouse button is released. For that, we can use the `buttons` property (note the plural), which tells us about the buttons that are currently held down. When it is zero, no buttons are down. When buttons are held, the value of the `buttons` property is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. With the left and right buttons held, for example, the value of `buttons` will be 3. -Note that the order of these codes is different from the one used by `button`, where the middle button came before the right one. As mentioned, consistency isn't really a strong point of the browser's programming interface. +Note that the order of these codes is different from the one used by `button`, where the middle button came before the right one. As mentioned, consistency isn't a strong point of the browser's programming interface. ### Touch events @@ -368,7 +370,7 @@ Note that the order of these codes is different from the one used by `button`, w The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the Web "work" on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you'll get `"mousedown"`, `"mouseup"`, and `"click"` events. -But this illusion isn't very robust. A touchscreen works differently from a mouse: it doesn't have multiple buttons, you can't track the finger when it isn't on the screen (to simulate `"mousemove"`), and it allows multiple fingers to be on the screen at the same time. +But this illusion isn't very robust. A touchscreen doesn't work like a mouse: it doesn't have multiple buttons, you can't track the finger when it isn't on the screen (to simulate `"mousemove"`), and it allows multiple fingers to be on the screen at the same time. Mouse events cover touch interaction only in straightforward cases—if you add a `"click"` handler to a button, touch users will still be able to use it. But something like the resizeable bar in the previous example does not work on a touchscreen. @@ -449,7 +451,7 @@ Giving an element a `position` of `fixed` acts much like an `absolute` position {{index "innerHeight property", "innerWidth property", "pageYOffset property"}} -The global `innerHeight` binding gives us the height of the window, which we have to subtract from the total scrollable height—you can't keep scrolling when you hit the bottom of the document. There's also an `innerWidth` for the window width. By dividing `pageYOffset`, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar. +The global `innerHeight` binding gives us the height of the window, which we must subtract from the total scrollable height—you can't keep scrolling when you hit the bottom of the document. There's also an `innerWidth` for the window width. By dividing `pageYOffset`, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar. {{index "preventDefault method"}} @@ -513,7 +515,7 @@ Elements such as ((image))s and script tags that load an external file also have {{index "beforeunload event", "page reload", "preventDefault method"}} -When a page is closed or navigated away from (for example, by following a link), a `"beforeunload"` event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event _and_ set the `returnValue` property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight loss ads, most browsers no longer display them. +When you close page or navigate away from it (for example, by following a link), a `"beforeunload"` event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event _and_ set the `returnValue` property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight loss ads, most browsers no longer display them. {{id timeline}} @@ -523,7 +525,7 @@ When a page is closed or navigated away from (for example, by following a link), In the context of the event loop, as discussed in [Chapter ?](async), browser event handlers behave like other asynchronous notifications. They are scheduled when the event occurs but must wait for other scripts that are running to finish before they get a chance to run. -The fact that events can be processed only when nothing else is running means that, if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there's time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use. +The fact that events can be processed only when nothing else is running means that if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there's time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use. For cases where you _really_ do want to do some time-consuming thing in the background without freezing the page, browsers provide something called _((web worker))s_. A worker is a JavaScript process that runs alongside the main script, on its own timeline. @@ -554,13 +556,9 @@ The `postMessage` function sends a message, which will cause a `"message"` event ## Timers -{{index timeout, "setTimeout function"}} - -We saw the `setTimeout` function in [Chapter ?](async). It schedules another function to be called later, after a given number of milliseconds. +{{index timeout, "setTimeout function", "clearTimeout function"}} -{{index "clearTimeout function"}} - -Sometimes you need to cancel a function you have scheduled. This is done by storing the value returned by `setTimeout` and calling `clearTimeout` on it. +The `setTimeout` function we saw in [Chapter ?](async) schedules another function to be called later, after a given number of milliseconds. Sometimes you need to cancel a function you have scheduled. You can do this by storing the value returned by `setTimeout` and calling `clearTimeout` on it. ``` let bombTimer = setTimeout(() => { @@ -575,11 +573,11 @@ if (Math.random() < 0.5) { // 50% chance {{index "cancelAnimationFrame function", "requestAnimationFrame function"}} -The `cancelAnimationFrame` function works in the same way as `clearTimeout`—calling it on a value returned by `requestAnimationFrame` will cancel that frame (assuming it hasn't already been called). +The `cancelAnimationFrame` function works in the same way as `clearTimeout`. Calling it on a value returned by `requestAnimationFrame` will cancel that frame (assuming it hasn't already been called). {{index "setInterval function", "clearInterval function", repetition}} -A similar set of functions, `setInterval` and `clearInterval`, are used to set timers that should _repeat_ every _X_ milliseconds. +A similar set of functions, `setInterval` and `clearInterval`, are used to set timers that should repeat every _X_ milliseconds. ``` let ticks = 0; @@ -596,7 +594,7 @@ let clock = setInterval(() => { {{index optimization, "mousemove event", "scroll event", blocking}} -Some types of events have the potential to fire rapidly, many times in a row (the `"mousemove"` and `"scroll"` events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow. +Some types of events have the potential to fire rapidly many times in a row, such as the `"mousemove"` and `"scroll"` events. When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow. {{index "setTimeout function"}} @@ -604,7 +602,7 @@ If you do need to do something nontrivial in such a handler, you can use `setTim {{index "textarea (HTML tag)", "clearTimeout function", "keydown event"}} -In the first example, we want to react when the user has typed something, but we don't want to do it immediately for every input event. When they are ((typing)) quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled. +For example, suppose we want to react when the user has typed something, but we don't want to do it immediately for every input event. When they are ((typing)) quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled. ```{lang: html} <textarea>Type something here...</textarea> @@ -646,9 +644,9 @@ We can use a slightly different pattern if we want to space responses so that th Event handlers make it possible to detect and react to events happening in our web page. The `addEventListener` method is used to register such a handler. -Each event has a type (`"keydown"`, `"focus"`, and so on) that identifies it. Most events are called on a specific DOM element and then _propagate_ to that element's ancestors, allowing handlers associated with those elements to handle them. +Each event has a type (`"keydown"`, `"focus"`, and so on) that identifies it. Most events are called on a specific DOM element and then propagate to that element's ancestors, allowing handlers associated with those elements to handle them. -When an event handler is called, it is passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (`stopPropagation`) and prevent the browser's default handling of the event (`preventDefault`). +When an event handler is called, it's passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (`stopPropagation`) and prevent the browser's default handling of the event (`preventDefault`). Pressing a key fires `"keydown"` and `"keyup"` events. Pressing a mouse button fires `"mousedown"`, `"mouseup"`, and `"click"` events. Moving the mouse fires `"mousemove"` events. Touchscreen interaction will result in `"touchstart"`, `"touchmove"`, and `"touchend"` events. @@ -660,7 +658,7 @@ Scrolling can be detected with the `"scroll"` event, and focus changes can be de {{index "balloon (exercise)", "arrow key"}} -Write a page that displays a ((balloon)) (using the balloon ((emoji)), 🎈). When you press the up arrow, it should inflate (grow) 10 percent, and when you press the down arrow, it should deflate (shrink) 10 percent. +Write a page that displays a ((balloon)) (using the balloon ((emoji)), 🎈). When you press the up arrow, it should inflate (grow) 10 percent. When you press the down arrow, it should deflate (shrink) 10 percent. {{index "font-size (CSS)"}} @@ -668,7 +666,7 @@ You can control the size of text (emoji are text) by setting the `font-size` CSS The key names of the arrow keys are `"ArrowUp"` and `"ArrowDown"`. Make sure the keys change only the balloon, without scrolling the page. -When that works, add a feature where, if you blow up the balloon past a certain size, it explodes. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can't inflate or deflate the explosion). +Once you have that working, add a feature where if you blow up the balloon past a certain size, it “explodes”. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can't inflate or deflate the explosion). {{if interactive @@ -700,17 +698,15 @@ hint}} {{index animation, "mouse trail (exercise)"}} -In JavaScript's early days, which was the high time of ((gaudy home pages)) with lots of animated images, people came up with some truly inspiring ways to use the language. - -One of these was the _mouse trail_—a series of elements that would follow the mouse pointer as you moved it across the page. +In JavaScript's early days, which was the high time of ((gaudy home pages)) with lots of animated images, people came up with some truly inspiring ways to use the language. One of these was the _mouse trail_—a series of elements that would follow the mouse pointer as you moved it across the page. {{index "absolute positioning", "background (CSS)"}} -In this exercise, I want you to implement a mouse trail. Use absolutely positioned `<div>` elements with a fixed size and background color (refer to the [code](event#mouse_drawing) in the "Mouse Clicks" section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer. +In this exercise, I want you to implement a mouse trail. Use absolutely positioned `<div>` elements with a fixed size and background color (refer to the [code](event#mouse_drawing) in the "Mouse Clicks" section for an example). Create a bunch of these elements and, when the mouse moves, display them in the wake of the mouse pointer. {{index "mousemove event"}} -There are various possible approaches here. You can make your solution as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse's current position every time a `"mousemove"` event occurs. +There are various possible approaches here. You can make your trail as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse's current position every time a `"mousemove"` event occurs. {{if interactive @@ -754,11 +750,11 @@ hint}} {{index "tabbed interface (exercise)"}} -Tabbed panels are widely used in user interfaces. They allow you to select an interface panel by choosing from a number of tabs "sticking out" above an element. +Tabbed panels are common in user interfaces. They allow you to select an interface panel by choosing from a number of tabs "sticking out" above an element. {{index "button (HTML tag)", "display (CSS)", "hidden element", "data attribute"}} -In this exercise you must implement a simple tabbed interface. Write a function, `asTabs`, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of `<button>` elements at the top of the node, one for each child element, containing text retrieved from the `data-tabname` attribute of the child. All but one of the original children should be hidden (given a `display` style of `none`). The currently visible node can be selected by clicking the buttons. +Implement a simple tabbed interface. Write a function, `asTabs`, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of `<button>` elements at the top of the node, one for each child element, containing text retrieved from the `data-tabname` attribute of the child. All but one of the original children should be hidden (given a `display` style of `none`). The currently visible node can be selected by clicking the buttons. When that works, extend it to style the button for the currently selected tab differently so that it is obvious which tab is selected. From 99ad6f230db7d9b0ccc6b66bd13e2ffac247c791 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 11 Apr 2024 11:33:44 +0200 Subject: [PATCH 317/392] Reword help page --- src/client/ejs.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/ejs.mjs b/src/client/ejs.mjs index 04220539b..f77502565 100644 --- a/src/client/ejs.mjs +++ b/src/client/ejs.mjs @@ -31,7 +31,7 @@ function chapterInteraction() { let popup = document.body.appendChild(document.createElement("div")) popup.className = "popup" popup.appendChild(document.createElement("h2")).textContent = "Instructions" - popup.appendChild(document.createElement("p")).textContent = `Code on this page can be edited and run by clicking it or moving focus to it and pressing Enter. Code executed this way shares its environment with other code ran on the page, and some pre-defined code for the chapter. When inside the code editor, the following keyboard shortcuts are available.` + popup.appendChild(document.createElement("p")).textContent = `Code snippets on this page can be edited and run by clicking them or moving focus to them and pressing Enter. Executed snippets share their environment with other snippets ran on the page, and some pre-defined code for the chapter. When inside the code editor, the following keyboard shortcuts are available:` for (let [key, desc] of [ [modName + "Enter", "Run code"], [modName + "j", "Revert code"], From 18867fe421c9864197ae76004e56bfc2ea2a4063 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 11 Apr 2024 11:48:52 +0200 Subject: [PATCH 318/392] Integrate copyediting for chapter 16 --- 16_game.md | 54 +++++++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/16_game.md b/16_game.md index ff77abb54..ba0bddf7e 100644 --- a/16_game.md +++ b/16_game.md @@ -34,7 +34,7 @@ The dark ((box)) represents the ((player)), whose task is to collect the yellow {{index keyboard, jumping}} -The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen ((avatar)). +The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is this game character's specialty. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen ((avatar)). {{index "fractional number", discretization, "artificial life", "electronic life"}} @@ -52,7 +52,7 @@ The screen- and keyboard-related code is only a small part of the work we need t {{index "table (HTML tag)"}} -We can represent the background as a table since it is an unchanging ((grid)) of squares. The free-moving elements can be overlaid using absolutely positioned elements. +We can represent the background as a table, since it is an unchanging ((grid)) of squares. The free-moving elements can be overlaid using absolutely positioned elements. {{index performance, [DOM, graphics]}} @@ -126,7 +126,7 @@ class Level { {{index "trim method", "split method", [whitespace, trimming]}} -The `trim` method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all the lines are directly below each other. The remaining string is split on ((newline character))s, and each line is spread into an array, producing arrays of characters. +The `trim` method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all lines are directly below each other. The remaining string is split on ((newline character))s, and each line is spread into an array, producing arrays of characters. {{index [array, "as matrix"]}} @@ -174,13 +174,13 @@ This is again a persistent data structure—updating the game state creates a ne {{index actor, "Vec class", [interface, object]}} -Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. They have `size` and `pos` properties holding the size and the coordinates of the top-left corner of the rectangle representing this actor. +Actor objects represent the current position and state of a given moving element (player, coin, or mobile lava) in our game. All actor objects conform to the same interface. They have `size` and `pos` properties holding the size and the coordinates of the top-left corner of the rectangle representing this actor, and an `update` method. -Then they have an `update` method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object. +This `update` method is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object. A `type` property contains a string that identifies the type of the actor—`"player"`, `"coin"`, or `"lava"`. This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type. -Actor classes have a static `create` method that is used by the `Level` constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is needed because the `Lava` class handles several different characters. +Actor classes have a static `create` method that is used by the `Level` constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is necessary because the `Lava` class handles several different characters. {{id vector}} @@ -208,7 +208,7 @@ The different types of actors get their own classes since their behavior is very {{index simulation, "Player class"}} -The player class has a property `speed` that stores its current speed to simulate momentum and gravity. +The player class has a `speed` property that stores its current speed to simulate momentum and gravity. ```{includeCode: true} class Player { @@ -228,7 +228,7 @@ class Player { Player.prototype.size = new Vec(0.8, 1.5); ``` -Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the `@` character appeared. This way, its bottom aligns with the bottom of the square it appeared in. +Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the `@` character appeared. This way, its bottom aligns with the bottom of the square where it appeared. The `size` property is the same for all instances of `Player`, so we store it on the prototype rather than on the instances themselves. We could have used a ((getter)) like `type`, but that would create and return a new `Vec` object every time the property is read, which would be wasteful. (Strings, being ((immutable)), don't have to be re-created every time they are evaluated.) @@ -322,7 +322,7 @@ The task ahead is to display such levels on the screen and to model time and mot {{index graphics, encapsulation, "DOMDisplay class", [DOM, graphics]}} -In the [next chapter](canvas#canvasdisplay), we'll ((display)) the same game in a different way. To make that possible, we put the drawing logic behind an interface, and pass it to the game as an argument. That way, we can use the same game program with different new display ((module))s. +In the [next chapter](canvas#canvasdisplay), we'll ((display)) the same game in a different way. To make that possible, we put the drawing logic behind an interface and pass it to the game as an argument. That way, we can use the same game program with different new display ((module))s. A game display object draws a given ((level)) and state. We pass its constructor to the game to allow it to be replaced. The display class we define in this chapter is called `DOMDisplay` because it uses DOM elements to show the level. @@ -406,11 +406,11 @@ Some of these (`table-layout`, `border-spacing`, and `padding`) are used to supp {{index "background (CSS)", "rgb (CSS)", CSS}} -The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) or with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. In the `.lava` rule, the first number (red) is the largest. +The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) or with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255. In `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. In the `.lava` rule, the first number (red) is the largest. {{index [DOM, graphics]}} -We draw each ((actor)) by creating a DOM element for it and setting that element's position and size based on the actor's properties. The values have to be multiplied by `scale` to go from game units to pixels. +We draw each ((actor)) by creating a DOM element for it and setting that element's position and size based on the actor's properties. The values must be multiplied by `scale` to go from game units to pixels. ```{includeCode: true} function drawActors(actors) { @@ -427,7 +427,7 @@ function drawActors(actors) { {{index "position (CSS)", "class attribute"}} -To give an element more than one class, we separate the class names by spaces. In the ((CSS)) code shown next, the `actor` class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don't have to define the `lava` class again because we're reusing the class for the lava grid squares we defined earlier. +To give an element more than one class, we separate the class names by spaces. In the following ((CSS)) code, the `actor` class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don't have to define the `lava` class again because we're reusing the class for the lava grid squares we defined earlier. ```{lang: "css"} .actor { position: absolute; } @@ -470,7 +470,7 @@ After touching ((lava)), the player's color turns dark red, suggesting scorching {{index "position (CSS)", "max-width (CSS)", "overflow (CSS)", "max-height (CSS)", viewport, scrolling, [DOM, graphics]}} -We can't assume that the level always fits in the _viewport_—the element into which we draw the game. That is why the `scrollPlayerIntoView` call is needed. It ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following ((CSS)) gives the game's wrapping DOM element a maximum size and ensures that anything that sticks out of the element's box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level's top-left corner. +We can't assume that the level always fits in the _viewport_, the element into which we draw the game. That is why we need the `scrollPlayerIntoView` call: it ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following ((CSS)) gives the game's wrapping DOM element a maximum size and ensures that anything that sticks out of the element's box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level's top-left corner. ```{lang: css} .game { @@ -518,9 +518,9 @@ The way the player's center is found shows how the methods on our `Vec` type all {{index validation}} -Next, a series of checks verifies that the player position isn't outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element's scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting `scrollLeft` to -10 will cause it to become 0. +Next, a series of checks verifies that the player position isn't outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element's scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting `scrollLeft` to `-10` will cause it to become `0`. -It would have been slightly simpler to always try to scroll the player to the center of the ((viewport)). But this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It is more pleasant to have a "neutral" area in the middle of the screen where you can move around without causing any scrolling. +While it would have been slightly simpler to always try to scroll the player to the center of the ((viewport)), this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It's more pleasant to have a "neutral" area in the middle of the screen where you can move around without causing any scrolling. {{index [game, screenshot]}} @@ -550,13 +550,13 @@ The `<link>` tag, when used with `rel="stylesheet"`, is a way to load a CSS file {{index physics, [animation, "platform game"]}} -Now we're at the point where we can start adding motion. The basic approach, taken by most games like this, is to split ((time)) into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We'll measure time in seconds, so speeds are expressed in units per second. +Now we're at the point where we can start adding motion. The basic approach taken by most games like this is to split ((time)) into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We'll measure time in seconds, so speeds are expressed in units per second. {{index obstacle, "collision detection"}} Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, that coin must be collected. When touching lava, the game should be lost. -Solving this for the general case is a big task. You can find libraries, usually called _((physics engine))s_, that simulate interaction between physical objects in two or three ((dimensions)). We'll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way. +Solving this for the general case is a major task. You can find libraries, usually called _((physics engine))s_, that simulate interaction between physical objects in two or three ((dimensions)). We'll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way. {{index bouncing, "collision detection", [animation, "platform game"]}} @@ -564,7 +564,7 @@ Before moving the ((player)) or a block of ((lava)), we test whether the motion {{index discretization}} -This approach requires our ((time)) steps to be rather small since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps. +This approach requires our ((time)) steps to be rather small, since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps. {{index obstacle, "touches method", "collision detection"}} @@ -625,7 +625,7 @@ State.prototype.update = function(time, keys) { The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the `update` method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that's the only actor that's controlled by the keyboard. -If the game is already over, no further processing has to be done (the game can't be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost, and we're done. Finally, if the game really is still going on, it sees whether any other actors overlap the player. +If the game is already over, no further processing has to be done (the game can't be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost and we're done. Finally, if the game really is still going on, it sees whether any other actors overlap the player. Overlap between actors is detected with the `overlap` function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x-axis and along the y-axis. @@ -676,7 +676,7 @@ Lava.prototype.update = function(time, state) { {{index bouncing, multiplication, "Vec class", "collision detection"}} -This `update` method computes a new position by adding the product of the ((time)) step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the ((lava)) block—dripping lava has a `reset` position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by -1 so that it starts moving in the opposite direction. +This `update` method computes a new position by adding the product of the ((time)) step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the ((lava)) block—dripping lava has a `reset` position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by `-1` so that it starts moving in the opposite direction. {{index "Coin class", coin, wave}} @@ -916,13 +916,9 @@ if}} ### Pausing the game -{{index "pausing (exercise)", "escape key", keyboard}} +{{index "pausing (exercise)", "escape key", keyboard, "runLevel function", "event handling"}} -Make it possible to pause (suspend) and unpause the game by pressing the Esc key. - -{{index "runLevel function", "event handling"}} - -This can be done by changing the `runLevel` function to set up a keyboard event handler that interrupts or resumes the animation whenever the Esc key is hit. +Make it possible to pause (suspend) and unpause the game by pressing the [esc]{keyname} key. You can do this by changing the `runLevel` function to set up a keyboard event handler that interrupts or resumes the animation whenever the [esc]{keyname} key is hit. {{index "runAnimation function"}} @@ -930,7 +926,7 @@ The `runAnimation` interface may not look like it is suitable for this at first {{index [binding, global], "trackKeys function"}} -When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The `arrowKeys` object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they _((leak))_ out of our system. Extend `trackKeys` to provide a way to unregister its handlers and then change `runLevel` to register its handlers when it starts and unregister them again when it is finished. +When you have that working, there's something else you can try. The way we've been registering keyboard event handlers is somewhat problematic. The `arrowKeys` object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they _((leak))_ out of our system. Extend `trackKeys` to provide a way to unregister its handlers, then change `runLevel` to register its handlers when it starts and unregister them again when it is finished. {{if interactive @@ -990,9 +986,9 @@ hint}} {{index "monster (exercise)"}} -It is traditional for platform games to have enemies that you can jump on top of to defeat. This exercise asks you to add such an actor type to the game. +It is traditional for platform games to have enemies that you can defeat by jumping on top of them. This exercise asks you to add such an actor type to the game. -We'll call it a monster. Monsters move only horizontally. You can make them move in the direction of the player, bounce back and forth like horizontal lava, or have any movement pattern you want. The class doesn't have to handle falling, but it should make sure the monster doesn't walk through walls. +We'll call this actor a monster. Monsters move only horizontally. You can make them move in the direction of the player, bounce back and forth like horizontal lava, or have any other movement pattern you want. The class doesn't have to handle falling, but it should make sure the monster doesn't walk through walls. When a monster touches the player, the effect depends on whether the player is jumping on top of them or not. You can approximate this by checking whether the player's bottom is near the monster's top. If this is the case, the monster disappears. If not, the game is lost. From 338dae6a835abab7fe68400da7bbaf43ed0ed8e6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 11 Apr 2024 12:10:47 +0200 Subject: [PATCH 319/392] Integrate copyediting for chapter 17 --- 17_canvas.md | 56 +++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 4b5d64b57..6ff572168 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -14,7 +14,7 @@ quote}} {{index CSS, "transform (CSS)", [DOM, graphics]}} -Browsers give us several ways to display ((graphics)). The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the [previous chapter](game) showed. By adding partially transparent background ((image))s to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the `transform` style. +Browsers give us several ways to display ((graphics)). The simplest way is to use styles to position and color regular DOM elements. This can get us quite far, as the game in the [previous chapter](game) showed. By adding partially transparent background ((image))s to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the `transform` style. But we'd be using the DOM for something that it wasn't originally designed for. Some tasks, such as drawing a ((line)) between arbitrary points, are extremely awkward to do with regular HTML elements. @@ -28,7 +28,7 @@ The second alternative is called a _((canvas))_. A canvas is a single DOM elemen ## SVG -This book will not go into ((SVG)) in detail, but I will briefly explain how it works. At the [end of the chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs that you must consider when deciding which ((drawing)) mechanism is appropriate for a given application. +This book won't go into ((SVG)) in detail, but I'll briefly explain how it works. At the [end of the chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs that you must consider when deciding which ((drawing)) mechanism is appropriate for a given application. This is an HTML document with a simple SVG ((picture)) in it: @@ -104,7 +104,7 @@ if}} {{index SVG, coordinates}} -Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-((axis)) goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner. +Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-((axis)) goes down from there. This means (10,10) is 10 pixels below and to the right of the top-left corner. {{id fill_stroke}} @@ -112,11 +112,11 @@ Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0, {{index filling, stroking, drawing, SVG}} -In the ((canvas)) interface, a shape can be _filled_, meaning its area is given a certain color or pattern, or it can be _stroked_, which means a ((line)) is drawn along its edge. The same terminology is used by SVG. +In the ((canvas)) interface, a shape can be _filled_, meaning its area is given a certain color or pattern, or it can be _stroked_, which means a ((line)) is drawn along its edge. SVG uses the same terminology. {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. +The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. {{index [state, "of canvas"]}} @@ -200,7 +200,7 @@ When filling a path (using the `fill` method), each ((shape)) is filled separate </script> ``` -This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be there when you stroke the path. +This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be there if you stroked the path. {{if book @@ -250,7 +250,7 @@ We draw a ((quadratic curve)) from the left to the right, with (60,10) as contro {{index canvas, "bezierCurveTo method"}} -The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this one has two—one for each of the ((line))'s endpoints. Here is a similar sketch to illustrate the behavior of such a curve: +The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this method has two—one for each of the ((line))'s endpoints. Here is a similar sketch to illustrate the behavior of such a curve: ```{lang: html} <canvas></canvas> @@ -302,7 +302,7 @@ Those last two parameters make it possible to draw only part of the circle. The {{index "moveTo method", "arc method", [path, " canvas"]}} -The resulting picture contains a ((line)) from the right of the full circle (first call to `arc`) to the right of the quarter-((circle)) (second call). Like other path-drawing methods, a line drawn with `arc` is connected to the previous path segment. You can call `moveTo` or start a new path to avoid this. +The resulting picture contains a ((line)) from the right of the full circle (first call to `arc`) to the right of the quarter-((circle)) (second call). {{if book @@ -310,13 +310,15 @@ The resulting picture contains a ((line)) from the right of the full circle (fir if}} +Like other path-drawing methods, a line drawn with `arc` is connected to the previous path segment.You can call `moveTo` or start a new path to avoid this. + {{id pie_chart}} ## Drawing a pie chart {{index "pie chart example"}} -Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results. +Imagine we've just taken a ((job)) at EconomiCorp, Inc. Your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results. The `results` binding contains an array of objects that represent the survey responses. @@ -400,7 +402,7 @@ In computer ((graphics)), a distinction is often made between _vector_ graphics {{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} -The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This pixel data can originate from an `<img>` element or from another canvas. The following example creates a detached `<img>` element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a `"load"` event handler and do the drawing after the image has loaded. +The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This pixel data can originate from an `<img>` element or from another canvas. The following example creates a detached `<img>` element and loads an image file into it. But the method cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a `"load"` event handler and do the drawing after the image has loaded. ```{lang: html} <canvas></canvas> @@ -418,13 +420,13 @@ The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This {{index "drawImage method", scaling}} -By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to set a different width and height. +By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to specify the width and height of the drawn image, when those aren't the same as origin image. When `drawImage` is given _nine_ arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied. {{index "player", "pixel art"}} -This can be used to pack multiple _((sprite))s_ (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple ((pose))s: +This can be used to pack multiple _((sprite))s_ (image elements) into a single image file and then draw only the part you need. For example, this picture contains a game character in multiple ((pose))s: {{figure {url: "img/player_big.png", alt: "Pixel art showing a computer game character in 10 different poses. The first 8 form its running animation cycle, the 9th has the character standing still, and the 10th shows him jumping.", width: "6cm"}}} @@ -472,7 +474,7 @@ The `cycle` binding tracks our position in the animation. For each ((frame)), it {{indexsee flipping, mirroring}} -But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the ((canvas)) to draw the picture the other way round. +What if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we could also instruct the ((canvas)) to draw the picture the other way round. {{index "scale method", scaling}} @@ -504,7 +506,7 @@ Scaling will cause everything about the drawn image, including the ((line width) {{index "drawImage method"}} -So to turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage` because that would move our picture outside of the ((canvas)), where it won't be visible. You could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn't require the code that does the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens. +To turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage`. That would move our picture outside of the ((canvas)), where it won't be visible. We could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn't require the code doing the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens. {{index "rotate method", "translate method", transformation}} @@ -512,7 +514,7 @@ There are several other methods besides `scale` that influence the coordinate sy {{index "rotate method", "translate method"}} -So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50,50). +If we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50,50). {{figure {url: "img/transform.svg", alt: "Diagram showing the result of stacking transformations. The first diagram translates and then rotates, causing the translation to happen normally and rotation to happen around the target of the translation. The second diagram first rotates, and then translates, causing the rotation to happen around the origin and the translation direction to be tilted by that rotation.", width: "9cm"}}} @@ -623,7 +625,7 @@ We define another display object type called `CanvasDisplay`, supporting the sam {{index [state, "in objects"]}} -This object keeps a little more information than `DOMDisplay`. Rather than using the scroll position of its DOM element, it tracks its own ((viewport)), which tells us what part of the level we are currently looking at. Finally, it keeps a `flipPlayer` property so that even when the player is standing still, it keeps facing the direction it last moved in. +This object keeps a little more information than `DOMDisplay`. Rather than using the scroll position of its DOM element, it tracks its own ((viewport)), which tells us which part of the level we are currently looking at. Finally, it keeps a `flipPlayer` property so that even when the player is standing still, it keeps facing the direction in which it last moved. ```{sandbox: "game", includeCode: true} class CanvasDisplay { @@ -748,11 +750,11 @@ Tiles that are not empty are drawn with `drawImage`. The `otherSprites` image co {{index scaling}} -Background tiles are 20 by 20 pixels since we will use the same scale that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 (the value of the `scale` binding), and the offset for walls is 0. +Background tiles are 20 by 20 pixels, since we'll use the same scale as in `DOMDisplay`. Thus, the offset for lava tiles is 20 (the value of the `scale` binding), and the offset for walls is 0. {{index drawing, "load event", "drawImage method"}} -We don't bother waiting for the sprite image to load. Calling `drawImage` with an image that hasn't been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few ((frame))s, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes. +We don't bother waiting for the sprite image to load. Calling `drawImage` with an image that hasn't been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few ((frame))s while the image is still loading, but that isn't a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes. {{index "player", [animation, "platform game"], drawing}} @@ -818,7 +820,7 @@ When ((drawing)) something that is not the ((player)), we look at its type to fi {{index viewport}} -We have to subtract the viewport's position when computing the actor's position since (0,0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works. +We have to subtract the viewport's position when computing the actor's position, since (0,0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works. {{if interactive @@ -848,7 +850,7 @@ if}} ## Choosing a graphics interface -Thus, when you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses. +When you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses. {{index "text wrapping"}} @@ -862,19 +864,15 @@ SVG can be used to produce ((crisp)) ((graphics)) that look good at any zoom lev Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big ((picture)) in response to what the user is doing or as part of an ((animation)), doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can't do that with canvas. -{{index performance, optimization}} - -But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape. - -{{index "ray tracer"}} +{{index performance, optimization, "ray tracer"}} -There are also effects, such as rendering a scene one ((pixel)) at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that are only practical with a canvas element. +But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape. There are also effects that are only practical with a canvas element, such as rendering a scene one ((pixel)) at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it). In some cases, you may want to combine several of these techniques. For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but show ((text))ual information by positioning an HTML element on top of the picture. {{index display}} -For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three ((graphics)) technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements. +For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three ((graphics)) technologies, since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements. ## Summary @@ -918,7 +916,7 @@ Write a program that draws the following ((shape))s on a ((canvas)): {{figure {url: "img/exercise_shapes.png", alt: "Picture showing the shapes you are asked to draw", width: "8cm"}}} -When drawing the last two, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions. +When drawing the last two shapes, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions. {{index readability, "hard-coding"}} @@ -1077,7 +1075,7 @@ One unfortunate thing about ((transformation))s is that they slow down the drawi In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion. -Think of a way to allow us to draw an inverted character without loading additional image files and without having to make transformed `drawImage` calls every frame. +Think of a way to draw an inverted character without loading additional image files and without having to make transformed `drawImage` calls every frame. {{hint From 52cf5318717e1e4ac8695bcd0a425910a15ace4c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 11 Apr 2024 12:29:10 +0200 Subject: [PATCH 320/392] Integrate copyediting for chapter 18 --- 18_http.md | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/18_http.md b/18_http.md index 3903b960b..19ebd29eb 100644 --- a/18_http.md +++ b/18_http.md @@ -4,7 +4,7 @@ {{quote {author: "Tim Berners-Lee", chapter: true} -What was often difficult for people to understand about the design was that there was nothing else beyond URLs, HTTP and HTML. There was no central computer "controlling" the Web, no single network on which these protocols worked, not even an organisation anywhere that "ran" the Web. The Web was not a physical "thing" that existed in a certain "place". It was a "space" in which information could exist. +What was often difficult for people to understand about the design was that there was nothing else beyond URLs, HTTP and HTML. There was no central computer 'controlling' the Web, no single network on which these protocols worked, not even an organisation anywhere that 'ran' the Web. The Web was not a physical 'thing' that existed in a certain 'place'. It was a 'space' in which information could exist. quote}} @@ -14,13 +14,13 @@ quote}} {{index [browser, environment]}} -The _Hypertext Transfer Protocol_, already mentioned in [Chapter ?](browser#web), is the mechanism through which data is requested and provided on the ((World Wide Web)). This chapter describes the ((protocol)) in more detail and explains the way browser JavaScript has access to it. +The Hypertext Transfer Protocol, introduced in [Chapter ?](browser#web), is the mechanism through which data is requested and provided on the ((World Wide Web)). This chapter describes the ((protocol)) in more detail and explains the way browser JavaScript has access to it. ## The protocol {{index "IP address"}} -If you type _eloquentjavascript.net/18_http.html_ into your browser's ((address bar)), the ((browser)) first looks up the ((address)) of the server associated with _eloquentjavascript.net_ and tries to open a ((TCP)) ((connection)) to it on ((port)) 80, the default port for ((HTTP)) traffic. If the ((server)) exists and accepts the connection, the browser might send something like this: +If you type _eloquentjavascript.net/18_http.html_ in your browser's ((address bar)), the ((browser)) first looks up the ((address)) of the server associated with _eloquentjavascript.net_ and tries to open a ((TCP)) ((connection)) to it on ((port)) 80, the default port for ((HTTP)) traffic. If the ((server)) exists and accepts the connection, the browser might send something like this: ```{lang: http} GET /18_http.html HTTP/1.1 @@ -72,7 +72,7 @@ HTTP/1.1 200 OK {{index "200 (HTTP status code)", "error response", "404 (HTTP status code)"}} -Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the ((request)). 404 is probably the most famous HTTP status code—it means that the resource could not be found. Codes that start with 5 mean an error happened on the ((server)) and the request is not to blame. +Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the ((request)). The most famous HTTP status code is probably 404, which means that the resource could not be found. Codes that start with 5 mean an error happened on the ((server)) and the request is not to blame. {{index HTTP}} @@ -153,7 +153,7 @@ Content-type: application/x-www-form-urlencoded name=Jean&message=Yes%3F ``` -`GET` requests should be used for requests that do not have ((side effect))s but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as `POST`. Client-side software such as a browser knows that it shouldn't blindly make `POST` requests but will often implicitly make `GET` requests—for example to prefetch a resource it believes the user will soon need. +`GET` requests should be used for requests that do not have ((side effect))s but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as `POST`. Client-side software such as a browser knows that it shouldn't blindly make `POST` requests but will often implicitly make `GET` requests—to prefetch a resource it believes the user will soon need, for example. We'll come back to forms and how to interact with them from JavaScript [later in the chapter](http#forms). @@ -176,9 +176,9 @@ fetch("example/data.txt").then(response => { {{index "Response class", "status property", "headers property"}} -Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. +Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case-insensitive because header names are not supposed to be case-sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. -Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It can also be rejected if there is a network error or if the ((server)) that the request is addressed to can't be found. +Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It can also be rejected if there is a network error or if the ((server)) to which that the request is addressed can't be found. {{index [path, URL], "relative URL"}} @@ -216,7 +216,7 @@ The 405 status code means "method not allowed", an HTTP server's way of saying " {{index "Range header", "body property", "headers property"}} -To add a request body, you can include a `body` option. To set headers, there's the `headers` option. For example, this request includes a `Range` header, which instructs the server to return only part of a document. +To add a request body for a `PUT` or `POST` request, you can include a `body` option. To set headers, there's the `headers` option. For example, this request includes a `Range` header, which instructs the server to return only part of a document. ```{test: no} fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) @@ -261,13 +261,13 @@ When thinking in terms of remote procedure calls, HTTP is just a vehicle for com Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/user/larry`), which again returns the document representing the resource. -This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy of a resource on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around. +This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy of a resource on the client for fast access). The concepts used in HTTP, which are well-designed, can provide a helpful set of principles to design your server interface around. ## Security and HTTPS {{index "man-in-the-middle", security, HTTPS, [network, security]}} -Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route it may be inspected or even modified. +Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route, it may be inspected or even modified. {{index tampering}} @@ -279,7 +279,7 @@ If it is important that something remain secret, such as the ((password)) to you The secure ((HTTP)) protocol, used for ((URL))s starting with _https://_, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic ((certificate)) issued by a certificate authority that the browser recognizes. Next, all data going over the ((connection)) is encrypted in a way that should prevent eavesdropping and tampering. -Thus, when it works right, ((HTTPS)) prevents other people from impersonating the website you are trying to talk to _and_ from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a _lot_ safer than plain HTTP. +Thus, when it works right, ((HTTPS)) prevents other people from impersonating the website you are trying to talk to _and_ from snooping on your communication. It's not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a _lot_ safer than plain HTTP. {{id forms}} @@ -289,7 +289,7 @@ Forms were originally designed for the pre-JavaScript Web to allow web sites to {{index [DOM, fields]}} -But their elements are part of the DOM like the rest of the page, and the DOM elements that represent form ((field))s support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application. +However, the form elements are part of the DOM, like the rest of the page, and the DOM elements that represent form ((field))s support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application. {{index "form (HTML tag)"}} @@ -407,7 +407,7 @@ For some pages, the user is expected to want to interact with a form field immed {{index "tab key", keyboard, "tabindex attribute", "a (HTML tag)"}} -Browsers allow the user to move the focus through the document by pressing the [tab]{keyname} key to move to the next focusable element, and [shift-tab]{keyname} to move back to the previous element. By default, elements are visited in the order they appear in the document. It is possible to use the `tabindex` attribute to change this order. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first: +Browsers allow the user to move the focus through the document by pressing the [tab]{keyname} key to move to the next focusable element, and [shift-tab]{keyname} to move back to the previous element. By default, elements are visited in the order in which they appear in the document. It is possible to use the `tabindex` attribute to change this order. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first: ```{lang: html, focus: true} <input type="text" tabindex=1> <a href=".">(help)</a> @@ -416,7 +416,7 @@ Browsers allow the user to move the focus through the document by pressing the [ {{index "tabindex attribute"}} -By default, most types of HTML elements cannot be focused. But you can add a `tabindex` attribute to any element that will make it focusable. A `tabindex` of 0 makes an element focusable without affecting the focus order. +By default, most types of HTML elements cannot be focused. You can add a `tabindex` attribute to any element to make it focusable. A `tabindex` of 0 makes an element focusable without affecting the focus order. ## Disabled fields @@ -506,7 +506,7 @@ The `selectionStart` and `selectionEnd` properties of ((text field))s give us in {{index Khasekhemwy, "textarea (HTML tag)", keyboard, "event handling"}} -Imagine you are writing an article about Khasekhemwy but have some trouble spelling his name. The following code wires up a `<textarea>` tag with an event handler that, when you press F2, inserts the string "Khasekhemwy" for you. +Imagine you are writing an article about Khasekhemwy, last pharaoh of the Second Dynasty, but have some trouble spelling his name. The following code wires up a `<textarea>` tag with an event handler that, when you press F2, inserts the string "Khasekhemwy" for you. ```{lang: html} <textarea></textarea> @@ -535,7 +535,7 @@ The `replaceSelection` function replaces the currently selected part of a text f {{index "change event", "input event"}} -The `"change"` event for a ((text field)) does not fire every time something is typed. Rather, it fires when the field loses ((focus)) after its content was changed. To respond immediately to changes in a text field, you should register a handler for the `"input"` event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field's content. +The `"change"` event for a ((text field)) does not fire every time something is typed. Rather, it fires when the field loses ((focus)) after its content was changed. To respond immediately to changes in a text field, you should register a handler for the `"input"` event instead, which fires every time the user types a character, deletes text, or otherwise manipulates the field's content. The following example shows a text field and a counter displaying the current length of the text in the field: @@ -610,7 +610,7 @@ Select fields are conceptually similar to radio buttons—they also allow the us {{index "multiple attribute", "drop-down menu"}} -Select fields also have a variant that is more akin to a list of checkboxes, rather than radio boxes. When given the `multiple` attribute, a `<select>` tag will allow the user to select any number of options, rather than just a single option. Whereas a regular select field is drawn as a _drop-down_ control, which shows the inactive options only when you open it, a field with `multiple` enabled shows multiple options at the same time, allowing the user to enable or disable them individually. +Select fields also have a variant more akin to a list of checkboxes rather than radio boxes. When given the `multiple` attribute, a `<select>` tag will allow the user to select any number of options, rather than just a single option. Whereas a regular select field is drawn as a _drop-down_ control, which shows the inactive options only when you open it, a field with `multiple` enabled shows multiple options at the same time, allowing the user to enable or disable them individually. {{index "option (HTML tag)", "value attribute"}} @@ -728,7 +728,7 @@ Simple ((HTML)) pages with a bit of JavaScript can be a great format for "((mini {{index persistence, [binding, "as state"], [browser, storage]}} -When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there. We will see how to do that in [Chapter ?](node). But that's a lot of extra work and complexity. Sometimes it is enough to just keep the data in the ((browser)). +When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there (we'll see how to do that in [Chapter ?](node)). But that's a lot of extra work and complexity. Sometimes it's enough to just keep the data in the ((browser)). {{index "localStorage object", "setItem method", "getItem method", "removeItem method"}} @@ -743,7 +743,7 @@ localStorage.removeItem("username"); {{index "localStorage object"}} -A value in `localStorage` sticks around until it is overwritten, it is removed with `removeItem`, or the user clears their local data. +A value in `localStorage` sticks around until it is overwritten or is removed with `removeItem`, or the user clears their local data. {{index security}} @@ -807,13 +807,13 @@ Notes: <select></select> <button>Add</button><br> {{index "getItem method", JSON, "?? operator", "default value"}} -The script gets its starting state from the `"Notes"` value stored in `localStorage` or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from `localStorage` will yield `null`. Passing `null` to `JSON.parse` will make it parse the string `"null"` and return `null`. Thus, the `??` operator can be used to provide a default value in a situation like this. +The script gets its starting state from the `"Notes"` value stored in `localStorage` or, if that's missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from `localStorage` will yield `null`. Passing `null` to `JSON.parse` will make it parse the string `"null"` and return `null`. Thus, the `??` operator can be used to provide a default value in a situation like this. The `setState` method makes sure the DOM is showing a given state and stores the new state to `localStorage`. Event handlers call this function to move to a new state. {{index [object, creation], property, "computed property"}} -The `...` syntax in the example is used to create a new object that is a clone of the old `state.notes`, but with one property added or overwritten. It uses ((spread)) syntax to first add the properties from the old object, and then set a new property. The ((square brackets)) notation in the object literal is used to create a property whose name is based on some dynamic value. +The `...` syntax in the example is used to create a new object that is a clone of the old `state.notes`, but with one property added or overwritten. It uses ((spread)) syntax to first add the properties from the old object and then set a new property. The ((square brackets)) notation in the object literal is used to create a property whose name is based on some dynamic value. {{index "sessionStorage object", [browser, storage]}} @@ -833,9 +833,7 @@ fetch("/18_http.html").then(r => r.text()).then(text => { Browsers make `GET` requests to fetch the resources needed to display a web page. A page may also contain forms, which allow information entered by the user to be sent as a request for a new page when the form is submitted. -HTML can represent various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers. - -Such fields can be inspected and manipulated with JavaScript. They fire the `"change"` event when changed, fire the `"input"` event when text is typed, and receive keyboard events when they have keyboard focus. Properties like `value` (for text and select fields) or `checked` (for checkboxes and radio buttons) are used to read or set the field's content. +HTML can represent various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers. Such fields can be inspected and manipulated with JavaScript. They fire the `"change"` event when changed, fire the `"input"` event when text is typed, and receive keyboard events when they have keyboard focus. Properties like `value` (for text and select fields) or `checked` (for checkboxes and radio buttons) are used to read or set the field's content. When a form is submitted, a `"submit"` event is fired on it. A JavaScript handler can call `preventDefault` on that event to disable the browser's default behavior. Form field elements may also occur outside of a form tag. @@ -925,7 +923,7 @@ hint}} {{index "game of life (exercise)", "artificial life", "Conway's Game of Life"}} -Conway's Game of Life is a simple ((simulation)) that creates artificial "life" on a ((grid)), each cell of which is either alive or not. Each ((generation)) (turn), the following rules are applied: +Conway's Game of Life is a simple ((simulation)) that creates artificial "life" on a ((grid)), each cell of which is either alive or not. In each ((generation)) (turn), the following rules are applied: * Any live ((cell)) with fewer than two or more than three live ((neighbor))s dies. From 4ef5c70f19ec4c3203542144fcf8684fc68f98d7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Sun, 14 Apr 2024 00:42:27 +0200 Subject: [PATCH 321/392] Fix typo in chapter 10 --- 10_modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10_modules.md b/10_modules.md index db4e7c516..34cba76b3 100644 --- a/10_modules.md +++ b/10_modules.md @@ -99,7 +99,7 @@ console.log(nomDeJour(3)); // → Wednesday ``` -M module may also have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. +A module may also have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. ``` export default ["Winter", "Spring", "Summer", "Autumn"]; From 42a031f5c9dc0c4a8a2ac103455e3a1ef3c6c19e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Sun, 14 Apr 2024 00:43:58 +0200 Subject: [PATCH 322/392] Fix typo in chapter 1 Closes https://github.com/marijnh/Eloquent-JavaScript/pull/583 --- 01_values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/01_values.md b/01_values.md index 843a47e06..fd9254b71 100644 --- a/01_values.md +++ b/01_values.md @@ -163,7 +163,7 @@ To make it possible to include such characters in a string, the following notati "This is the first line\nAnd this is the second" ``` -This is the actual text is that string: +This is the actual text in that string: ```{lang: null} This is the first line From 4fecad2352aa5c1d38d7d6d1481bb0df4afb8d3d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 16 Apr 2024 10:41:58 +0200 Subject: [PATCH 323/392] Integrate copyediting for chapter 19 --- 19_paint.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/19_paint.md b/19_paint.md index b3fe33cb2..2c59c9150 100644 --- a/19_paint.md +++ b/19_paint.md @@ -16,7 +16,7 @@ The material from the previous chapters gives you all the elements you need to b {{index [file, image]}} -Our ((application)) will be a ((pixel)) ((drawing)) program, where you can modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, scribble on them with your mouse or other pointer device, and save them. This is what it will look like: +Our ((application)) will be a ((pixel))-((drawing)) program that allows you to modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, scribble on them with your mouse or other pointer device, and save them. This is what it will look like: {{figure {url: "img/pixel_editor.png", alt: "Screenshot of the pixel editor interface, with a grid of colored pixels at the top and a number of controls, in the form of HTML fields and buttons, below that", width: "8cm"}}} @@ -34,7 +34,7 @@ We will structure the editor interface as a number of _((component))s_, objects {{index [state, "of application"]}} -The state of the application consists of the current picture, the selected tool, and the selected color. We'll set things up so that the state lives in a single value, and the interface components always base the way they look on the current state. +The state of the application consists of the current picture, the selected tool, and the selected color. We'll set things up so that the state lives in a single value and the interface components always base the way they look on the current state. To see why this is important, let's consider the alternative—distributing pieces of state throughout the interface. Up to a certain point, this is easier to program. We can just put in a ((color field)) and read its value when we need to know the current color. @@ -102,7 +102,7 @@ class Picture { {{index "side effect", "persistent data structure"}} -We want to be able to treat a picture as an ((immutable)) value, for reasons that we'll get back to later in the chapter. But we also sometimes need to update a whole bunch of pixels at a time. To be able to do that, the class has a `draw` method that expects an array of updated pixels—objects with `x`, `y`, and `color` properties—and creates a new picture with those pixels overwritten. This method uses `slice` without arguments to copy the entire pixel array—the start of the slice defaults to 0, and the end defaults to the array's length. +We want to be able to treat a picture as an ((immutable)) value, for reasons we'll get back to later in the chapter. But we also sometimes need to update a whole bunch of pixels at a time. To be able to do that, the class has a `draw` method that expects an array of updated pixels—objects with `x`, `y`, and `color` properties—and creates a new picture with those pixels overwritten. This method uses `slice` without arguments to copy the entire pixel array—the start of the slice defaults to 0, and the end defaults to the array's length. {{index "Array constructor", "fill method", ["length property", "for array"], [array, creation]}} @@ -136,7 +136,7 @@ This pattern, in which object ((spread)) is used to first add the properties an {{index "createElement method", "elt function", [DOM, construction]}} -One of the main things that interface components do is creating DOM structure. We again don't want to directly use the verbose DOM methods for that, so here's a slightly expanded version of the `elt` function: +One of the main things that interface components do is create DOM structure. We again don't want to directly use the verbose DOM methods for that, so here's a slightly expanded version of the `elt` function: ```{includeCode: true} function elt(type, props, ...children) { @@ -395,7 +395,7 @@ function draw(pos, state, dispatch) { } ``` -The function immediately calls the `drawPixel` function but then also returns it so that it is called again for newly touched pixels when the user drags or ((swipe))s over the picture. +The function immediately calls the `drawPixel` function but then also returns it so that it's called again for newly touched pixels when the user drags or ((swipe))s over the picture. {{index "rectangle function"}} @@ -526,11 +526,11 @@ class SaveButton { {{index "canvas (HTML tag)"}} -The component keeps track of the current picture so that it can access it when saving. To create the image file, it uses a `<canvas>` element that it draws the picture on (at a scale of one pixel per pixel). +The component keeps track of the current picture so that it can access it when saving. To create the image file, it uses a `<canvas>` element on which it draws the picture (at a scale of one pixel per pixel). {{index "toDataURL method", "data URL"}} -The `toDataURL` method on a canvas element creates a URL that starts with `data:`. Unlike `http:` and `https:` URLs, data URLs contain the whole resource in the URL. They are usually very long, but they allow us to create working links to arbitrary pictures, right here in the browser. +The `toDataURL` method on a canvas element creates a URL that uses the `data:` scheme. Unlike `http:` and `https:` URLs, data URLs contain the whole resource in the URL. They are usually very long, but they allow us to create working links to arbitrary pictures, right here in the browser. {{index "a (HTML tag)", "download attribute"}} @@ -563,7 +563,7 @@ function startLoad(dispatch) { {{index [file, access], "input (HTML tag)"}} -To get access to a file on the user's computer, we need the user to select the file through a file input field. But I don't want the load button to look like a file input field, so we create the file input when the button is clicked and then pretend that this file input itself was clicked. +To get access to a file on the user's computer, we need the user to select the file through a file input field. But we don't want the load button to look like a file input field, so we create the file input when the button is clicked and then pretend that this file input itself was clicked. {{index "FileReader class", "img (HTML tag)", "readAsDataURL method", "Picture class"}} @@ -587,7 +587,7 @@ function finishLoad(file, dispatch) { {{index "canvas (HTML tag)", "getImageData method", "pictureFromImage function"}} -To get access to the pixels, we must first draw the picture to a `<canvas>` element. The canvas context has a `getImageData` method that allows a script to read its ((pixel))s. So, once the picture is on the canvas, we can access it and construct a `Picture` object. +To get access to the pixels, we must first draw the picture to a `<canvas>` element. The canvas context has a `getImageData` method that allows a script to read its ((pixel))s. So once the picture is on the canvas, we can access it and construct a `Picture` object. ```{includeCode: true} function pictureFromImage(image) { @@ -610,25 +610,25 @@ function pictureFromImage(image) { } ``` -We'll limit the size of images to 100 by 100 pixels since anything bigger will look _huge_ on our display and might slow down the interface. +We'll limit the size of images to 100 by 100 pixels, since anything bigger will look _huge_ on our display and might slow down the interface. {{index "getImageData method", color, transparency}} -The `data` property of the object returned by `getImageData` is an array of color components. For each pixel in the rectangle specified by the arguments, it contains four values, which represent the red, green, blue, and _((alpha))_ components of the pixel's color, as numbers between 0 and 255. The alpha part represents opacity—when it is zero, the pixel is fully transparent, and when it is 255, it is fully opaque. For our purpose, we can ignore it. +The `data` property of the object returned by `getImageData` is an array of color components. For each pixel in the rectangle specified by the arguments, it contains four values that represent the red, green, blue, and _((alpha))_ components of the pixel's color, as numbers between 0 and 255. The alpha part represents opacity—when it is 0, the pixel is fully transparent, and when it is 255, it is fully opaque. For our purpose, we can ignore it. {{index "hexadecimal number", "toString method"}} -The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 16^2^ = 256 different numbers. The `toString` method of numbers can be given a base as argument, so `n.toString(16)` will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the `hex` helper function calls `padStart` to add a leading zero when necessary. +The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 16^2^ = 256 different numbers. The `toString` method of numbers can be given a base as argument, so `n.toString(16)` will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the `hex` helper function calls `padStart` to add a leading 0 when necessary. We can load and save now! That leaves one more feature before we're done. ## Undo history -Half of the process of editing is making little mistakes and correcting them. So an important feature in a drawing program is an ((undo history)). +Because half the process of editing is making little mistakes and correcting them, an important feature in a drawing program is an ((undo history)). {{index "persistent data structure", [state, "of application"]}} -To be able to undo changes, we need to store previous versions of the picture. Since it's an ((immutable)) value, that is easy. But it does require an additional field in the application state. +To be able to undo changes, we need to store previous versions of the picture. Since pictures are ((immutable)) values, that's easy. But it does require an additional field in the application state. {{index "done property"}} @@ -636,7 +636,7 @@ We'll add a `done` array to keep previous versions of the ((picture)). Maintaini {{index "doneAt property", "historyUpdateState function", "Date.now function"}} -But we don't want to store _every_ change, only changes a certain amount of ((time)) apart. To be able to do that, we'll need a second property, `doneAt`, tracking the time at which we last stored a picture in the history. +We don't want to store _every_ change, though—just changes that are a certain amount of ((time)) apart. To be able to do that, we'll need a second property, `doneAt`, to track the time at which we last stored a picture in the history. ```{includeCode: true} function historyUpdateState(state, action) { @@ -750,7 +750,7 @@ At the same time, browser technology is ridiculous. You have to learn a large nu {{index standard, evolution}} -And though the situation is definitely improving, it mostly does so in the form of more elements being added to address shortcomings—creating even more ((complexity)). A feature used by a million websites can't really be replaced. Even if it could, it would be hard to decide what it should be replaced with. +While the situation is definitely improving, it mostly does so in the form of more elements being added to address shortcomings—creating even more ((complexity)). A feature used by a million websites can't really be replaced. Even if it could, it would be hard to decide what it should be replaced with. {{index "social factors", "economic factors", history}} @@ -937,7 +937,7 @@ hint}} {{index "proper lines (exercise)", "line drawing"}} -This is a more advanced exercise than the preceding two, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and ((patience)) before starting to work on this exercise, and do not get discouraged by initial failures. +This is a more advanced exercise than the preceding two, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and ((patience)) before starting to work on this exercise, and don't get discouraged by initial failures. {{index "draw function", "mousemove event", "touchmove event"}} @@ -947,7 +947,7 @@ Improve the `draw` tool to make it draw a full line. This means you have to make To do this, since the pixels can be an arbitrary distance apart, you'll have to write a general line drawing function. -A line between two pixels is a connected chain of pixels, as straight as possible, going from the start to the end. Diagonally adjacent pixels count as connected. So a slanted line should look like the picture on the left, not the picture on the right. +A line between two pixels is a connected chain of pixels, as straight as possible, going from the start to the end. Diagonally adjacent pixels count as connected. A slanted line should look like the picture on the left, not the picture on the right. {{figure {url: "img/line-grid.svg", alt: "Diagram of two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically", width: "6cm"}}} From 38c59298e6c867d245226177f4955e2618c0a97a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 16 Apr 2024 13:08:02 +0200 Subject: [PATCH 324/392] Integrate copyediting for chapter 20 --- 20_node.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/20_node.md b/20_node.md index d4569a123..731d1ba2a 100644 --- a/20_node.md +++ b/20_node.md @@ -14,7 +14,7 @@ quote}} {{index "command line"}} -So far, we have used the JavaScript language in a single environment: the browser. This chapter and the [next one](skillsharing) will briefly introduce ((Node.js)), a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP ((server))s that power dynamic ((website))s. +So far, we've used the JavaScript language in a single environment: the browser. This chapter and the [next one](skillsharing) will briefly introduce ((Node.js)), a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP ((server))s that power dynamic ((website))s. These chapters aim to teach you the main concepts that Node.js uses and to give you enough information to write useful programs for it. They do not try to be a complete, or even a thorough, treatment of the platform. @@ -105,15 +105,15 @@ Node started out using the ((CommonJS)) module system, based on the `require` fu {{index "import keyword", "ES modules"}} -But it also support the more modern ES module system. When a script's filename ends in `.mjs`, it is considered to be such a module, and you can use `import` and `export` in it (but not `require`). We will use ES modules in this chapter. +But today, Node also supports the more modern ES module system. When a script's filename ends in `.mjs`, it is considered to be such a module, and you can use `import` and `export` in it (but not `require`). We will use ES modules in this chapter. {{index [path, "file system"], "relative path", resolution}} -When importing a module—whether with `require` or `import`—Node has to resolve the given string to an actual ((file)) that it can load. Names that start with `/`, `./`, or `../` are resolved as files, relative to the current module's path. Here, `.` stands for the current directory, `../` for one directory up, and `/` for the root of the file system. So if you ask for `"./graph.mjs"` from the file `/tmp/robot/robot.mjs`, Node will try to load the file `/tmp/robot/graph.mjs`. +When importing a module—whether with `require` or `import`—Node has to resolve the given string to an actual ((file)) that it can load. Names that start with `/`, `./`, or `../` are resolved as files, relative to the current module's path. Here, `.` stands for the current directory, `../` for one directory up, and `/` for the root of the file system. If you ask for `"./graph.mjs"` from the file `/tmp/robot/robot.mjs`, Node will try to load the file `/tmp/robot/graph.mjs`. {{index "node_modules directory", directory}} -When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in file system module. And importing `"robot"` might try to load the library found in `node_modules/robot/`. A common way to install such libraries is by using ((NPM)), which we'll come back to in a moment. +When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in file system module. Importing `"robot"` might try to load the library found in `node_modules/robot/`. It's common to install such libraries is by using ((NPM)), which we'll return to in a moment. {{index "import keyword", "Node.js", "garble example"}} @@ -153,7 +153,7 @@ tpircSavaJ {{index NPM, "Node.js", "npm program", library}} -NPM, which was introduced in [Chapter ?](modules#modules_npm), is an online repository of JavaScript ((module))s, many of which are specifically written for Node. When you install Node on your computer, you also get the `npm` command, which you can use to interact with this repository. +NPM, introduced in [Chapter ?](modules#modules_npm), is an online repository of JavaScript ((module))s, many of which are specifically written for Node. When you install Node on your computer, you also get the `npm` command, which you can use to interact with this repository. {{index "ini package"}} @@ -214,11 +214,11 @@ NPM demands that its packages follow a schema called _((semantic versioning))_, {{index "caret character"}} -A caret character (`^`) in front of the version number for a dependency in `package.json` indicates that any version compatible with the given number may be installed. So, for example, `"^2.3.0"` would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed. +A caret character (`^`) in front of the version number for a dependency in `package.json` indicates that any version compatible with the given number may be installed. For example, `"^2.3.0"` would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed. {{index publishing}} -The `npm` command is also used to publish new packages or new versions of packages. If you run `npm publish` in a ((directory)) that has a `package.json` file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn't in use yet since it would not be good if random people could update existing packages. +The `npm` command is also used to publish new packages or new versions of packages. If you run `npm publish` in a ((directory)) that has a `package.json` file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn't in use yet, since it wouldn't be good if random people could update existing packages. This book won't delve further into the details of ((NPM)) usage. Refer to [_https://npmjs.org_](https://npmjs.org) for further documentation and a way to search for packages. @@ -242,7 +242,7 @@ readFile("file.txt", "utf8", (error, text) => { {{index "Buffer class"}} -The second argument to `readFile` indicates the _((character encoding))_ used to decode the file into a string. There are several ways in which ((text)) can be encoded to ((binary data)), but most modern systems use ((UTF-8)). So unless you have reasons to believe another encoding is used, pass `"utf8"` when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a `Buffer` object instead of a string. This is an ((array-like object)) that contains numbers representing the bytes (8-bit chunks of data) in the files. +The second argument to `readFile` indicates the _((character encoding))_ used to decode the file into a string. There are several ways in which ((text)) can be encoded to ((binary data)), but most modern systems use ((UTF-8)). Unless you have reasons to believe another encoding is used, pass `"utf8"` when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a `Buffer` object instead of a string. This is an ((array-like object)) that contains numbers representing the bytes (8-bit chunks of data) in the files. ``` import {readFile} from "node:fs"; @@ -289,7 +289,7 @@ readFile("file.txt", "utf8") {{index "synchronous programming", "node:fs package", "readFileSync function"}} -Sometimes you don't need asynchronicity, and it just gets in the way. Many of the functions in `node:fs` also have a synchronous variant, which has the same name with `Sync` added to the end. For example, the synchronous version of `readFile` is called `readFileSync`. +Sometimes you don't need asynchronicity and it just gets in the way. Many of the functions in `node:fs` also have a synchronous variant, which has the same name with `Sync` added to the end. For example, the synchronous version of `readFile` is called `readFileSync`. ``` import {readFileSync} from "node:fs"; @@ -299,7 +299,7 @@ console.log("The file contains:", {{index optimization, performance, blocking}} -Do note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays. +Note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays. ## The HTTP module @@ -332,7 +332,7 @@ If you run this script on your own machine, you can point your web browser at [_ The function passed as argument to `createServer` is called every time a client connects to the server. The `request` and `response` bindings are objects representing the incoming and outgoing data. The first contains information about the ((request)), such as its `url` property, which tells us to what URL the request was made. -So, when you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser. +When you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser. {{index "200 (HTTP status code)", "Content-Type header", "writeHead method"}} @@ -340,7 +340,7 @@ To send something to the client, you call methods on the `response` object. The {{index "writable stream", "body (HTTP)", stream, "write method", "end method"}} -Next, the actual response body (the document itself) is sent with `response.write`. You are allowed to call this method multiple times if you want to send the response piece by piece, for example to stream data to the client as it becomes available. Finally, `response.end` signals the end of the response. +Next, the actual response body (the document itself) is sent with `response.write`. You're allowed to call this method multiple times if you want to send the response piece by piece, for example to stream data to the client as it becomes available. Finally, `response.end` signals the end of the response. {{index "listen method"}} @@ -348,15 +348,15 @@ The call to `server.listen` causes the ((server)) to start waiting for connectio {{index "Node.js", "kill process"}} -When you run this script, the process just sits there and waits. When a script is listening for events—in this case, network connections—`node` will not automatically exit when it reaches the end of the script. To close it, press [control]{keyname}-C. +When you run this script, the process just sits there and waits. When a script is listening for events—in this case, network connections—`node` will not automatically exit when it reaches the end of the script. To close it, press [ctrl]{keyname}-C. {{index [method, HTTP]}} -A real web ((server)) usually does more than the one in the example—it looks at the request's ((method)) (the `method` property) to see what action the client is trying to perform and looks at the request's ((URL)) to find out which resource this action is being performed on. We'll see a more advanced server [later in this chapter](node#file_server). +A real web ((server)) usually does more than the one in the example—it looks at the request's ((method)) (the `method` property) to see what action the client is trying to perform and looks at the request's ((URL)) to find out on which resource this action is being performed. We'll see a more advanced server [later in this chapter](node#file_server). {{index "node:http package", "request function", "fetch function", [HTTP, client]}} -The `node:http` module also provides a `request` function, which can be used to make HTTP requests. However, it is a lot more cumbersome to use than `fetch`, which we saw in [Chapter ?](http). Fortunately, `fetch` is also available in Node, as a global binding. Unless you want to do something very specific, such as processing the response document piece by piece as the data comes in over the network, I recommend sticking to `fetch`. +The `node:http` module also provides a `request` function that can be used to make HTTP requests. However, it is a lot more cumbersome to use than `fetch`, which we saw in [Chapter ?](http). Fortunately, `fetch` is also available in Node, as a global binding. Unless you want to do something very specific, such as processing the response document piece by piece as the data comes in over the network, I recommend sticking to `fetch`. ## Streams @@ -370,7 +370,7 @@ It is possible to create a writable stream that points at a file with the `creat {{index "createServer function", "request function", "event handling", "readable stream"}} -_Readable ((stream))s_ are a little more involved. The `request` argument to the HTTP server's callback is a readable stream. Reading from a stream is done using event handlers, rather than methods. +_Readable ((stream))s_ are a little more involved. The `request` argument to the HTTP server's callback is a readable stream. Reading from a stream is done using event handlers rather than methods. {{index "on method", "addEventListener method"}} @@ -440,7 +440,7 @@ createServer((request, response) => { return {body: String(error), status: 500}; }).then(({body, status = 200, type = "text/plain"}) => { response.writeHead(status, {"Content-Type": type}); - if (body && body.pipe) body.pipe(response); + if (body?.pipe) body.pipe(response); else response.end(body); }); }).listen(8000); @@ -467,7 +467,7 @@ The `status` field of the response description may be omitted, in which case it {{index "end method", "pipe method", stream}} -When the value of `body` is a ((readable stream)), it will have a `pipe` method that is used to forward all content from a readable stream to a ((writable stream)). If not, it is assumed to be either `null` (no body), a string, or a buffer, and it is passed directly to the ((response))'s `end` method. +When the value of `body` is a ((readable stream)), it will have a `pipe` method that we can use to forward all content from a readable stream to a ((writable stream)). If not, it is assumed to be either `null` (no body), a string, or a buffer, and it is passed directly to the ((response))'s `end` method. {{index [path, URL], "urlPath function", "URL class", parsing, [escaping, "in URLs"], "decodeURIComponent function", "startsWith method"}} @@ -492,11 +492,11 @@ function urlPath(url) { As soon as you set up a program to accept network requests, you have to start worrying about ((security)). In this case, if we aren't careful, it is likely that we'll accidentally expose our whole ((file system)) to the network. -File paths are strings in Node. To map such a string to an actual file, there is a nontrivial amount of interpretation going on. Paths may, for example, include `../` to refer to a parent directory. So one obvious source of problems would be requests for paths like `/../secret_file`. +File paths are strings in Node. To map such a string to an actual file, there's a nontrivial amount of interpretation going on. Paths may, for example, include `../` to refer to a parent directory. One obvious source of problems would be requests for paths like `/../secret_file`. {{index "node:path package", "resolve function", "cwd function", "process object", "403 (HTTP status code)", "sep binding", ["backslash character", "as path separator"], "slash character"}} -To avoid such problems, `urlPath` uses the `resolve` function from the `node:path` module, which resolves relative paths. It then verifies that the result is _below_ the working directory. The `process.cwd` function (where `cwd` stands for "current working directory") can be used to find this working directory. The `sep` binding from the `node:path` package is the system's path separator—a backslash on Windows and a forward slash on most other systems. When the path doesn't start with the base directory, the function throws an error response object, using the HTTP status code indicating that access to the resource is forbidden. +To avoid such problems, `urlPath` uses the `resolve` function from the `node:path` module, which resolves relative paths. It then verifies that the result is _below_ the working directory. The `process.cwd` function (where `cwd` stands for _current working directory_) can be used to find this working directory. The `sep` binding from the `node:path` package is the system's path separator—a backslash on Windows and a forward slash on most other systems. When the path doesn't start with the base directory, the function throws an error response object, using the HTTP status code indicating that access to the resource is forbidden. {{index "file server example", "Node.js", "GET method", [file, resource]}} @@ -583,7 +583,7 @@ When an ((HTTP)) ((response)) does not contain any data, the status code 204 ("n {{index idempotence, "error response"}} -You may be wondering why trying to delete a nonexistent file returns a success status code, rather than an error. When the file that is being deleted is not there, you could say that the request's objective is already fulfilled. The ((HTTP)) standard encourages us to make requests _idempotent_, which means that making the same request multiple times produces the same result as making it once. In a way, if you try to delete something that's already gone, the effect you were trying to do has been achieved—the thing is no longer there. +You may be wondering why trying to delete a nonexistent file returns a success status code rather than an error. When the file being deleted is not there, you could say that the request's objective is already fulfilled. The ((HTTP)) standard encourages us to make requests _idempotent_, which means that making the same request multiple times produces the same result as making it once. In a way, if you try to delete something that's already gone, the effect you were trying to create has been achieved—the thing is no longer there. {{index "file server example", "Node.js", "PUT method"}} @@ -614,7 +614,7 @@ We don't need to check whether the file exists this time—if it does, we'll jus {{index "error event", "finish event"}} -When something goes wrong when opening the file, `createWriteStream` will still return a stream, but that stream will fire an `"error"` event. The stream from the request may also fail, for example if the network goes down. So we wire up both streams' `"error"` events to reject the promise. When `pipe` is done, it will close the output stream, which causes it to fire a `"finish"` event. That's the point where we can successfully resolve the promise (returning nothing). +When something goes wrong when opening the file, `createWriteStream` will still return a stream, but that stream will fire an `"error"` event. The stream from the request may also fail, for example if the network goes down. So we wire up both streams' `"error"` events to reject the promise. When `pipe` is done, it will close the output stream, which causes it to fire a `"finish"` event. That's the point at which we can successfully resolve the promise (returning nothing). {{index download, "file server example", "Node.js"}} @@ -641,7 +641,7 @@ The first request for `file.txt` fails since the file does not exist yet. The `P {{index "Node.js"}} -Node is a nice, small system that lets us run JavaScript in a nonbrowser context. It was originally designed for network tasks to play the role of a _node_ in a network. But it lends itself to all kinds of scripting tasks, and if writing JavaScript is something you enjoy, automating tasks with Node works well. +Node is a nice, small system that lets us run JavaScript in a nonbrowser context. It was originally designed for network tasks to play the role of a node in a network, but it lends itself to all kinds of scripting tasks. If writing JavaScript is something you enjoy, automating tasks with Node may work well for you. NPM provides packages for everything you can think of (and quite a few things you'd probably never think of), and it allows you to fetch and install those packages with the `npm` program. Node comes with a number of built-in modules, including the `node:fs` module for working with the file system and the `node:http` module for running HTTP servers. @@ -655,7 +655,7 @@ All input and output in Node is done asynchronously, unless you explicitly use a On ((Unix)) systems, there is a command line tool called `grep` that can be used to quickly search files for a ((regular expression)). -Write a Node script that can be run from the ((command line)) and acts somewhat like `grep`. It treats its first command line argument as a regular expression and treats any further arguments as files to search. It should output the names of any file whose content matches the regular expression. +Write a Node script that can be run from the ((command line)) and acts somewhat like `grep`. It treats its first command line argument as a regular expression and treats any further arguments as files to search. It outputs the names of any file whose content matches the regular expression. When that works, extend it so that when one of the arguments is a ((directory)), it searches through all files in that directory and its subdirectories. @@ -725,7 +725,7 @@ hint}} {{index "public space (exercise)", "file server example", "Content-Type header", website}} -Since the file server serves up any kind of file and even includes the right `Content-Type` header, you can use it to serve a website. Since it allows everybody to delete and replace files, it would be an interesting kind of website: one that can be modified, improved, and vandalized by everybody who takes the time to make the right HTTP request. +Since the file server serves up any kind of file and even includes the right `Content-Type` header, you can use it to serve a website. Given that this server allows everybody to delete and replace files, this would make for an interesting kind of website: one that can be modified, improved, and vandalized by everybody who takes the time to make the right HTTP request. Write a basic ((HTML)) page that includes a simple JavaScript file. Put the files in a directory served by the file server and open them in your browser. @@ -737,7 +737,7 @@ Start by making only a single file editable. Then make it so that the user can s {{index overwriting}} -Don't work directly in the code exposed by the file server since if you make a mistake, you are likely to damage the files there. Instead, keep your work outside of the publicly accessible directory and copy it there when testing. +Don't work directly in the code exposed by the file server, since if you make a mistake, you are likely to damage the files there. Instead, keep your work outside of the publicly accessible directory and copy it there when testing. {{hint From e5e034a7cb889264cc6c9fd199336512257a21bd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 16 Apr 2024 13:50:19 +0200 Subject: [PATCH 325/392] Integrate copyediting for chapter 21 --- 21_skillsharing.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/21_skillsharing.md b/21_skillsharing.md index c9f028638..30438aeda 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -14,7 +14,7 @@ quote}} A _((skill-sharing))_ meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a ((gardening)) skill-sharing meeting, someone might explain how to cultivate ((celery)). Or in a programming skill-sharing group, you could drop by and tell people about Node.js. -In this final project chapter, our goal is to set up a ((website)) for managing ((talk))s given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about ((unicycling)). The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves, without an active organizer. +In this final project chapter, our goal is to set up a ((website)) for managing ((talk))s given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about ((unicycling)). The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves without an active organizer. [Just like in the [previous chapter](node), some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work.]{if interactive} The full code for the project can be ((download))ed from [_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip). @@ -44,13 +44,11 @@ A common solution to this problem is called _((long polling))_, which happens to To be able to immediately notify a client that something changed, we need a ((connection)) to that client. Since web browsers do not traditionally accept connections and clients are often behind ((router))s that would block such connections anyway, having the server initiate this connection is not practical. -We can arrange for the client to open the connection and keep it around so that the server can use it to send information when it needs to do so. - {{index socket}} -But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called _((WebSockets))_ that makes it possible to open ((connection))s for arbitrary data exchange. But using them properly is somewhat tricky. +We can arrange for the client to open the connection and keep it around so that the server can use it to send information when it needs to do so. But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that's it. A technology called _((WebSockets))_ makes it possible to open ((connection))s for arbitrary data exchange, but using such sockets properly is somewhat tricky. -In this chapter, we use a simpler technique—((long polling))—where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report. +In this chapter, we use a simpler technique, ((long polling)), where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report. {{index "live view"}} @@ -155,7 +153,7 @@ Content-Length: 295 {{index security}} -The protocol described here does not do any ((access control)). Everybody can comment, modify talks, and even delete them. (Since the Internet is full of ((hooligan))s, putting such a system online without further protection probably wouldn't end well.) +The protocol described here doesn't do any ((access control)). Everybody can comment, modify talks, and even delete them. (Since the Internet is full of ((hooligan))s, putting such a system online without further protection probably wouldn't end well.) ## The server @@ -167,7 +165,7 @@ Let's start by building the ((server))-side part of the program. The code in thi {{index "createServer function", [path, URL], [method, HTTP]}} -Our server will use Node's `createServer` to start an HTTP server. In the function that handles a new request, we must distinguish between the various kinds of requests (as determined by the method and the path) that we support. This can be done with a long chain of `if` statements, but there is a nicer way. +Our server will use Node's `createServer` to start an HTTP server. In the function that handles a new request, we must distinguish between the various kinds of requests (as determined by the method and the path) that we support. This can be done with a long chain of `if` statements, but there's a nicer way. {{index dispatch}} @@ -209,11 +207,11 @@ Handler functions are called with the `context` value given to `resolve`. We wil ### Serving files -When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the `public` directory. It would be possible to use the file server defined in [Chapter ?](node#file_server) to serve such files, but we neither need nor want to support `PUT` and `DELETE` requests on files, and we would like to have advanced features such as support for caching. So let's use a solid, well-tested ((static file)) server from ((NPM)) instead. +When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the `public` directory. It would be possible to use the file server defined in [Chapter ?](node#file_server) to serve such files, but we neither need nor want to support `PUT` and `DELETE` requests on files, and we would like to have advanced features such as support for caching. Let's use a solid, well-tested ((static file)) server from ((NPM)) instead. {{index "createServer function", "serve-static package"}} -I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server from `"node:http"`, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests that we should handle specially, as defined in the router, so we wrap it in another function. +I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server from `"node:http"`, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests we should handle specially, as defined in the router, so we wrap it in another function. ```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} import {createServer} from "node:http"; @@ -247,9 +245,9 @@ class SkillShareServer { } ``` -The `serveFromRouter` function has the same interface as `fileServer`, taking `(request, response, next)` arguments. This allows us to “chain” several request handlers, allowing each to either handle the request, or pass responsibility for that on to the next handler. The final handler, `notFound`, simply responds with a “not found” error. +The `serveFromRouter` function has the same interface as `fileServer`, taking `(request, response, next)` arguments. We can use this to “chain” several request handlers, allowing each to either handle the request or pass responsibility for that on to the next handler. The final handler, `notFound`, simply responds with a “not found” error. -Our `serveFromRouter` function uses a similar convention as the file server from the [previous chapter](node) for responses—handlers in the router return promises that resolve to objects describing the response. +Our `serveFromRouter` function uses a similar convention to the file server from the [previous chapter](node) for responses—handlers in the router return promises that resolve to objects describing the response. ```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} import {Router} from "./router.mjs"; @@ -274,7 +272,7 @@ async function serveFromRouter(server, request, ### Talks as resources -The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. We will add some handlers to our router that expose these as HTTP ((resource))s under `/talks/[title]`. +The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. We'll add some handlers to our router that expose these as HTTP ((resource))s under `/talks/[title]`. {{index "GET method", "404 (HTTP status code)" "hasOwn function"}} @@ -392,7 +390,7 @@ SkillShareServer.prototype.talkResponse = function() { {{index "query string", "url package", parsing}} -The handler itself needs to look at the request headers to see whether `If-None-Match` and `Prefer` headers are present. Node stores headers, whose names are specified to be case insensitive, under their lowercase names. +The handler itself needs to look at the request headers to see whether `If-None-Match` and `Prefer` headers are present. Node stores headers, whose names are specified to be case-insensitive, under their lowercase names. ```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} router.add("GET", /^\/talks$/, async (server, request) => { @@ -479,7 +477,7 @@ Thus, if we want a page to show up when a browser is pointed at our server, we s {{index CSS}} -It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks. Then it adds a heading at the top of the page and loads the script that contains the ((client))-side application. +It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks. It then adds a heading at the top of the page and loads the script that contains the ((client))-side application. ### Actions @@ -551,7 +549,7 @@ function talkURL(title) { {{index "error handling", "user experience", "reportError function"}} -When the request fails, we don't want to have our page just sit there, doing nothing without explanation. So we define a function called `reportError`, which at least shows the user a dialog that tells them something went wrong. +When the request fails, we don't want our page to just sit there doing nothing without explanation. The function called `reportError`, which we used as `catch` handler, shows the user a crude dialog to tell them something went wrong. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function reportError(error) { @@ -563,7 +561,7 @@ function reportError(error) { {{index "renderUserField function"}} -We'll use an approach similar to the one we saw in [Chapter ?](paint), splitting the application into components. But since some of the components either never need to update or are always fully redrawn when updated, we'll define those not as classes but as functions that directly return a DOM node. For example, here is a component that shows the field where the user can enter their name: +We'll use an approach similar to the one we saw in [Chapter ?](paint), splitting the application into components. However, since some of the components either never need to update or are always fully redrawn when updated, we'll define those not as classes but as functions that directly return a DOM node. For example, here is a component that shows the field where the user can enter their name: ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function renderUserField(name, dispatch) { @@ -668,7 +666,7 @@ function renderTalkForm(dispatch) { {{index "pollTalks function", "long polling", "If-None-Match header", "Prefer header", "fetch function"}} -To start the app we need the current list of talks. Since the initial load is closely related to the long polling process—the `ETag` from the load must be used when polling—we'll write a function that keeps polling the server for `/talks` and calls a ((callback function)) when a new set of talks is available. +To start the app, we need the current list of talks. Since the initial load is closely related to the long polling process—the `ETag` from the load must be used when polling—we'll write a function that keeps polling the server for `/talks` and calls a ((callback function)) when a new set of talks is available. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} async function pollTalks(update) { @@ -770,7 +768,7 @@ If you run the server and open two browser windows for [_http://localhost:8000_] {{index "Node.js", NPM}} -The following exercises will involve modifying the system defined in this chapter. To work on them, make sure you ((download)) the code first ([_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip)), have Node installed ([_https://nodejs.org_](https://nodejs.org)), and install the project's dependency with `npm install`. +The following exercises will involve modifying the system defined in this chapter. To work on them, make sure you've ((download))ed the code ([_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip)), installed Node ([_https://nodejs.org_](https://nodejs.org)), and installed the project's dependency with `npm install`. ### Disk persistence @@ -780,7 +778,7 @@ The skill-sharing server keeps its data purely in memory. This means that when i {{index "hard drive"}} -Extend the server so that it stores the talk data to disk and automatically reloads the data when it is restarted. Do not worry about efficiency—do the simplest thing that works. +Extend the server so that it stores the talk data to disk and automatically reloads the data when it is restarted. Don't worry about efficiency—do the simplest thing that works. {{hint From b71d3b205525097f5dedb63d057386019e4312b8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 1 May 2024 09:41:16 +0200 Subject: [PATCH 326/392] Add missing u flag to regexp in solution 9.1 --- code/solutions/09_1_regexp_golf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/solutions/09_1_regexp_golf.js b/code/solutions/09_1_regexp_golf.js index 702f38233..ad84ccd4f 100644 --- a/code/solutions/09_1_regexp_golf.js +++ b/code/solutions/09_1_regexp_golf.js @@ -24,7 +24,7 @@ verify(/\p{L}{7}/u, ["Siebentausenddreihundertzweiundzwanzig"], ["no", "three small words"]); -verify(/(^|\P{L})[^\P{L}e]+($|\P{L})/i, +verify(/(^|\P{L})[^\P{L}e]+($|\P{L})/ui, ["red platypus", "wobbling nest"], ["earth bed", "bedrøvet abe", "BEET"]); From 1b532bd160f594af8ebaf1086be67a0dff409550 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 1 May 2024 09:43:35 +0200 Subject: [PATCH 327/392] Use ES module syntax in the solution to 10.2 --- code/solutions/10_2_roads_module.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/solutions/10_2_roads_module.js b/code/solutions/10_2_roads_module.js index ec83e579b..af405b799 100644 --- a/code/solutions/10_2_roads_module.js +++ b/code/solutions/10_2_roads_module.js @@ -1,4 +1,4 @@ -const {buildGraph} = require("./graph"); +import {buildGraph} from "./graph"; const roads = [ "Alice's House-Bob's House", "Alice's House-Cabin", @@ -10,4 +10,4 @@ const roads = [ "Marketplace-Town Hall", "Shop-Town Hall" ]; -exports.roadGraph = buildGraph(roads.map(r => r.split("-"))); +export const roadGraph = buildGraph(roads.map(r => r.split("-"))); From a2dd646ef2c685a5877f8c34565e3c84d6736fd0 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 3 May 2024 14:15:56 +0200 Subject: [PATCH 328/392] Remove credit for picture that's no longer used --- epub/frontmatter.xhtml | 3 +-- html/index.html | 5 ++--- pdf/book.tex | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/epub/frontmatter.xhtml b/epub/frontmatter.xhtml index d9dfbec3e..2e9f53317 100644 --- a/epub/frontmatter.xhtml +++ b/epub/frontmatter.xhtml @@ -23,8 +23,7 @@ Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular expression diagrams in Chapter 9 generated with <a href="https://regexper.com/">regexper.com</a> by Jeff - Avallone. Village photograph in Chapter 11 by Fabrice Creuzot. - Game concept for Chapter 16 + Avallone. Game concept for Chapter 16 by <a href="http://lessmilk.com">Thomas Palef</a>.</p> <p>A paper version of Eloquent JavaScript, including a bonus diff --git a/html/index.html b/html/index.html index f5ca4eb00..f6e309b7b 100644 --- a/html/index.html +++ b/html/index.html @@ -48,9 +48,8 @@ <h1>Eloquent JavaScript<div style="font-size: 70%">4th edition (2024)</div></h2> Tantareanu</a>. Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular expression diagrams in Chapter 9 generated with <a href="https://regexper.com">regexper.com</a> by Jeff - Avallone. Village photograph in Chapter 11 by Fabrice Creuzot. Game - concept for Chapter 16 by <a href="http://lessmilk.com">Thomas - Palef</a>.</p> + Avallone. Game concept for Chapter 16 + by <a href="http://lessmilk.com">Thomas Palef</a>.</p> </div> </div> diff --git a/pdf/book.tex b/pdf/book.tex index 673224e9b..517b55cd1 100644 --- a/pdf/book.tex +++ b/pdf/book.tex @@ -101,9 +101,8 @@ Péchane Sumi-e. Chapter illustrations by Madalina Tantareanu. Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular expression diagrams in Chapter 9 generated with - \href{http://regexper.com}{regexper.com} by Jeff Avallone. Village - photograph in Chapter 11 by Fabrice Creuzot. Game concept for - Chapter 16 by \href{http://lessmilk.com}{Thomas Palef}. + \href{http://regexper.com}{regexper.com} by Jeff Avallone. Game + concept for Chapter 16 by \href{http://lessmilk.com}{Thomas Palef}. \vskip 1em From de7d7bb210cb186e02164e028c9b79524988200a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Sat, 11 May 2024 15:26:11 +0200 Subject: [PATCH 329/392] Copyediting for Chapter 5 --- 04_data.md | 6 +++--- 05_higher_order.md | 36 ++++++++++++++++++------------------ 14_dom.md | 2 +- 16_game.md | 2 +- 17_canvas.md | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/04_data.md b/04_data.md index 54e19c813..a1ac4f27f 100644 --- a/04_data.md +++ b/04_data.md @@ -415,9 +415,9 @@ console.log(phi([76, 9, 4, 1])); This is a direct translation of the _ϕ_ formula into JavaScript. `Math.sqrt` is the square root function, as provided by the `Math` object in a standard JavaScript environment. We have to add two fields from the table to get fields like [n~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} because the sums of rows or columns are not stored directly in our data structure. -{{index "JOURNAL data set"}} +{{index "JOURNAL dataset"}} -Jacques keeps his journal for three months. The resulting ((data set)) is available in the [coding sandbox](https://eloquentjavascript.net/code#4) for this chapter[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book}, where it is stored in the `JOURNAL` binding, and in a downloadable [file](https://eloquentjavascript.net/code/journal.js). +Jacques keeps his journal for three months. The resulting ((dataset)) is available in the [coding sandbox](https://eloquentjavascript.net/code#4) for this chapter[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book}, where it is stored in the `JOURNAL` binding, and in a downloadable [file](https://eloquentjavascript.net/code/journal.js). {{index "tableFor function"}} @@ -484,7 +484,7 @@ When a `for` loop uses the word `of` after its variable definition, it will loop {{index journal, "weresquirrel example", "journalEvents function"}} -We need to compute a correlation for every type of event that occurs in the data set. To do that, we first need to _find_ every type of event. +We need to compute a correlation for every type of event that occurs in the dataset. To do that, we first need to _find_ every type of event. {{index "includes method", "push method"}} diff --git a/05_higher_order.md b/05_higher_order.md index 1dc9d912d..7701331cf 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -139,7 +139,7 @@ console.log(labels); {{index "loop body", [braces, body], [parentheses, arguments]}} -This is structured a little like a `for` loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to `repeat`. This is why it has to be closed with the closing brace _and_ closing parenthesis. In cases like this example where the body is a single small expression, you could also omit the braces and write the loop on a single line. +This is structured a little like a `for` loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to `repeat`. This is why it has to be closed with the closing brace _and_ closing parenthesis. In cases like this example, where the body is a single small expression, you could also omit the braces and write the loop on a single line. ## Higher-order functions @@ -204,19 +204,19 @@ There is a built-in array method, `forEach`, that provides something like a `for {{id scripts}} -## Script data set +## Script dataset -One area where higher-order functions shine is data processing. To process data, we'll need some actual example data. This chapter will use a ((data set)) about scripts—((writing system))s such as Latin, Cyrillic, or Arabic. +One area where higher-order functions shine is data processing. To process data, we'll need some actual example data. This chapter will use a ((dataset)) about scripts—((writing system))s such as Latin, Cyrillic, or Arabic. -Remember ((Unicode)) from [Chapter ?](values#unicode), the system that assigns a number to each character in written language? Most of these characters are associated with a specific script. The standard contains 140 different scripts, of which 81 are still in use today and 59 are historic. +Remember ((Unicode)), the system that assigns a number to each character in written language, from [Chapter ?](values#unicode)? Most of these characters are associated with a specific script. The standard contains 140 different scripts, of which 81 are still in use today and 59 are historic. Though I can fluently read only Latin characters, I appreciate the fact that people are writing texts in at least 80 other writing systems, many of which I wouldn't even recognize. For example, here's a sample of ((Tamil)) handwriting: {{figure {url: "img/tamil.png", alt: "A line of verse in Tamil handwriting. The characters are relatively simple, and neatly separated, yet completely different from Latin."}}} -{{index "SCRIPTS data set"}} +{{index "SCRIPTS dataset"}} -The example ((data set)) contains some pieces of information about the 140 scripts defined in Unicode. It is available in the [coding sandbox](https://eloquentjavascript.net/code#5) for this chapter[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} as the `SCRIPTS` binding. The binding contains an array of objects, each of which describes a script: +The example ((dataset)) contains some pieces of information about the 140 scripts defined in Unicode. It is available in the [coding sandbox](https://eloquentjavascript.net/code#5) for this chapter[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} as the `SCRIPTS` binding. The binding contains an array of objects, each of which describes a script: ```{lang: "json"} @@ -240,7 +240,7 @@ The `ranges` property contains an array of Unicode character ((range))s, each of {{index [array, methods], [array, filtering], "filter method", [function, "higher-order"], "predicate function"}} -If we want to find the scripts in the data set that are still in use, the following function might be helpful. It filters out elements in an array that don't pass a test. +If we want to find the scripts in the dataset that are still in use, the following function might be helpful. It filters out elements in an array that don't pass a test. ``` function filter(array, test) { @@ -327,7 +327,7 @@ console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); // → 10 ``` -{{index "reduce method", "SCRIPTS data set"}} +{{index "reduce method", "SCRIPTS dataset"}} The standard array method `reduce`, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the `start` argument. The method will take the first element of the array as its start value and start reducing at the second element. @@ -355,7 +355,7 @@ console.log(SCRIPTS.reduce((a, b) => { The `characterCount` function reduces the ranges assigned to a script by summing their sizes. Note the use of destructuring in the parameter list of the reducer function. The second call to `reduce` then uses this to find the largest script by repeatedly comparing two scripts and returning the larger one. -The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the data set. Han is a script sometimes used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (US-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called _Han unification_ and still makes some people very angry. +The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the dataset. Han is a script sometimes used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (US-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called _Han unification_ and still makes some people very angry. ## Composability @@ -381,7 +381,7 @@ There are a few more bindings, and the program is four lines longer, but it is s {{id average_function}} -The abstractions provided by these functions really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the data set: +The abstractions these functions provide really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the dataset: ``` function average(array) { @@ -420,9 +420,9 @@ In terms of what the computer is actually doing, these two approaches are also q ## Strings and character codes -{{index "SCRIPTS data set"}} +{{index "SCRIPTS dataset"}} -One interesting use of this data set would be figuring out what script a piece of text is using. Let's go through a program that does this. +One interesting use of this dataset would be figuring out what script a piece of text is using. Let's go through a program that does this. Remember that each script has an array of character code ranges associated with it. Given a character code, we could use a function like this to find the corresponding script (if any): @@ -494,7 +494,7 @@ If you have a character (which will be a string of one or two code units), you c ## Recognizing text -{{index "SCRIPTS data set", "countBy function", [array, counting]}} +{{index "SCRIPTS dataset", "countBy function", [array, counting]}} We have a `characterScript` function and a way to correctly loop over characters. The next step is to count the characters that belong to each script. The following counting abstraction will be useful there: @@ -521,7 +521,7 @@ The `countBy` function expects a collection (anything that we can loop over with {{index "find method"}} -It uses another array method, `find`, which goes over the elements in the array and returns the first one for which a function returns true. It returns `undefined` when no such element is found. +It uses another array method, `find`, which goes over the elements in the array and returns the first one for which a function returns true. It returns `undefined` when it finds no such element. {{index "textScripts function", "Chinese characters"}} @@ -552,13 +552,13 @@ The function first counts the characters by name, using `characterScript` to ass {{index "reduce method", "map method", "join method", [array, methods]}} -To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If no such characters are found, the function returns a specific string. Otherwise it transforms the counting entries into readable strings with `map` and then combines them with `join`. +To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If we find no such characters, the function returns a specific string. Otherwise it transforms the counting entries into readable strings with `map` and then combines them with `join`. ## Summary Being able to pass function values to other functions is a deeply useful aspect of JavaScript. It allows us to write functions that model computations with "gaps" in them. The code that calls these functions can fill in the gaps by providing function values. -Arrays provide a number of useful higher-order methods. You can use `forEach` to loop over the elements in an array. The `filter` method returns a new array containing only the elements that pass the ((predicate function)). Transforming an array by putting each element through a function is done with `map`. You can use `reduce` to combine all the elements in an array into a single value. The `some` method tests whether any element matches a given predicate function, while `find` finds the first element that matches a predicate. +Arrays provide a number of useful higher-order methods. You can use `forEach` to loop over the elements in an array. The `filter` method returns a new array containing only the elements that pass the ((predicate function)). You can transform an array by putting each element through a function using `map`. You can use `reduce` to combine all the elements in an array into a single value. The `some` method tests whether any element matches a given predicate function, while `find` finds the first element that matches a predicate. ## Exercises @@ -581,7 +581,7 @@ if}} {{index "your own loop (example)", "for loop"}} -Write a higher-order function `loop` that provides something like a `for` loop statement. It should take a value, a test function, an update function, and a body function. Each iteration, it should first run the test function on the current loop value and stop if that returns false. It should then call the body function, giving it the current value, then finally call the update function to create a new value and start over from the beginning. +Write a higher-order function `loop` that provides something like a `for` loop statement. It should take a value, a test function, an update function, and a body function. Each iteration, it should first run the test function on the current loop value and stop if that returns false. It should then call the body function, giving it the current value, and finally call the update function to create a new value and start over from the beginning. When defining the function, you can use a regular loop to do the actual looping. @@ -635,7 +635,7 @@ hint}} ### Dominant writing direction -{{index "SCRIPTS data set", "direction (writing)", "groupBy function", "dominant direction (exercise)"}} +{{index "SCRIPTS dataset", "direction (writing)", "groupBy function", "dominant direction (exercise)"}} Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a `direction` property that can be `"ltr"` (left to right), `"rtl"` (right to left), or `"ttb"` (top to bottom). diff --git a/14_dom.md b/14_dom.md index ed5d80805..62e10be84 100644 --- a/14_dom.md +++ b/14_dom.md @@ -693,7 +693,7 @@ An HTML table is built with the following tag structure: For each _((row))_, the `<table>` tag contains a `<tr>` tag. Inside of these `<tr>` tags, we can put cell elements: either heading cells (`<th>`) or regular cells (`<td>`). -Given a data set of mountains, an array of objects with `name`, `height`, and `place` properties, generate the DOM structure for a table that enumerates the objects. It has one column per key and one row per object, plus a header row with `<th>` elements at the top, listing the column names. +Given a dataset of mountains, an array of objects with `name`, `height`, and `place` properties, generate the DOM structure for a table that enumerates the objects. It has one column per key and one row per object, plus a header row with `<th>` elements at the top, listing the column names. Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data. diff --git a/16_game.md b/16_game.md index ba0bddf7e..4bfa8e00c 100644 --- a/16_game.md +++ b/16_game.md @@ -859,7 +859,7 @@ async function runGame(plans, Display) { Because we made `runLevel` return a promise, `runGame` can be written using an `async` function, as shown in [Chapter ?](async). It returns another promise, which resolves when the player finishes the game. -{{index game, "GAME_LEVELS data set"}} +{{index game, "GAME_LEVELS dataset"}} There is a set of ((level)) plans available in the `GAME_LEVELS` binding in [this chapter's sandbox](https://eloquentjavascript.net/code#16)[ ([_https://eloquentjavascript.net/code#16_](https://eloquentjavascript.net/code#16))]{if book}. This page feeds them to `runGame`, starting an actual game. diff --git a/17_canvas.md b/17_canvas.md index 6ff572168..e0d922e95 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -965,7 +965,7 @@ hint}} {{index label, text, "pie chart example"}} -[Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave enough room for their labels. +[Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other datasets as well. You may assume that categories are big enough to leave enough room for their labels. You might need `Math.sin` and `Math.cos` again, which are described in [Chapter ?](dom#sin_cos). From bbafb111f9c604ed300129358e7cfa4e46caf080 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 17 May 2024 08:22:44 +0200 Subject: [PATCH 330/392] Copyediting introduction --- 00_intro.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/00_intro.md b/00_intro.md index 35055bc4c..895eaa409 100644 --- a/00_intro.md +++ b/00_intro.md @@ -52,13 +52,13 @@ quote}} {{index [program, "nature of"], data}} -A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer's memory, and at the same time it controls the actions performed on this memory. Analogies that try to compare programs to familiar objects tend to fall short. A superficially fitting one is to compare a program to a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole. +A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer's memory, and, at the same time, it controls the actions performed on this memory. Analogies that try to compare programs to familiar objects tend to fall short. A superficially fitting one is to compare a program to a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole. A ((computer)) is a physical machine that acts as a host for these immaterial machines. Computers themselves can do only stupidly straightforward things. The reason they are so useful is that they do these things at an incredibly high ((speed)). A program can ingeniously combine an enormous number of these simple actions to do very complicated things. {{index [programming, "joy of"]}} -A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands. But as a program grows, so does its ((complexity)). The skill of programming is the skill of building programs that don't confuse yourself. The best programs are those that manage to do something interesting while still being easy to understand. +A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands. But as a program grows, so does its ((complexity)). The skill of programming is the skill of building programs that don't confuse the programmer. The best programs are those that manage to do something interesting while still being easy to understand. {{index "programming style", "best practices"}} @@ -195,7 +195,7 @@ This flexibility also has its advantages, though. It leaves room for techniques {{index future, [JavaScript, "versions of"], ECMAScript, "ECMAScript 6"}} -There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version during JavaScript's ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on the version 4 was abandoned in 2008. A much less ambitious version 5, which made only some uncontroversial improvements, came out in 2009. In 2015, version 6 came out, a major update that included some of the ideas planned for version 4. Since then we've had new, small updates every year. +There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version during JavaScript's ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on version 4 was abandoned in 2008. A much less ambitious version 5, which made only some uncontroversial improvements, came out in 2009. In 2015, version 6 came out, a major update that included some of the ideas planned for version 4. Since then we've had new, small updates every year. The fact that JavaScript is evolving means that browsers have to constantly keep up. If you're using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I'm using the 2023 version of JavaScript. From 15644efbf8a891c440da061534407c1f8742ece3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 17 May 2024 08:24:55 +0200 Subject: [PATCH 331/392] Copyediting chapter 1 --- 01_values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/01_values.md b/01_values.md index fd9254b71..e973c0c33 100644 --- a/01_values.md +++ b/01_values.md @@ -357,7 +357,7 @@ The difference in meaning between `undefined` and `null` is an accident of JavaS {{index NaN, "type coercion"}} -In the Introduction, I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: +In the [Introduction](intro), I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: ``` console.log(8 * null) From f01c172842c1c73d04be122d3762b3e5d6f8cb73 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 17 May 2024 08:26:38 +0200 Subject: [PATCH 332/392] Copyediting chapter 2 --- 02_program_structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02_program_structure.md b/02_program_structure.md index 9e7296943..1bd551d25 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -578,7 +578,7 @@ The first style can be hard to read. I rather like the look of the underscores, {{index "Number function", constructor}} -In a few cases, such as the `Number` function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. It will become clear what a constructor is in [Chapter ?](object#constructors). For now, the important thing is not to be bothered by this apparent lack of ((consistency)). +In a few cases, such as the `Number` function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. It will become clear what a constructor is in [Chapter ?](object#constructors). For now, the important thing is to not be bothered by this apparent lack of ((consistency)). ## Comments From abee434721402a93ab29a76ca07747c4bf0e8b5f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 17 May 2024 08:28:40 +0200 Subject: [PATCH 333/392] Copyediting chapter 3 --- 03_functions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/03_functions.md b/03_functions.md index ab8622ae5..10561cb34 100644 --- a/03_functions.md +++ b/03_functions.md @@ -371,7 +371,7 @@ console.log(wrap2()); This is allowed and works as you'd hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls don't affect each other's local bindings. -This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about the lifetimes of bindings but also makes it possible to use function values in some creative ways. +This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about the lifetimes of bindings, it also makes it possible to use function values in some creative ways. {{index "multiplier function"}} @@ -389,7 +389,7 @@ console.log(twice(5)); {{index [binding, "from parameter"]}} -The explicit `local` binding from the `wrapValue` example isn't really needed, since a parameter is itself a local binding. +The explicit `local` binding from the `wrapValue` example isn't really needed since a parameter is itself a local binding. {{index [function, "model of"]}} @@ -422,7 +422,7 @@ This is rather close to the way mathematicians define exponentiation and arguabl {{index [function, application], efficiency}} -This implementation has one problem, however: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. +However, this implementation has one problem: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. {{index optimization}} @@ -436,7 +436,7 @@ Worrying about efficiency can be a distraction. It's yet another factor that com {{index "premature optimization"}} -Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't, since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. +Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. {{index "branching recursion"}} @@ -470,7 +470,7 @@ console.log(findSolution(24)); Note that this program doesn't necessarily find the _shortest_ sequence of operations. It is satisfied when it finds any sequence at all. -It's okay if you don't see how this code works right away. Let's work through it, since it makes for a great exercise in recursive thinking. +It's okay if you don't see how this code works right away. Let's work through it since it makes for a great exercise in recursive thinking. The inner function `find` does the actual recursing. It takes two ((argument))s: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If it can find no solution starting from this number, it returns `null`. @@ -510,7 +510,7 @@ There are two more or less natural ways for functions to be introduced into prog The first occurs when you find yourself writing similar code multiple times. You'd prefer not to do that, as having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function. -The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You start by naming the function, then write its body. You might even start writing code that uses the function before you actually define the function itself. +The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You start by naming the function, and then write its body. You might even start writing code that uses the function before you actually define the function itself. {{index [function, naming], [binding, naming]}} @@ -620,7 +620,7 @@ The first helper function in the ((farm example)), `printZeroPaddedWithLabel`, i {{index substitution}} -A _pure_ function is a specific kind of value-producing function that not only has no side effects but also doesn't rely on side effects from other code—for example, it doesn't read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn't do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test. +A _pure_ function is a specific kind of value-producing function that not only has no side effects, but also doesn't rely on side effects from other code—for example, it doesn't read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn't do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test. {{index optimization, "console.log"}} From 2894d47bdfc6b933e399af2279b7847af09a790b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 17 May 2024 08:32:21 +0200 Subject: [PATCH 334/392] Copyediting chapter 4 --- 04_data.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/04_data.md b/04_data.md index a1ac4f27f..07ecfde2f 100644 --- a/04_data.md +++ b/04_data.md @@ -42,7 +42,7 @@ Switching to a more scientific approach, Jacques has started keeping a daily log The first thing he needs is a data structure to store this information. -## Data sets +## Datasets {{index ["data structure", collection], [memory, organization]}} @@ -192,7 +192,7 @@ let descriptions = { {{index [braces, object]}} -This means that braces have _two_ meanings in JavaScript. At the start of a ((statement)), they begin a ((block)) of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem. The one case where this does come up is when you want to return an object from a short-hand arrow function—you can't write `n => {prop: n}`, since the braces will be interpreted as a function body. Instead, you have to put a set of parentheses around the object to make it clear that it is an expression. +This means that braces have _two_ meanings in JavaScript. At the start of a ((statement)), they begin a ((block)) of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem. The one case where this does come up is when you want to return an object from a short-hand arrow function—you can't write `n => {prop: n}` since the braces will be interpreted as a function body. Instead, you have to put a set of parentheses around the object to make it clear that it is an expression. {{index undefined}} @@ -225,7 +225,7 @@ console.log("right" in anObject); {{index "in operator", [property, "testing for"], object}} -The binary `in` operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to `undefined` and actually deleting it is that in the first case, the object still _has_ the property (it just doesn't have a very interesting value), whereas in the second case the property is no longer present and `in` will return `false`. +The binary `in` operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to `undefined` and actually deleting it is that in the first case, the object still _has_ the property (it just doesn't have a very interesting value), whereas in the second case, the property is no longer present and `in` will return `false`. {{index "Object.keys function"}} @@ -604,7 +604,7 @@ Both `indexOf` and `lastIndexOf` take an optional second argument that indicates {{index "slice method", [array, indexing]}} -Another fundamental array method is `slice`, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive, the end index exclusive. +Another fundamental array method is `slice`, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive and the end index is exclusive. ``` console.log([0, 1, 2, 3, 4].slice(2, 4)); @@ -809,7 +809,7 @@ console.log(randomPointOnCircle(2)); // → {x: 0.3667, y: 1.966} ``` -If you're not familiar with sines and cosines, don't worry. I'll explain them when they are used in this book, in [Chapter ?](dom#sin_cos). +If you're not familiar with sines and cosines, don't worry. I'll explain them when they are used in [Chapter ?](dom#sin_cos). {{index "Math.random function", "random number"}} @@ -893,7 +893,7 @@ Note that if you try to destructure `null` or `undefined`, you get an error, muc {{index "optional chaining", "period character"}} -When you aren't sure whether a given value produces an object but still want to read a property from it when it does, you can use a variant of the dot notation: `object?.property`. +When you aren't sure whether a given value produces an object, but still want to read a property from it when it does, you can use a variant of the dot notation: `object?.property`. ``` function city(object) { @@ -928,7 +928,7 @@ If you want to save data in a file for later or send it to another computer over {{index serialization, "World Wide Web"}} -What we can do is _serialize_ the data. That means it is converted into a flat description. A popular serialization format is called _((JSON))_ (pronounced "Jason"), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the Web, even in languages other than JavaScript. +What we can do is _serialize_ the data. That means it is converted into a flat description. A popular serialization format is called _((JSON))_ (pronounced "Jason"), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the web, even with languages other than JavaScript. {{index [array, notation], [object, creation], [quoting, "in JSON"], comment}} From 08f32a51cdcb9b52c407acfd63415a7d546e25c6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 17 May 2024 08:42:32 +0200 Subject: [PATCH 335/392] Copyediting chapter 6 --- 06_object.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/06_object.md b/06_object.md index f1d9e228c..733c1f87a 100644 --- a/06_object.md +++ b/06_object.md @@ -35,7 +35,9 @@ If a problem is found in one such object class, it can often be repaired or even {{id interface}} {{index [interface, object]}} -Each abstract data type has an _interface_, the collection of operations that external code can perform on it. Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate, since useful pieces of functionality often involve a group of different object classes working closely together. +Each abstract data type has an _interface_, the collection of operations that external code can perform on it. Any details beyond that interface are _encapsulated_, treated as internal to the type and of no concern to the rest of the program. + +Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate since useful pieces of functionality often involve a group of different object classes working closely together. {{id obj_methods}} @@ -251,7 +253,7 @@ console.log(Object.getPrototypeOf(killerRabbit) == {{index constructor}} -Constructors will typically add some per-instance properties to `this`. It is also possible to declare properties directly in the ((class declaration)). Unlike methods, such properties are added to ((instance)) objects, not the prototype. +Constructors will typically add some per-instance properties to `this`. It is also possible to declare properties directly in the ((class declaration)). Unlike methods, such properties are added to ((instance)) objects and not the prototype. ``` class Particle { @@ -299,7 +301,7 @@ If you try to call `#getSecret` from outside the class, you get an error. Its ex To use private instance properties, you must declare them. Regular properties can be created by just assigning to them, but private properties _must_ be declared in the class declaration to be available at all. -This class implements an appliance for getting random whole number below a given maximum number. It only has one ((public)) property: `getNumber`. +This class implements an appliance for getting a random whole number below a given maximum number. It only has one ((public)) property: `getNumber`. ``` class RandomSource { @@ -461,7 +463,7 @@ This technique is called _polymorphism_. Polymorphic code can work with values o {{index "forEach method"}} -An example of a widely used interface is that of ((array-like object))s which have a `length` property holding a number, and numbered properties for each of their elements. Both arrays and strings support this interface, as do various other objects, some of which we'll see later in the chapters about the browser. Our implementation of `forEach` from [Chapter ?](higher_order) works on anything that provides this interface. In fact, so does `Array.prototype.forEach`. +An example of a widely used interface is that of ((array-like object))s which have a `length` property holding a number and numbered properties for each of their elements. Both arrays and strings support this interface, as do various other objects, some of which we'll see later in the chapters about the browser. Our implementation of `forEach` from [Chapter ?](higher_order) works on anything that provides this interface. In fact, so does `Array.prototype.forEach`. ``` Array.prototype.forEach.call({ @@ -527,7 +529,7 @@ The `Temperature` class allows you to read and write the temperature in either d {{index "static method", "static property"}} -Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. +Sometimes you want to attach some properties directly to your constructor function rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. For example, the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit: @@ -702,7 +704,7 @@ Imagine we need a list type much like the `List` class we saw before, but becaus {{index overriding, prototype}} -JavaScript's prototype system makes it possible to create a _new_ class, much like the old class but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `length` getter. +JavaScript's prototype system makes it possible to create a _new_ class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `length` getter. In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class. @@ -728,7 +730,7 @@ The use of the word `extends` indicates that this class shouldn't be directly ba To initialize a `LengthList` instance, the constructor calls the constructor of its superclass through the `super` keyword. This is necessary because if this new object is to behave (roughly) like a `List`, it is going to need the instance properties that lists have. -The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet, since `#length` hasn't been filled in yet. We can use `super.something` to call methods and getters on the superclass's prototype, which is often useful. +The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet since `#length` hasn't been filled in yet. We can use `super.something` to call methods and getters on the superclass's prototype, which is often useful. Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial. From 70e5f44e1b9d1554baa874558990e5257b616dd8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 21 May 2024 15:44:06 +0200 Subject: [PATCH 336/392] Copyedit chapter 7 --- 07_robot.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/07_robot.md b/07_robot.md index 4ba488402..24414657c 100644 --- a/07_robot.md +++ b/07_robot.md @@ -378,7 +378,7 @@ This robot usually finishes the task of delivering 5 parcels in about 16 turns. It's hard to objectively compare ((robot))s by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn't. -Write a function `compareRobots` that takes two robots (and their starting memory). It generates 100 tasks and lets each of the robots solve each of these tasks. When done, it outputs the average number of steps each robot took per task. +Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task. For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot. @@ -437,7 +437,7 @@ hint}} Most data structures provided in a standard JavaScript environment aren't very well suited for persistent use. Arrays have `slice` and `concat` methods, which allow us to easily create new arrays without damaging the old one. But `Set`, for example, has no methods for creating a new set with an item added or removed. -Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. Its `add` method, however, returns a _new_ `PGroup` instance with the given member added and leaves the old one unchanged. Similarly, `delete` creates a new instance without a given member. +Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. Its `add` method, however, should return a _new_ `PGroup` instance with the given member added and leave the old one unchanged. Similarly, `delete` should create a new instance without a given member. The class should work for values of any type, not just strings. It does _not_ have to be efficient when used with large numbers of values. @@ -447,7 +447,7 @@ The ((constructor)) shouldn't be part of the class's interface (though you'll de {{index singleton}} -Why do you need only one `PGroup.empty` value, rather than having a function that creates a new, empty map every time? +Why do you need only one `PGroup.empty` value rather than having a function that creates a new, empty map every time? {{if interactive From 995122d8116d524f6e51c73616cd7354cc489d69 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 21 May 2024 15:49:51 +0200 Subject: [PATCH 337/392] Copyedit chapter 8 --- 08_error.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/08_error.md b/08_error.md index dfb889b1b..b701fcece 100644 --- a/08_error.md +++ b/08_error.md @@ -20,7 +20,7 @@ If a program is crystallized thought, we can roughly categorize bugs into those {{index parsing, analysis}} -Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we're trying to do. But here JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. Even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. +Many mistakes could be pointed out to us automatically by the computer if it knew enough about what we're trying to do. But here, JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. Even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. {{index [syntax, error], [property, access]}} @@ -28,7 +28,7 @@ There are some things that JavaScript does complain about. Writing a program tha {{index NaN, error}} -Often, however, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. +Often, however, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all, but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. The process of finding mistakes—bugs—in programs is called _((debugging))_. @@ -75,7 +75,7 @@ console.log(name); {{index error}} -The bogus call to `Person` succeeded but returned an undefined value and created the global binding `name`. In strict mode, the result is different. +The bogus call to `Person` succeeded, but returned an undefined value and created the global binding `name`. In strict mode, the result is different. ```{test: "error \"TypeError: Cannot set properties of undefined (setting 'name')\""} "use strict"; @@ -131,7 +131,7 @@ If the language is not going to do much to help us find mistakes, we'll have to Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective since it takes too much time to exhaustively test everything every time you make a change. -Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice, rather than randomly running into it at some later time. +Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice rather than randomly running into it at some later time. {{index "toUpperCase method"}} @@ -221,7 +221,7 @@ _Right_. Dividing 13 by 10 does not produce a whole number. Instead of `n /= bas {{index "JavaScript console", "debugger statement"}} -An alternative to using `console.log` to peek into the program's behavior is to use the _debugger_ capabilities of your browser. Browsers come with the ability to set a _((breakpoint))_ on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won't go into details, as debuggers differ from browser to browser, but look in your browser's ((developer tools)) or search the Web for instructions. +An alternative to using `console.log` to peek into the program's behavior is to use the _debugger_ capabilities of your browser. Browsers come with the ability to set a _((breakpoint))_ on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won't go into details, as debuggers differ from browser to browser, but look in your browser's ((developer tools)) or search the web for instructions. Another way to set a breakpoint is to include a `debugger` statement (consisting simply of that keyword) in your program. If the ((developer tools)) of your browser are active, the program will pause whenever it reaches such a statement. From 0ab67b3b0d5b12f58b17f7af3fec6d4f1399b734 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 21 May 2024 15:52:35 +0200 Subject: [PATCH 338/392] Copyedit chapter 9 --- 09_regexp.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/09_regexp.md b/09_regexp.md index 4610562c9..162df15f6 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -22,7 +22,7 @@ if}} {{index evolution, adoption, integration}} -Programming ((tool))s and techniques survive and spread in a chaotic, evolutionary way. It's not always the best or brilliant ones that win but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology. +Programming ((tool))s and techniques survive and spread in a chaotic, evolutionary way. It's not always the best or most brilliant ones that win, but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology. {{index "domain-specific language"}} @@ -391,9 +391,9 @@ If we want to enforce that the match must span the whole string, we can add the {{index "word boundary", "word character"}} -There is also a `\b` marker that matches _word boundaries_, positions that have a word character one side, and a non-word character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w`, and are therefore not very reliable. +There is also a `\b` marker that matches _word boundaries_, positions that have a word character on one side, and a non-word character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w`, and are therefore not very reliable. -Note that these boundary markers don't match any actual characters. They just enforce that a given condition holds at the place where they appears in the pattern. +Note that these boundary markers don't match any actual characters. They just enforce that a given condition holds at the place where it appears in the pattern. {{index "look-ahead"}} @@ -716,7 +716,7 @@ This method returns an array of match arrays. The regular expression given to `m {{index comment, "file format", "enemies example", "INI file"}} -To conclude the chapter, we'll look at a problem that calls for ((regular expression))s. Imagine we are writing a program to automatically collect information about our enemies from the ((Internet)). (We will not actually write that program here, just the part that reads the ((configuration)) file. Sorry.) The configuration file looks like this: +To conclude the chapter, we'll look at a problem that calls for ((regular expression))s. Imagine we are writing a program to automatically collect information about our enemies from the ((internet)). (We will not actually write that program here, just the part that reads the ((configuration)) file. Sorry.) The configuration file looks like this: ```{lang: "null"} searchengine=https://duckduckgo.com/?q=$1 From 451197603de781453cbe6a9f18a035a838446076 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 21 May 2024 15:56:49 +0200 Subject: [PATCH 339/392] Copyedit chapter 10 --- 10_modules.md | 6 +++--- 11_async.md | 4 ++-- 13_browser.md | 26 +++++++++++++------------- 15_event.md | 2 +- 18_http.md | 8 ++++---- 21_skillsharing.md | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/10_modules.md b/10_modules.md index 34cba76b3..257280ffd 100644 --- a/10_modules.md +++ b/10_modules.md @@ -58,7 +58,7 @@ A modular program is composed of a number of such modules, wired together via th {{index "Date class", "weekDay module"}} -The following example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant which is not part of its interface, and two functions which are. It has no dependencies. +The following example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant that is not part of its interface, and two functions that are. It has no dependencies. ``` const names = ["Sunday", "Monday", "Tuesday", "Wednesday", @@ -87,7 +87,7 @@ The `import` keyword, followed by a list of binding names in braces, makes bindi {{index [module, resolution], resolution}} -How such a module name is resolved to an actual program differs by platform. The browser treats them as Web addresses, whereas Node.js resolves them to files. When you run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. +How such a module name is resolved to an actual program differs by platform. The browser treats them as web addresses, whereas Node.js resolves them to files. When you run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. Import and export declarations cannot appear inside of functions, loops, or other blocks. They are immediately resolved when the module is loaded, regardless of how the code in the module executes. To reflect this, they must appear only in the outer module body. @@ -299,7 +299,7 @@ Many JavaScript packages aren't technically written in JavaScript. Language exte {{index latency, performance, [file, access], [network, speed]}} -Including a modular program that consists of 200 different files in a ((web page)) produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That's a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that combine their programs (which they painstakingly split into modules) into a single big file before they publish it to the Web. Such tools are called _((bundler))s_. +Including a modular program that consists of 200 different files in a ((web page)) produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That's a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that combine their programs (which they painstakingly split into modules) into a single big file before they publish it to the web. Such tools are called _((bundler))s_. {{index "file size"}} diff --git a/11_async.md b/11_async.md index cf18415c0..0be189e10 100644 --- a/11_async.md +++ b/11_async.md @@ -265,7 +265,7 @@ Carla is a somewhat peculiar crow. In her youth, she was fascinated by human lan {{index "Carla the crow"}} -Carla loves the Internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. +Carla loves the internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device must send along the correct six-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not. Sending incorrect numbers immediately returns a failure message. When sending the correct ones, the access point waits for more digits. @@ -437,7 +437,7 @@ One morning, Carla wakes up to unfamiliar noise from the tarmac outside of her h Being a curious crow, Carla takes a closer look at the wall. It appears to consist of a number of large glass-fronted devices wired up to cables. On the back, the devices say “LedTec SIG-5030”. -A quick Internet search turns up a user's manual for these devices. They appear to be traffic signs, with a programmable matrix of amber LED lights. The intent is of the humans is probably to display some kind of information on them during their event. Interestingly, the screens can be programmed over a wireless network. Could it be they are connected to the building's local network? +A quick internet search turns up a user's manual for these devices. They appear to be traffic signs, with a programmable matrix of amber LED lights. The intent is of the humans is probably to display some kind of information on them during their event. Interestingly, the screens can be programmed over a wireless network. Could it be they are connected to the building's local network? Each device on a network gets an _IP address_, which other devices can use to send it messages. We talk more about that in [Chapter ?](browser). Carla notices that her own phones all get addresses like `10.0.0.20` or `10.0.0.33`. It might be worth trying to send messages to all such addresses and see if any one of them responds to the interface described in the manual for the signs. diff --git a/13_browser.md b/13_browser.md index 7700d0bd9..1bdb35de3 100644 --- a/13_browser.md +++ b/13_browser.md @@ -2,7 +2,7 @@ {{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A very short personal history", chapter: true} -The dream behind the Web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished. +The dream behind the web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished. quote}} @@ -16,13 +16,13 @@ The next chapters of this book will discuss web browsers. Without ((browser))s, Web technology has been decentralized from the start, not just technically but also in terms of the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which were then—sometimes—adopted by others—and finally set down as in ((standards)). -This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally, open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. +This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally, open hostility). On the other hand, the haphazard way in which the web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. ## Networks and the Internet Computer ((network))s have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things. -If connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((Internet))_. It has lived up to its promise. +If connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((internet))_. It has lived up to its promise. A computer can use this network to shoot bits at another computer. For any effective ((communication)) to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the ((encoding)) mechanism used. @@ -48,7 +48,7 @@ Most protocols are built on top of other protocols. HTTP treats the network as a {{indexsee "Transmission Control Protocol", TCP}} -The _Transmission Control Protocol_ (TCP) is a ((protocol)) that addresses this problem. All Internet-connected devices "speak" it, and most communication on the ((Internet)) is built on top of it. +The _Transmission Control Protocol_ (TCP) is a ((protocol)) that addresses this problem. All internet-connected devices "speak" it, and most communication on the ((internet)) is built on top of it. {{index "listening (TCP)"}} @@ -64,15 +64,15 @@ Such a connection acts as a two-way ((pipe)) through which bits can flow—the m ## The Web -The _((World Wide Web))_ (not to be confused with the ((Internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. "Web" refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. +The _((World Wide Web))_ (not to be confused with the ((internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. "Web" refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. -To become part of the Web, all you need to do is connect a machine to the ((Internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. +To become part of the web, all you need to do is connect a machine to the ((internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. {{index URL}} {{indexsee "Uniform Resource Locator", URL}} -Each ((document)) on the Web is named by a _Uniform Resource Locator_ (URL), which looks something like this: +Each ((document)) on the web is named by a _Uniform Resource Locator_ (URL), which looks something like this: ```{lang: null} http://eloquentjavascript.net/13_browser.html @@ -84,7 +84,7 @@ Each ((document)) on the Web is named by a _Uniform Resource Locator_ (URL), whi The first part tells us that this URL uses the HTTP ((protocol)) (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which ((server)) we are requesting the document from. Last is a path string that identifies the document (or _((resource))_) we are interested in. -Machines connected to the Internet get an _((IP address))_, a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. Since lists of more or less random numbers are hard to remember and awkward to type, you can instead register a _((domain)) name_ for an address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. +Machines connected to the internet get an _((IP address))_, a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. Since lists of more or less random numbers are hard to remember and awkward to type, you can instead register a _((domain)) name_ for an address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. {{index browser}} @@ -232,9 +232,9 @@ Note that I had to use single quotes for the string in the `onclick` attribute b {{index "malicious script", "World Wide Web", browser, website, security}} -Running programs downloaded from the ((Internet)) is potentially dangerous. You don't know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by malicious actors is how you get your computer infected by ((virus))es, your data stolen, and your accounts hacked. +Running programs downloaded from the ((internet)) is potentially dangerous. You don't know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by malicious actors is how you get your computer infected by ((virus))es, your data stolen, and your accounts hacked. -Yet the attraction of the Web is that you can browse it without necessarily ((trust))ing all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can't look at the files on your computer or modify anything not related to the web page it was embedded in. +Yet the attraction of the web is that you can browse it without necessarily ((trust))ing all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can't look at the files on your computer or modify anything not related to the web page it was embedded in. {{index isolation}} @@ -250,9 +250,9 @@ Every now and then, someone comes up with a new way to circumvent the limitation {{index Microsoft, "World Wide Web"}} -In the early stages of the Web, a browser called ((Mosaic)) dominated the market. After a few years, the balance shifted to ((Netscape)), which was, in turn, largely supplanted by Microsoft's ((Internet Explorer)). At any point where a single ((browser)) was dominant, that browser's vendor would feel entitled to unilaterally invent new features for the Web. Since most users used the most popular browser, ((website))s would simply start using those features—never mind the other browsers. +In the early stages of the web, a browser called ((Mosaic)) dominated the market. After a few years, the balance shifted to ((Netscape)), which was, in turn, largely supplanted by Microsoft's ((Internet Explorer)). At any point where a single ((browser)) was dominant, that browser's vendor would feel entitled to unilaterally invent new features for the web. Since most users used the most popular browser, ((website))s would simply start using those features—never mind the other browsers. -This was the dark age of ((compatibility)), often called the _((browser wars))_. Web developers were left with not one unified Web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of ((bug))s, and of course the bugs were different for each ((browser)). Life was hard for people writing web pages. +This was the dark age of ((compatibility)), often called the _((browser wars))_. Web developers were left with not one unified web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of ((bug))s, and of course the bugs were different for each ((browser)). Life was hard for people writing web pages. {{index Apple, "Internet Explorer", Mozilla}} @@ -262,6 +262,6 @@ Mozilla ((Firefox)), a not-for-profit offshoot of ((Netscape)), challenged Inter The new players had a more serious attitude toward ((standards)) and better ((engineering)) practices, giving us less incompatibility and fewer ((bug))s. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaced Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs. -Unfortunately, with Firefox's market share getting ever smaller, and Edge becoming just a wrapper around Chrome's core in 2018, this uniformity might once again take the form of a single vendor—Google, this time—having enough control over the browser market to push its idea of what the Web should look like onto the rest of the world. +Unfortunately, with Firefox's market share getting ever smaller, and Edge becoming just a wrapper around Chrome's core in 2018, this uniformity might once again take the form of a single vendor—Google, this time—having enough control over the browser market to push its idea of what the web should look like onto the rest of the world. For what it is worth, this long chain of historical events and accidents has produced the web platform that we have today. In the next chapters, we are going to write programs for it. \ No newline at end of file diff --git a/15_event.md b/15_event.md index 1157ef309..10f64fbb0 100644 --- a/15_event.md +++ b/15_event.md @@ -368,7 +368,7 @@ Note that the order of these codes is different from the one used by `button`, w {{index touch, "mousedown event", "mouseup event", "click event"}} -The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the Web "work" on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you'll get `"mousedown"`, `"mouseup"`, and `"click"` events. +The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the web "work" on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you'll get `"mousedown"`, `"mouseup"`, and `"click"` events. But this illusion isn't very robust. A touchscreen doesn't work like a mouse: it doesn't have multiple buttons, you can't track the finger when it isn't on the screen (to simulate `"mousemove"`), and it allows multiple fingers to be on the screen at the same time. diff --git a/18_http.md b/18_http.md index 19ebd29eb..44eebf345 100644 --- a/18_http.md +++ b/18_http.md @@ -4,7 +4,7 @@ {{quote {author: "Tim Berners-Lee", chapter: true} -What was often difficult for people to understand about the design was that there was nothing else beyond URLs, HTTP and HTML. There was no central computer 'controlling' the Web, no single network on which these protocols worked, not even an organisation anywhere that 'ran' the Web. The Web was not a physical 'thing' that existed in a certain 'place'. It was a 'space' in which information could exist. +What was often difficult for people to understand about the design was that there was nothing else beyond URLs, HTTP and HTML. There was no central computer 'controlling' the web, no single network on which these protocols worked, not even an organisation anywhere that 'ran' the Web. The Web was not a physical 'thing' that existed in a certain 'place'. It was a 'space' in which information could exist. quote}} @@ -267,7 +267,7 @@ This second approach makes it easier to use some of the features that HTTP provi {{index "man-in-the-middle", security, HTTPS, [network, security]}} -Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route, it may be inspected or even modified. +Data traveling over the internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route, it may be inspected or even modified. {{index tampering}} @@ -285,7 +285,7 @@ Thus, when it works right, ((HTTPS)) prevents other people from impersonating th ## Form fields -Forms were originally designed for the pre-JavaScript Web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. +Forms were originally designed for the pre-JavaScript web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. {{index [DOM, fields]}} @@ -728,7 +728,7 @@ Simple ((HTML)) pages with a bit of JavaScript can be a great format for "((mini {{index persistence, [binding, "as state"], [browser, storage]}} -When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there (we'll see how to do that in [Chapter ?](node)). But that's a lot of extra work and complexity. Sometimes it's enough to just keep the data in the ((browser)). +When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the internet, and have your application store something there (we'll see how to do that in [Chapter ?](node)). But that's a lot of extra work and complexity. Sometimes it's enough to just keep the data in the ((browser)). {{index "localStorage object", "setItem method", "getItem method", "removeItem method"}} diff --git a/21_skillsharing.md b/21_skillsharing.md index 30438aeda..4d929a72e 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -153,7 +153,7 @@ Content-Length: 295 {{index security}} -The protocol described here doesn't do any ((access control)). Everybody can comment, modify talks, and even delete them. (Since the Internet is full of ((hooligan))s, putting such a system online without further protection probably wouldn't end well.) +The protocol described here doesn't do any ((access control)). Everybody can comment, modify talks, and even delete them. (Since the internet is full of ((hooligan))s, putting such a system online without further protection probably wouldn't end well.) ## The server From f5a19f145cf60480f4c3fa20fc8d546c0cbd34a9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 22 May 2024 17:38:17 +0200 Subject: [PATCH 340/392] Copyedit chapter 11 --- 11_async.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/11_async.md b/11_async.md index 0be189e10..f193e42a6 100644 --- a/11_async.md +++ b/11_async.md @@ -63,7 +63,7 @@ One approach to ((asynchronous programming)) is to make functions that need to w {{index "setTimeout function", waiting}} -As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds, then calls a function. +As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds and then calls a function. ```{test: no} setTimeout(() => console.log("Tick"), 500); @@ -249,7 +249,7 @@ Much like an uncaught exception is handled by the environment, JavaScript enviro {{index "Carla the crow"}} -It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. +It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container, a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. @@ -267,7 +267,7 @@ Carla is a somewhat peculiar crow. In her youth, she was fascinated by human lan Carla loves the internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. -Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device must send along the correct six-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not. Sending incorrect numbers immediately returns a failure message. When sending the correct ones, the access point waits for more digits. +Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device must send along the correct six-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending a partial code (say, only three digits), the response is different based on whether those digits are the correct start of the code or not. Sending incorrect numbers immediately returns a failure message. When sending the correct ones, the access point waits for more digits. This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find the second digit in the same way, and so on, until she knows the entire passcode. From 2290d4e609eb9448580c9fc6e36a0ab15b06ed9c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 22 May 2024 17:54:10 +0200 Subject: [PATCH 341/392] Copyedit chapter 13 --- 13_browser.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/13_browser.md b/13_browser.md index 1bdb35de3..1927c775c 100644 --- a/13_browser.md +++ b/13_browser.md @@ -30,9 +30,9 @@ A computer can use this network to shoot bits at another computer. For any effec A _network ((protocol))_ describes a style of communication over a ((network)). There are protocols for sending email, for fetching email, for sharing files, and even for controlling computers that happen to be infected by malicious software. -{{indexsee "Hypertext Transfer Protocol", HTTP}} +{{indexsee "HyperText Transfer Protocol", HTTP}} -The _Hypertext Transfer Protocol_ (((HTTP))) is a protocol for retrieving named ((resource))s (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use: +The _HyperText Transfer Protocol_ (((HTTP))) is a protocol for retrieving named ((resource))s (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use: ```{lang: http} GET /index.html HTTP/1.1 @@ -70,9 +70,9 @@ To become part of the web, all you need to do is connect a machine to the ((inte {{index URL}} -{{indexsee "Uniform Resource Locator", URL}} +{{indexsee "uniform resource locator", URL}} -Each ((document)) on the web is named by a _Uniform Resource Locator_ (URL), which looks something like this: +Each ((document)) on the web is named by a _uniform resource locator_ (URL), which looks something like this: ```{lang: null} http://eloquentjavascript.net/13_browser.html @@ -94,9 +94,9 @@ If you type this URL into your browser's ((address bar)), the browser will try t {{index HTML}} -{{indexsee "Hypertext Markup Language", HTML}} +{{indexsee "HyperText Markup Language", HTML}} -_HTML_, which stands for _Hypertext Markup Language_, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. +_HTML_, which stands for _HyperText Markup Language_, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. A short HTML document might look like this: From 3115d1dcf3de446c50c8f45476491939874f48e3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 24 May 2024 07:43:19 +0200 Subject: [PATCH 342/392] Copyedit chapter 15 --- 15_event.md | 6 +++--- 18_http.md | 2 +- 19_paint.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/15_event.md b/15_event.md index 10f64fbb0..20cae3b1b 100644 --- a/15_event.md +++ b/15_event.md @@ -199,7 +199,7 @@ This can be used to implement your own ((keyboard)) shortcuts or ((context menu) Try not to do such things without a really good reason. It'll be unpleasant for people who use your page when expected behavior is broken. -Depending on the browser, some events can't be intercepted at all. On Chrome, for example, the ((keyboard)) shortcut to close the current tab ([control]{keyname}-W or [command]{keyname}-W) cannot be handled by JavaScript. +Depending on the browser, some events can't be intercepted at all. On Chrome, for example, the ((keyboard)) shortcut to close the current tab ([ctrl]{keyname}-W or [command]{keyname}-W) cannot be handled by JavaScript. ## Key events @@ -233,7 +233,7 @@ The previous example looks at the `key` property of the event object to see whic {{index "modifier key", "shift key", "control key", "alt key", "meta key", "command key", "ctrlKey property", "shiftKey property", "altKey property", "metaKey property"}} -Modifier keys such as [shift]{keyname}, [control]{keyname}, [alt]{keyname}, and [meta]{keyname} ([command]{keyname} on Mac) generate key events just like normal keys. When looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events. +Modifier keys such as [shift]{keyname}, [ctrl]{keyname}, [alt]{keyname}, and [meta]{keyname} ([command]{keyname} on Mac) generate key events just like normal keys. When looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events. ```{lang: html, focus: true} <p>Press Control-Space to continue.</p> @@ -622,7 +622,7 @@ Giving an undefined value to `clearTimeout` or calling it on a timeout that has {{index "mousemove event"}} -We can use a slightly different pattern if we want to space responses so that they're separated by at least a certain length of ((time)) but want to fire them _during_ a series of events, not just afterward. For example, we might want to respond to `"mousemove"` events by showing the current coordinates of the mouse but only every 250 milliseconds. +We can use a slightly different pattern if we want to space responses so that they're separated by at least a certain length of ((time)) but want to fire them _during_ a series of events, not just afterward. For example, we might want to respond to `"mousemove"` events by showing the current coordinates of the mouse, but only every 250 milliseconds. ```{lang: html} <script> diff --git a/18_http.md b/18_http.md index 44eebf345..b3e8be227 100644 --- a/18_http.md +++ b/18_http.md @@ -622,7 +622,7 @@ The `<option>` tags for a `<select>` field can be accessed as an array-like obje {{index "multiple attribute", "binary number"}} -This example extracts the selected values from a `multiple` select field and uses them to compose a binary number from individual bits. Hold [control]{keyname} (or [command]{keyname} on a Mac) to select multiple options. +This example extracts the selected values from a `multiple` select field and uses them to compose a binary number from individual bits. Hold [ctrl]{keyname} (or [command]{keyname} on a Mac) to select multiple options. ```{lang: html} <select multiple> diff --git a/19_paint.md b/19_paint.md index 2c59c9150..33d63091c 100644 --- a/19_paint.md +++ b/19_paint.md @@ -766,7 +766,7 @@ There is still room for improvement in our program. Let's add a few more feature {{index "keyboard bindings (exercise)"}} -Add ((keyboard)) shortcuts to the application. The first letter of a tool's name selects the tool, and [control]{keyname}-Z or [command]{keyname}-Z activates undo. +Add ((keyboard)) shortcuts to the application. The first letter of a tool's name selects the tool, and [ctrl]{keyname}-Z or [command]{keyname}-Z activates undo. {{index "PixelEditor class", "tabindex attribute", "elt function", "keydown event"}} From cdb80719823a864b115e6be483f55bab96535ae4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 28 May 2024 18:24:53 +0200 Subject: [PATCH 343/392] Copyediting chapter 17 --- 17_canvas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/17_canvas.md b/17_canvas.md index e0d922e95..1e44a7564 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -116,7 +116,7 @@ In the ((canvas)) interface, a shape can be _filled_, meaning its area is given {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. +The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. {{index [state, "of canvas"]}} From ee5ab13d7a21b4d6bf4acf41cb0c5a3e431b1343 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 28 May 2024 18:26:33 +0200 Subject: [PATCH 344/392] Copyediting chapter 18 --- 18_http.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/18_http.md b/18_http.md index b3e8be227..f37d47bbb 100644 --- a/18_http.md +++ b/18_http.md @@ -259,7 +259,7 @@ When thinking in terms of remote procedure calls, HTTP is just a vehicle for com {{index "media type", "document format", [method, HTTP]}} -Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/user/larry`), which again returns the document representing the resource. +Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/users/larry`), which again returns the document representing the resource. This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy of a resource on the client for fast access). The concepts used in HTTP, which are well-designed, can provide a helpful set of principles to design your server interface around. @@ -285,7 +285,7 @@ Thus, when it works right, ((HTTPS)) prevents other people from impersonating th ## Form fields -Forms were originally designed for the pre-JavaScript web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. +Forms were originally designed for the pre-JavaScript web to allow websites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. {{index [DOM, fields]}} From 34f887f0d43135b67dc54ebe8489461f49f43819 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 28 May 2024 18:30:19 +0200 Subject: [PATCH 345/392] Copyediting chapter 19 --- 19_paint.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/19_paint.md b/19_paint.md index 33d63091c..7e2364cad 100644 --- a/19_paint.md +++ b/19_paint.md @@ -2,13 +2,13 @@ # Project: A Pixel Art Editor -{{quote {author: "Joan Miro", chapter: true} +{{quote {author: "Joan Miró", chapter: true} I look at the many colors before me. I look at my blank canvas. Then, I try to apply colors like words that shape poems, like notes that shape music. quote}} -{{index "Miro, Joan", "drawing program example", "project chapter"}} +{{index "Miró, Joan", "drawing program example", "project chapter"}} {{figure {url: "img/chapter_picture_19.jpg", alt: "Illustration showing a mosaic of black tiles, with jars of other tiles next to it", chapter: "framed"}}} @@ -618,7 +618,7 @@ The `data` property of the object returned by `getImageData` is an array of colo {{index "hexadecimal number", "toString method"}} -The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 16^2^ = 256 different numbers. The `toString` method of numbers can be given a base as argument, so `n.toString(16)` will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the `hex` helper function calls `padStart` to add a leading 0 when necessary. +The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 16^2^ = 256 different numbers. The `toString` method of numbers can be given a base as an argument, so `n.toString(16)` will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the `hex` helper function calls `padStart` to add a leading 0 when necessary. We can load and save now! That leaves one more feature before we're done. From 60ffd54b5898cd053dff595dfdb37a515154cc5f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 28 May 2024 19:17:04 +0200 Subject: [PATCH 346/392] Fix misspelled index term --- 16_game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/16_game.md b/16_game.md index 4bfa8e00c..0cd9d6915 100644 --- a/16_game.md +++ b/16_game.md @@ -8,7 +8,7 @@ All reality is a game. quote}} -{{index "Banks, Ian", "project chapter", simulation}} +{{index "Banks, Iain", "project chapter", simulation}} {{figure {url: "img/chapter_picture_16.jpg", alt: "Illustration showing a computer game character jumping over lava in a two dimensional world", chapter: "framed"}}} From 987e68967d2db6b38b480af8c5280810251062ee Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 29 May 2024 19:39:00 +0200 Subject: [PATCH 347/392] Copyediting for chapter 20 --- 20_node.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/20_node.md b/20_node.md index 731d1ba2a..bd85fea4c 100644 --- a/20_node.md +++ b/20_node.md @@ -38,7 +38,7 @@ In such programs, asynchronous programming is often helpful. It allows the progr {{index "programming language", "Node.js", standard}} -Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do in- and output. Thus, JavaScript could be fit onto Node's rather eccentric approach to network and file system programming without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the ((community)) around the language was used to an asynchronous programming style. +Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do input and output. Thus, JavaScript could be fit onto Node's rather eccentric approach to network and file system programming without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the ((community)) around the language was used to an asynchronous programming style. ## The node command @@ -356,7 +356,7 @@ A real web ((server)) usually does more than the one in the example—it looks a {{index "node:http package", "request function", "fetch function", [HTTP, client]}} -The `node:http` module also provides a `request` function that can be used to make HTTP requests. However, it is a lot more cumbersome to use than `fetch`, which we saw in [Chapter ?](http). Fortunately, `fetch` is also available in Node, as a global binding. Unless you want to do something very specific, such as processing the response document piece by piece as the data comes in over the network, I recommend sticking to `fetch`. +The `node:http` module also provides a `request` function that can be used to make HTTP requests. However, it is a lot more cumbersome to use than `fetch`, which we saw in [Chapter ?](http). Fortunately, `fetch` is also available in Node as a global binding. Unless you want to do something very specific, such as processing the response document piece by piece as the data comes in over the network, I recommend sticking to `fetch`. ## Streams From 59ea297f8d190f2061be6b5abfb7f87330c60303 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 31 May 2024 09:44:29 +0200 Subject: [PATCH 348/392] Make robot animation images load properly in /code sandbox --- code/animatevillage.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/animatevillage.js b/code/animatevillage.js index 0f8efc334..241d7921d 100644 --- a/code/animatevillage.js +++ b/code/animatevillage.js @@ -33,12 +33,15 @@ this.node = outer.appendChild(doc.createElement("div")) this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px" this.map = this.node.appendChild(doc.createElement("img")) - this.map.src = "img/village2x.png" + let imgPath = "img/" + if (/\/code($|\/)/.test(outer.ownerDocument.defaultView.location)) imgPath = "../" + imgPath + console.log(outer.ownerDocument.defaultView.location.toString(), /\/code($|\/)/.test(outer.ownerDocument.defaultView.localation), imgPath) + this.map.src = imgPath + "village2x.png" this.map.style.cssText = "vertical-align: -8px" this.robotElt = this.node.appendChild(doc.createElement("div")) this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;` let robotPic = this.robotElt.appendChild(doc.createElement("img")) - robotPic.src = "img/robot_moving2x.gif" + robotPic.src = imgPath + "robot_moving2x.gif" this.parcels = [] this.text = this.node.appendChild(doc.createElement("span")) From cb6b5d67ec22d336c9d946d101c960437cb97157 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 31 May 2024 10:02:00 +0200 Subject: [PATCH 349/392] Copyediting exercise hints --- 10_modules.md | 2 +- 11_async.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/10_modules.md b/10_modules.md index 257280ffd..ae9cd89e0 100644 --- a/10_modules.md +++ b/10_modules.md @@ -482,6 +482,6 @@ The `require` function given [earlier in this chapter](modules#require) supports {{index overriding, "circular dependency", "exports object"}} -The trick is that `require` adds the interface object for a module to its cache _before_ it starts loading the module. That way, if any `require` call made while it is running try to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack). +The trick is that `require` adds the interface object for a module to its cache _before_ it starts loading the module. That way, if any `require` call made while it is running tries to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack). hint}} diff --git a/11_async.md b/11_async.md index f193e42a6..363272618 100644 --- a/11_async.md +++ b/11_async.md @@ -789,7 +789,7 @@ function activityTable(day) { {{index "await keyword", scheduling}} -Which shows that the way you structure your promises can have a real effect on the way the work is scheduled. A simple loop with `await` in it will make the process completely linear—it waits for each file to load before proceeding. `Promise.all` makes it possible for multiple tasks to conceptually be worked on at the same time, allowing them to make progress while files are still being loaded. This can be faster, but it also makes the order in which things will happen less predictable. In this case, where we're only going to be incrementing numbers in a table, that isn't hard to do in a safe way. For other kinds of problems, it may be a lot more difficult. +Which shows that the way you structure your promises can have a real effect on the way the work is scheduled. A simple loop with `await` in it will make the process completely linear—it waits for each file to load before proceeding. `Promise.all` makes it possible for multiple tasks to conceptually be worked on at the same time, allowing them to make progress while files are still being loaded. This can be faster, but it also makes the order in which things will happen less predictable. In this case, where we're only going to be incrementing numbers in a table, which isn't hard to do in a safe way. For other kinds of problems, it may be a lot more difficult. {{index "rejecting (a promise)", "then method"}} From 0014c083601a484ebca3b71df3284de2d27c27df Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 14:20:26 +0200 Subject: [PATCH 350/392] Copyediting the introduction --- 00_intro.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/00_intro.md b/00_intro.md index 895eaa409..4546ee387 100644 --- a/00_intro.md +++ b/00_intro.md @@ -2,7 +2,7 @@ # Introduction -{{quote {author: "Ellen Ullman", title: "Close to the Machine: Technophilia and its Discontents", chapter: true} +{{quote {author: "Ellen Ullman", title: "Close to the Machine: Technophilia and Its Discontents", chapter: true} We think we are creating the system for our own purposes. We believe we are making it in our own image... But the computer is not really like us. It is a projection of a very slim part of ourselves: that portion devoted to logic, order, rule, and clarity. @@ -88,7 +88,7 @@ In the beginning, at the birth of computing, there were no programming languages {{index [programming, "history of"], "punch card", complexity}} -This is a program to add the numbers from 1 to 10 together and print the result: `1 + 2 + ... + 10 = 55`. It could run on a simple hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can imagine how tedious and error-prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable. +This is a program to add the numbers from 1 to 10 together and print the result: `1 + 2 + ... + 10 = 55`. It could run on a simple hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can imagine how tedious and error prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable. {{index bit, "wizard (mighty)"}} @@ -118,7 +118,7 @@ Although that is already more readable than the soup of bits, it is still rather [loop] Set “compare” to “count”. Subtract 11 from “compare”. - If “compare” is zero, continue at [end]. + If “compare” is 0, continue at [end]. Add “count” to “total”. Add 1 to “count”. Continue at [loop]. @@ -128,7 +128,7 @@ Although that is already more readable than the soup of bits, it is still rather {{index loop, jump, "summing example"}} -Can you see how the program works at this point? The first two lines give two memory locations their starting values: `total` will be used to build up the result of the computation, and `count` will keep track of the number that we are currently looking at. The lines using `compare` are probably the most confusing ones. The program wants to see whether `count` is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can only test whether a number is zero and make a decision based on that. It therefore uses the memory location labeled `compare` to compute the value of `count - 11` and makes a decision based on that value. The next two lines add the value of `count` to the result and increment `count` by 1 every time the program decides that `count` is not 11 yet. +Can you see how the program works at this point? The first two lines give two memory locations their starting values: `total` will be used to build up the result of the computation, and `count` will keep track of the number that we are currently looking at. The lines using `compare` are probably the most confusing ones. The program wants to see whether `count` is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can test only whether a number is zero and make a decision based on that. It therefore uses the memory location labeled `compare` to compute the value of `count - 11` and makes a decision based on that value. The next two lines add the value of `count` to the result and increment `count` by 1 every time the program decides that `count` is not 11 yet. Here is the same program in JavaScript: @@ -239,7 +239,7 @@ The language part of the book starts with four chapters that introduce the basic After a [first project chapter](robot) that builds a crude delivery robot, the language part of the book continues with chapters on [error handling and bug fixing](error), [regular expressions](regexp) (an important tool for working with text), [modularity](modules) (another defense against complexity), and [asynchronous programming](async) (dealing with events that take time). The [second project chapter](language), where we implement a programming language, concludes the first part of the book. -The second part of the book, Chapters [?](browser) to [?](paint), describes the tools that browser JavaScript has access to. You'll learn to display things on the screen (Chapters [?](dom) and [?](canvas)), respond to user input ([Chapter ?](event)), and communicate over the network ([Chapter ?](http)). There are again two project chapters in this part, building a [platform game](game) and a [pixel paint program](paint). +The second part of the book, Chapters [?](browser) to [?](paint), describes the tools that browser JavaScript has access to. You'll learn to display things on the screen (Chapters [?](dom) and [?](canvas)), respond to user input ([Chapter ?](event)), and communicate over the network ([Chapter ?](http)). There are again two project chapters in this part: building a [platform game](game) and a [pixel paint program](paint). [Chapter ?](node) describes Node.js, and [Chapter ?](skillsharing) builds a small website using that tool. From 187ab2cea5d7ebb53424eae56ea419c755019b40 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 14:39:24 +0200 Subject: [PATCH 351/392] Copyediting chapter 1 --- 01_values.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/01_values.md b/01_values.md index e973c0c33..e560e1c4d 100644 --- a/01_values.md +++ b/01_values.md @@ -29,7 +29,7 @@ For example, we can express the number 13 in bits. This works the same way as a 128 64 32 16 8 4 2 1 ``` -That's the binary number 00001101. Its non-zero digits stand for 8, 4, and 1, and add up to 13. +That's the binary number 00001101. Its nonzero digits stand for 8, 4, and 1, and add up to 13. ## Values @@ -79,7 +79,7 @@ Fractional numbers are written using a dot: {{index exponent, "scientific notation", [number, notation]}} -For very big or very small numbers, you may also use scientific notation by adding an _e_ (for _exponent_), followed by the exponent of the number: +For very big or very small numbers, you may also use scientific notation by adding an _e_ (for _exponent_), followed by the exponent of the number. ``` 2.998e8 @@ -107,7 +107,7 @@ The `+` and `*` symbols are called _operators_. The first stands for addition an {{index grouping, parentheses, precedence}} -Does this example mean "Add 4 and 100, and multiply the result by 11", or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. As in mathematics, you can change this by wrapping the addition in parentheses: +Does this example mean "Add 4 and 100, and multiply the result by 11", or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. As in mathematics, you can change this by wrapping the addition in parentheses. ```{meta: "expr"} (100 + 4) * 11 @@ -204,7 +204,7 @@ Strings written with single or double quotes behave very much the same—the onl `half of 100 is ${100 / 2}` ``` -When you write something inside `${}` in a template literal, its result will be computed, converted to a string, and included at that position. This example produces "_half of 100 is 50_". +When you write something inside `${}` in a template literal, its result will be computed, converted to a string, and included at that position. This example produces the string `"half of 100 is 50"`. ## Unary operators @@ -223,11 +223,11 @@ console.log(typeof "x") {{id "console.log"}} -We will use `console.log` in example code to indicate that we want to see the result of evaluating something. More about that in the [next chapter](program_structure). +We will use `console.log` in example code to indicate that we want to see the result of evaluating something. (More about that in the [next chapter](program_structure).) {{index negation, "- operator", "binary operator", "unary operator"}} -The other operators shown so far in this chapter all operated on two values, but `typeof` takes only one. Operators that use two values are called _binary_ operators, while those that take one are called _unary_ operators. The minus operator can be used both as a binary operator and as a unary operator. +The other operators shown so far in this chapter all operated on two values, but `typeof` takes only one. Operators that use two values are called _binary_ operators, while those that take one are called _unary_ operators. The minus operator (`-`) can be used both as a binary operator and as a unary operator. ``` console.log(- (10 - 2)) @@ -257,7 +257,7 @@ console.log(3 < 2) The `>` and `<` signs are the traditional symbols for "is greater than" and "is less than", respectively. They are binary operators. Applying them results in a Boolean value that indicates whether they hold true in this case. -Strings can be compared in the same way: +Strings can be compared in the same way. ``` console.log("Aardvark" < "Zoroaster") @@ -395,7 +395,7 @@ That behavior is often useful. When you want to test whether a value has a real {{index "type coercion", [Boolean, "conversion to"], "=== operator", "!== operator", comparison}} -What if you want to test whether something refers to the precise value `false`? Expressions like `0 == false` and `"" == false` are also true because of automatic type conversion. When you do _not_ want any type conversions to happen, there are two additional operators: `===` and `!==`. The first tests whether a value is _precisely_ equal to the other, and the second tests whether it is not precisely equal. Thus `"" === false` is false as expected. +What if you want to test whether something refers to the precise value `false`? Expressions like `0 == false` and `"" == false` are also true because of automatic type conversion. When you do _not_ want any type conversions to happen, there are two additional operators: `===` and `!==`. The first tests whether a value is _precisely_ equal to the other, and the second tests whether it is not precisely equal. Thus `"" === false` is false, as expected. I recommend using the three-character comparison operators defensively to prevent unexpected type conversions from tripping you up. But when you're certain the types on both sides will be the same, there is no problem with using the shorter operators. @@ -418,7 +418,7 @@ console.log("Agnes" || "user") {{index "default value"}} -We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put `||` after it with a replacement value. If the initial value can be converted to false, you'll get the replacement instead. The rules for converting strings and numbers to Boolean values state that `0`, `NaN`, and the empty string (`""`) count as `false`, while all the other values count as `true`. That means `0 || -1` produces `-1`, and `"" || "!?"` yields `"!?"`. +We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put `||` after it with a replacement value. If the initial value can be converted to false, you'll get the replacement instead. The rules for converting strings and numbers to Boolean values state that `0`, `NaN`, and the empty string (`""`) count as false, while all the other values count as true. That means `0 || -1` produces `-1`, and `"" || "!?"` yields `"!?"`. {{index "?? operator", null, undefined}} From 80c1f48fbe751e35de47c646728aad59dfbfb31e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 14:45:51 +0200 Subject: [PATCH 352/392] Copyediting chapter 2 --- 02_program_structure.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/02_program_structure.md b/02_program_structure.md index 1bd551d25..71c19e4ef 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -43,14 +43,14 @@ It is a useless program, though. An ((expression)) can be content to just produc {{index "programming style", "automatic semicolon insertion", semicolon}} -In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next ((line)) will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error-prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you've learned more about the subtleties of missing semicolons. +In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next ((line)) will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you've learned more about the subtleties of missing semicolons. ## Bindings {{indexsee variable, binding}} {{index [syntax, statement], [binding, definition], "side effect", [memory, organization], [state, in binding]}} -How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value must be used immediately or it will dissipate again. To catch and hold values, JavaScript provides a thing called a _binding_, or _variable_: +How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value must be used immediately or it will dissipate again. To catch and hold values, JavaScript provides a thing called a _binding_, or _variable_. ``` let caught = 5 * 5; @@ -87,7 +87,7 @@ console.log(mood); You should imagine bindings as tentacles rather than boxes. They do not _contain_ values; they _grasp_ them—two bindings can refer to the same value. A program can access only the values to which it still has a reference. When you need to remember something, you either grow a tentacle to hold on to it or reattach one of your existing tentacles to it. -Let's look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. When he pays back $35, you give this binding a new value: +Let's look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. When he pays back $35, you give this binding a new value. ``` let luigisDebt = 140; @@ -110,7 +110,7 @@ console.log(one + two); // → 3 ``` -The words `var` and `const` can also be used to create bindings, in a similar fashion to `let`: +The words `var` and `const` can also be used to create bindings, in a similar fashion to `let`. ``` var name = "Ayda"; @@ -162,7 +162,7 @@ The collection of bindings and their values that exist at a given time is called {{indexsee "calling (of functions)", [function, application]}} {{index output, function, [function, application], [browser, environment]}} -A lot of the values provided in the default environment have the type _((function))_. A function is a piece of program wrapped in a value. Such values can be _applied_ in order to run the wrapped program. For example, in a browser environment, the binding `prompt` holds a function that shows a little ((dialog box)) asking for user input. It is used like this: +A lot of the values provided in the default environment have the type _((function))_. A function is a piece of program wrapped in a value. Such values can be _applied_ in order to run the wrapped program. For example, in a browser environment, the binding `prompt` holds a function that shows a little ((dialog)) asking for user input. It is used like this: ``` prompt("Enter passcode"); @@ -203,7 +203,7 @@ Though binding names cannot contain ((period character))s, `console.log` does ha {{index [comparison, "of numbers"], "return value", "Math.max function", maximum}} -Showing a dialog box or writing text to the screen is a _((side effect))_. Many functions are useful because of the side effects they produce. Functions may also produce values, in which case they don't need to have a side effect to be useful. For example, the function `Math.max` takes any amount of number arguments and gives back the greatest: +Showing a dialog box or writing text to the screen is a _((side effect))_. Many functions are useful because of the side effects they produce. Functions may also produce values, in which case they don't need to have a side effect to be useful. For example, the function `Math.max` takes any amount of number arguments and gives back the greatest. ``` console.log(Math.max(2, 4)); From bfd22e51a89acd69058f20d728285c12f8fb6c6f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 15:00:46 +0200 Subject: [PATCH 353/392] Copyediting chapter 3 --- 03_functions.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/03_functions.md b/03_functions.md index 10561cb34..505554fb7 100644 --- a/03_functions.md +++ b/03_functions.md @@ -214,7 +214,7 @@ The arrow comes _after_ the list of parameters and is followed by the function's {{index [braces, "function body"], "square example", [parentheses, arguments]}} -When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression, rather than a ((block)) in braces, that expression will be returned from the function. So, these two definitions of `square` do the same thing: +When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression rather than a ((block)) in braces, that expression will be returned from the function. So, these two definitions of `square` do the same thing: ``` const square1 = (x) => { return x * x; }; @@ -260,11 +260,11 @@ We could show the flow of control schematically like this: ```{lang: null} not in function - in greet - in console.log - in greet + in greet + in console.log + in greet not in function - in console.log + in console.log not in function ``` @@ -351,9 +351,9 @@ console.log("C", "O", 2); {{index "call stack", "local binding", [function, "as value"], scope}} -The ability to treat functions as values, combined with the fact that local bindings are recreated every time a function is called, brings up an interesting question: what happens to local bindings when the function call that created them is no longer active? +The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question: what happens to local bindings when the function call that created them is no longer active? -The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding: +The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding. ``` function wrapValue(n) { @@ -375,7 +375,7 @@ This feature—being able to reference a specific instance of a local binding in {{index "multiplier function"}} -With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount: +With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount. ``` function multiplier(factor) { @@ -732,7 +732,7 @@ hint}} You can get the *N*th character, or letter, from a string by writing `[N]` after the string (for example, `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. -Write a function `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase B characters there are in the string. +Write a function called `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase B characters there are in the string. Next, write a function called `countChar` that behaves like `countBs`, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase B characters). Rewrite `countBs` to make use of this new function. From ddae57b5e1bc02cd58091227b90d0732d16148b9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 18:20:52 +0200 Subject: [PATCH 354/392] Avoid stray console.log in chapter 6's base code --- 06_object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_object.md b/06_object.md index 733c1f87a..8f1a03e8c 100644 --- a/06_object.md +++ b/06_object.md @@ -708,7 +708,7 @@ JavaScript's prototype system makes it possible to create a _new_ class, much li In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class. -```{includeCode: "top_lines: 17"} +```{includeCode: "top_lines: 12"} class LengthList extends List { #length; From 036ccdd2b57568fe3122084ef3c34c162d89df4e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 18:23:32 +0200 Subject: [PATCH 355/392] Fix a bug in the solution to 6.3 --- code/solutions/06_3_iterable_groups.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/solutions/06_3_iterable_groups.js b/code/solutions/06_3_iterable_groups.js index 0dfe8f62b..557a35fb2 100644 --- a/code/solutions/06_3_iterable_groups.js +++ b/code/solutions/06_3_iterable_groups.js @@ -29,6 +29,9 @@ class Group { } class GroupIterator { + #members; + #position; + constructor(members) { this.#members = members; this.#position = 0; From 6095635a99743c86c98de6b644a78b5406039d5b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 27 Jun 2024 23:31:22 +0200 Subject: [PATCH 356/392] Adjust indentation in introduction example --- 00_intro.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/00_intro.md b/00_intro.md index 4546ee387..503a81d02 100644 --- a/00_intro.md +++ b/00_intro.md @@ -113,17 +113,17 @@ Each line of the previous program contains a single instruction. It could be wri Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps: ```{lang: "null"} - Set “total” to 0. - Set “count” to 1. + Set “total” to 0. + Set “count” to 1. [loop] - Set “compare” to “count”. - Subtract 11 from “compare”. - If “compare” is 0, continue at [end]. - Add “count” to “total”. - Add 1 to “count”. - Continue at [loop]. + Set “compare” to “count”. + Subtract 11 from “compare”. + If “compare” is 0, continue at [end]. + Add “count” to “total”. + Add 1 to “count”. + Continue at [loop]. [end] - Output “total”. + Output “total”. ``` {{index loop, jump, "summing example"}} From 578c18216a6eede20121a492d191921542bf6fdc Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 28 Jun 2024 08:34:47 +0200 Subject: [PATCH 357/392] Copyediting chapter 4 --- 04_data.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/04_data.md b/04_data.md index 07ecfde2f..d9662bfa6 100644 --- a/04_data.md +++ b/04_data.md @@ -54,7 +54,7 @@ We could get creative with strings—after all, strings can have any length, so {{index [array, creation], "[] (array)"}} -Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an _array_ and is written as a list of values between ((square brackets)), separated by commas: +Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an _array_ and is written as a list of values between ((square brackets)), separated by commas. ``` let listOfNumbers = [2, 3, 5, 7, 11]; @@ -133,7 +133,7 @@ Properties that contain functions are generally called _methods_ of the value th {{id array_methods}} -This example demonstrates two methods you can use to manipulate arrays: +This example demonstrates two methods you can use to manipulate arrays. ``` let sequence = [1, 2, 3]; @@ -192,7 +192,7 @@ let descriptions = { {{index [braces, object]}} -This means that braces have _two_ meanings in JavaScript. At the start of a ((statement)), they begin a ((block)) of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem. The one case where this does come up is when you want to return an object from a short-hand arrow function—you can't write `n => {prop: n}` since the braces will be interpreted as a function body. Instead, you have to put a set of parentheses around the object to make it clear that it is an expression. +This means that braces have _two_ meanings in JavaScript. At the start of a ((statement)), they begin a ((block)) of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem. The one case where this does come up is when you want to return an object from a shorthand arrow function—you can't write `n => {prop: n}` since the braces will be interpreted as a function body. Instead, you have to put a set of parentheses around the object to make it clear that it is an expression. {{index undefined}} @@ -264,7 +264,7 @@ let journal = [ {events: ["weekend", "cycling", "break", "peanuts", "beer"], squirrel: true}, - /* and so on... */ + /* And so on... */ ]; ``` @@ -518,7 +518,7 @@ for (let event of journalEvents(JOURNAL)) { // → weekend: 0.1371988681 // → bread: -0.0757554019 // → pudding: -0.0648203724 -// and so on... +// And so on... ``` Most correlations seem to lie close to zero. Eating carrots, bread, or pudding apparently does not trigger squirrel-lycanthropy. The transformations _do_ seem to occur somewhat more often on weekends. Let's filter the results to show only correlations greater than 0.1 or less than -0.1: @@ -541,7 +541,7 @@ for (let event of journalEvents(JOURNAL)) { Aha! There are two factors with a ((correlation)) clearly stronger than the others. Eating ((peanuts)) has a strong positive effect on the chance of turning into a squirrel, whereas brushing teeth has a significant negative effect. -Interesting. Let's try something: +Interesting. Let's try something. ``` for (let entry of JOURNAL) { @@ -560,13 +560,13 @@ Knowing this, Jacques stops eating peanuts altogether and finds that his transfo {{index "weresquirrel example"}} -But it only takes a few months for him to notice that something is missing from this entirely human way of living. Without his feral adventures, Jacques hardly feels alive at all. He decides he'd rather be a full-time wild animal. After building a beautiful little tree house in the forest and equipping it with a peanut butter dispenser and a ten-year supply of peanut butter, he changes form one last time, and lives the short and energetic life of a squirrel. +But it takes only a few months for him to notice that something is missing from this entirely human way of living. Without his feral adventures, Jacques hardly feels alive at all. He decides he'd rather be a full-time wild animal. After building a beautiful little tree house in the forest and equipping it with a peanut butter dispenser and a ten-year supply of peanut butter, he changes form one last time, and lives the short and energetic life of a squirrel. ## Further arrayology {{index [array, methods], [method, array]}} -Before finishing the chapter, I want to introduce you to a few more object-related concepts. I'll start by introducing some generally useful array methods. +Before finishing the chapter, I want to introduce you to a few more object-related concepts. I'll start with some generally useful array methods. {{index "push method", "pop method", "shift method", "unshift method"}} @@ -745,7 +745,7 @@ When such a function is called, the _((rest parameter))_ is bound to an array co {{index [function, application]}} -You can use a similar three-dot notation to _call_ a function with an array of arguments: +You can use a similar three-dot notation to _call_ a function with an array of arguments. ``` let numbers = [5, 1, 7]; @@ -797,7 +797,7 @@ Many languages will stop you, or at least warn you, when you are defining a bind {{index "Math.cos function", "Math.sin function", "Math.tan function", "Math.acos function", "Math.asin function", "Math.atan function", "Math.PI constant", cosine, sine, tangent, "PI constant", pi}} -Back to the `Math` object. If you need to do ((trigonometry)), `Math` can help. It contains `cos` (cosine), `sin` (sine), and `tan` (tangent), as well as their inverse functions, `acos`, `asin`, and `atan`, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as `Math.PI`. There is an old programming tradition of writing the names of ((constant)) values in all caps: +Back to the `Math` object. If you need to do ((trigonometry)), `Math` can help. It contains `cos` (cosine), `sin` (sine), and `tan` (tangent), as well as their inverse functions, `acos`, `asin`, and `atan`, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as `Math.PI`. There is an old programming tradition of writing the names of ((constant)) values in all caps. ```{test: no} function randomPointOnCircle(radius) { @@ -813,7 +813,7 @@ If you're not familiar with sines and cosines, don't worry. I'll explain them wh {{index "Math.random function", "random number"}} -The previous example used `Math.random`. This is a function that returns a new pseudorandom number between zero (inclusive) and one (exclusive) every time you call it: +The previous example used `Math.random`. This is a function that returns a new pseudorandom number between 0 (inclusive) and 1 (exclusive) every time you call it: ```{test: no} console.log(Math.random()); @@ -877,7 +877,7 @@ This also works for bindings created with `let`, `var`, or `const`. If you know {{index [object, property], [braces, object]}} -A similar trick works for objects, using braces instead of square brackets: +A similar trick works for objects, using braces instead of square brackets. ``` let {name} = {name: "Faraji", age: 23}; @@ -905,7 +905,7 @@ console.log(city({name: "Vera"})); // → undefined ``` -The expression `a?.b` means the same as `a.b` when `a` isn't null or undefined. When it is, it evaluates to undefined. This can be convenient when, as in the example, you aren't sure that a given property exists or when a variable might hold an undefined value. +The expression `a?.b` means the same as `a.b` when `a` isn't null or undefined. When it is, it evaluates to `undefined`. This can be convenient when, as in the example, you aren't sure that a given property exists or when a variable might hold an undefined value. A similar notation can be used with square bracket access, and even with function calls, by putting `?.` in front of the parentheses or brackets: @@ -1029,7 +1029,7 @@ hint}} {{index "reversing (exercise)", "reverse method", [array, methods]}} -Arrays have a `reverse` method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, `reverseArray` and `reverseArrayInPlace`. The first, `reverseArray`, should take an array as argument and produce a _new_ array that has the same elements in the inverse order. The second, `reverseArrayInPlace`, should do what the `reverse` method does: _modify_ the array given as argument by reversing its elements. Neither may use the standard `reverse` method. +Arrays have a `reverse` method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, `reverseArray` and `reverseArrayInPlace`. The first, `reverseArray`, should take an array as its argument and produce a _new_ array that has the same elements in the inverse order. The second, `reverseArrayInPlace`, should do what the `reverse` method does: _modify_ the array given as its argument by reversing its elements. Neither may use the standard `reverse` method. {{index efficiency, "pure function", "side effect"}} @@ -1149,7 +1149,7 @@ hint}} The `==` operator compares objects by identity, but sometimes you'd prefer to compare the values of their actual properties. -Write a function `deepEqual` that takes two values and returns true only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to `deepEqual`. +Write a function `deepEqual` that takes two values and returns `true` only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to `deepEqual`. {{index null, "=== operator", "typeof operator"}} From 2a7e8bb66bb5fe383e132160f5d06845e2f2cab5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 28 Jun 2024 08:41:49 +0200 Subject: [PATCH 358/392] Copyediting chapter 5 --- 05_higher_order.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/05_higher_order.md b/05_higher_order.md index 7701331cf..70c253019 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -18,7 +18,7 @@ A large program is a costly program, and not just because of the time it takes t {{index "summing example"}} -Let's briefly go back to the final two example programs in the introduction. The first is self-contained and six lines long. +Let's briefly go back to the final two example programs in the introduction. The first is self contained and six lines long. ``` let total = 0, count = 1; @@ -97,7 +97,7 @@ for (let i = 0; i < 10; i++) { } ``` -Can we abstract "doing something _N_ times" as a function? Well, it's easy to write a function that calls `console.log` _N_ times: +Can we abstract "doing something _N_ times" as a function? Well, it's easy to write a function that calls `console.log` _N_ times. ``` function repeatLog(n) { @@ -111,7 +111,7 @@ function repeatLog(n) { {{indexsee "higher-order function", "function, higher-order"}} -But what if we want to do something other than logging the numbers? Since "doing something" can be represented as a function and functions are just values, we can pass our action as a function value: +But what if we want to do something other than logging the numbers? Since "doing something" can be represented as a function and functions are just values, we can pass our action as a function value. ```{includeCode: "top_lines: 5"} function repeat(n, action) { @@ -126,7 +126,7 @@ repeat(3, console.log); // → 2 ``` -We don't have to pass a predefined function to `repeat`. Often, it is easier to create a function value on the spot instead: +We don't have to pass a predefined function to `repeat`. Often, it is easier to create a function value on the spot instead. ``` let labels = []; @@ -149,7 +149,7 @@ Functions that operate on other functions, either by taking them as arguments or {{index abstraction}} -Higher-order functions allow us to abstract over _actions_, not just values. They come in several forms. For example, we can have functions that create new functions: +Higher-order functions allow us to abstract over _actions_, not just values. They come in several forms. For example, we can have functions that create new functions. ``` function greaterThan(n) { @@ -160,7 +160,7 @@ console.log(greaterThan10(11)); // → true ``` -We can also have functions that change other functions: +We can also have functions that change other functions. ``` function noisy(f) { @@ -176,7 +176,7 @@ noisy(Math.min)(3, 2, 1); // → called with [3, 2, 1] , returned 1 ``` -We can even write functions that provide new types of ((control flow)): +We can even write functions that provide new types of ((control flow)). ``` function unless(test, then) { @@ -194,7 +194,7 @@ repeat(3, n => { {{index [array, methods], [array, iteration], "forEach method"}} -There is a built-in array method, `forEach`, that provides something like a `for`/`of` loop as a higher-order function: +There is a built-in array method, `forEach`, that provides something like a `for`/`of` loop as a higher-order function. ``` ["A", "B"].forEach(l => console.log(l)); @@ -234,7 +234,7 @@ Such an object tells us the name of the script, the Unicode ranges assigned to i {{index "slice method"}} -The `ranges` property contains an array of Unicode character ((range))s, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower ((bound)) is inclusive (code 994 is a Coptic character) and the upper bound is non-inclusive (code 1008 isn't). +The `ranges` property contains an array of Unicode character ((range))s, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower ((bound)) is inclusive (code 994 is a Coptic character) and the upper bound is noninclusive (code 1008 isn't). ## Filtering arrays @@ -282,7 +282,7 @@ Say we have an array of objects representing scripts, produced by filtering the {{index [function, "higher-order"]}} -The `map` method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been _mapped_ to a new form by the function: +The `map` method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been _mapped_ to a new form by the function. ``` function map(array, transform) { @@ -548,11 +548,11 @@ console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"')); {{index "characterScript function", "filter method"}} -The function first counts the characters by name, using `characterScript` to assign them a name and falling back to the string `"none"` for characters that aren't part of any script. The `filter` call drops the entry for `"none"` from the resulting array since we aren't interested in those characters. +The function first counts the characters by name, using `characterScript` to assign them a name and falling back to the string `"none"` for characters that aren't part of any script. The `filter` call drops the entry for `"none"` from the resulting array, since we aren't interested in those characters. {{index "reduce method", "map method", "join method", [array, methods]}} -To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If we find no such characters, the function returns a specific string. Otherwise it transforms the counting entries into readable strings with `map` and then combines them with `join`. +To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If we find no such characters, the function returns a specific string. Otherwise, it transforms the counting entries into readable strings with `map` and then combines them with `join`. ## Summary @@ -581,7 +581,7 @@ if}} {{index "your own loop (example)", "for loop"}} -Write a higher-order function `loop` that provides something like a `for` loop statement. It should take a value, a test function, an update function, and a body function. Each iteration, it should first run the test function on the current loop value and stop if that returns false. It should then call the body function, giving it the current value, and finally call the update function to create a new value and start over from the beginning. +Write a higher-order function `loop` that provides something like a `for` loop statement. It should take a value, a test function, an update function, and a body function. Each iteration, it should first run the test function on the current loop value and stop if that returns `false`. It should then call the body function, giving it the current value, and finally call the update function to create a new value and start over from the beginning. When defining the function, you can use a regular loop to do the actual looping. @@ -602,7 +602,7 @@ if}} {{index "predicate function", "everything (exercise)", "every method", "some method", [array, methods], "&& operator", "|| operator"}} -Arrays also have an `every` method analogous to the `some` method. This method returns true when the given function returns true for _every_ element in the array. In a way, `some` is a version of the `||` operator that acts on arrays, and `every` is like the `&&` operator. +Arrays also have an `every` method analogous to the `some` method. This method returns `true` when the given function returns `true` for _every_ element in the array. In a way, `some` is a version of the `||` operator that acts on arrays, and `every` is like the `&&` operator. Implement `every` as a function that takes an array and a predicate function as parameters. Write two versions, one using a loop and one using the `some` method. @@ -627,7 +627,7 @@ if}} {{index "everything (exercise)", "short-circuit evaluation", "return keyword"}} -Like the `&&` operator, the `every` method can stop evaluating further elements as soon as it has found one that doesn't match. So the loop-based version can jump out of the loop—with `break` or `return`—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return true. +Like the `&&` operator, the `every` method can stop evaluating further elements as soon as it has found one that doesn't match. So the loop-based version can jump out of the loop—with `break` or `return`—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return `true`. To build `every` on top of `some`, we can apply _((De Morgan's laws))_, which state that `a && b` equals `!(!a || !b)`. This can be generalized to arrays, where all elements in the array match if there is no element in the array that does not match. From deb2003dc22b01616630ec16d912e9b49987af92 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 28 Jun 2024 08:50:32 +0200 Subject: [PATCH 359/392] Copyediting chapter 6 --- 06_object.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/06_object.md b/06_object.md index 8f1a03e8c..a3522ffbb 100644 --- a/06_object.md +++ b/06_object.md @@ -22,7 +22,7 @@ In programming culture, _((object-oriented programming))_ is a set of techniques The main idea in object-oriented programming is to use objects, or rather _types_ of objects, as the unit of program organization. Setting up a program as a number of strictly separated object types provides a way to think about its structure and thus to enforce some kind of discipline, preventing everything from becoming entangled. -The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). The people who design and assemble a mixer have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell so that the people who only want to mix pancake batter don't have to worry about all that—they only have to understand the few knobs that the mixer can be operated with. +The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). The people who design and assemble a mixer have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell so that the people who only want to mix pancake batter don't have to worry about all that—they have to understand only the few knobs that the mixer can be operated with. {{index "class"}} @@ -100,9 +100,9 @@ If I had written the argument to `some` using the `function` keyword, this code ## Prototypes -One way to create a rabbit object type with a `speak` method would be to create a helper function that has a rabbit type as parameter and returns an object holding that as its `type` property and our speak function in its `speak` property. +One way to create a rabbit object type with a `speak` method would be to create a helper function that has a rabbit type as its parameter and returns an object holding that as its `type` property and our speak function in its `speak` property. -All rabbits share that same method. Especially for types with many methods, it would be nice if there was a way to keep a type's methods in a single place, rather than adding them to each object individually. +All rabbits share that same method. Especially for types with many methods, it would be nice if there were a way to keep a type's methods in a single place, rather than adding them to each object individually. {{index [property, inheritance], [object, property], "Object prototype"}} @@ -151,7 +151,7 @@ Such a prototype object will itself have a prototype, often `Object.prototype`, {{index "rabbit example", "Object.create function"}} -You can use `Object.create` to create an object with a specific ((prototype)): +You can use `Object.create` to create an object with a specific ((prototype)). ```{includeCode: "top_lines: 7"} let protoRabbit = { @@ -210,7 +210,7 @@ class Rabbit { {{index "prototype property", [braces, class]}} -The `class` keyword starts a ((class declaration)), which allows us to define a constructor and a set of methods together. Any number of methods may be written inside the declaration's braces. This code has the effect of defining a binding called `Rabbit`, which holds a function that runs the code in `constructor` and has a `prototype` property which holds the `speak` method. +The `class` keyword starts a ((class declaration)), which allows us to define a constructor and a set of methods together. Any number of methods may be written inside the declaration's braces. This code has the effect of defining a binding called `Rabbit`, which holds a function that runs the code in `constructor` and has a `prototype` property that holds the `speak` method. {{index "new operator", "this binding", [object, creation]}} @@ -281,7 +281,7 @@ It is common for classes to define some properties and ((method))s for internal {{index [method, private]}} -To declare a private method, put a `#` sign in front of its name. Such methods can only be called from inside the `class` declaration that defines them. +To declare a private method, put a `#` sign in front of its name. Such methods can be called only from inside the `class` declaration that defines them. ``` class SecretiveObject { @@ -301,7 +301,7 @@ If you try to call `#getSecret` from outside the class, you get an error. Its ex To use private instance properties, you must declare them. Regular properties can be created by just assigning to them, but private properties _must_ be declared in the class declaration to be available at all. -This class implements an appliance for getting a random whole number below a given maximum number. It only has one ((public)) property: `getNumber`. +This class implements an appliance for getting a random whole number below a given maximum number. It has only one ((public)) property: `getNumber`. ``` class RandomSource { @@ -392,11 +392,11 @@ console.log("Is toString's age known?", "toString" in ages); {{index "Object.prototype", "toString method"}} -Here, the object's property names are the people's names and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet, because plain objects derive from `Object.prototype`, it looks like the property is there. +Here, the object's property names are the people's names and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet because plain objects derive from `Object.prototype`, it looks like the property is there. {{index "Object.create function", prototype}} -As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, you can create objects with _no_ prototype. If you pass `null` to `Object.create`, the resulting object will not derive from `Object.prototype` and can safely be used as a map. +For this reason, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, you can create objects with _no_ prototype. If you pass `null` to `Object.create`, the resulting object will not derive from `Object.prototype` and can be safely used as a map. ``` console.log("toString" in Object.create(null)); @@ -463,7 +463,7 @@ This technique is called _polymorphism_. Polymorphic code can work with values o {{index "forEach method"}} -An example of a widely used interface is that of ((array-like object))s which have a `length` property holding a number and numbered properties for each of their elements. Both arrays and strings support this interface, as do various other objects, some of which we'll see later in the chapters about the browser. Our implementation of `forEach` from [Chapter ?](higher_order) works on anything that provides this interface. In fact, so does `Array.prototype.forEach`. +An example of a widely used interface is that of ((array-like object))s that have a `length` property holding a number and numbered properties for each of their elements. Both arrays and strings support this interface, as do various other objects, some of which we'll see later in the chapters about the browser. Our implementation of `forEach` from [Chapter ?](higher_order) works on anything that provides this interface. In fact, so does `Array.prototype.forEach`. ``` Array.prototype.forEach.call({ @@ -531,7 +531,7 @@ The `Temperature` class allows you to read and write the temperature in either d Sometimes you want to attach some properties directly to your constructor function rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. -Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. For example, the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit: +Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. For example, the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit. ``` let boil = Temperature.fromFahrenheit(212); @@ -665,7 +665,7 @@ class ListIterator { The class tracks the progress of iterating through the list by updating its `list` property to move to the next list object whenever a value is returned and reports that it is done when that list is empty (null). -Let's set up the `List` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead. +Let's set up the `List` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead. ```{includeCode: true} List.prototype[Symbol.iterator] = function() { @@ -836,7 +836,7 @@ Use the `===` operator, or something equivalent such as `indexOf`, to determine {{index "static method"}} -Give the class a static `from` method that takes an iterable object as argument and creates a group that contains all the values produced by iterating over it. +Give the class a static `from` method that takes an iterable object as its argument and creates a group that contains all the values produced by iterating over it. {{if interactive From a2835df1146b0a5c991d0deb3d0f71f1c65bbbb7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 28 Jun 2024 08:52:29 +0200 Subject: [PATCH 360/392] Copyediting chapter 7 --- 07_robot.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/07_robot.md b/07_robot.md index 24414657c..2ac9acb80 100644 --- a/07_robot.md +++ b/07_robot.md @@ -113,7 +113,7 @@ class VillageState { } ``` -The `move` method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state since this is not a valid move. +The `move` method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state, since this is not a valid move. {{index "map method", "filter method"}} @@ -163,7 +163,7 @@ Unfortunately, although understanding a system built on persistent data structur {{index simulation, "virtual world"}} -A delivery ((robot)) looks at the world and decides in which direction it wants to move. As such, we could say that a robot is a function that takes a `VillageState` object and returns the name of a nearby place. +A delivery ((robot)) looks at the world and decides in which direction it wants to move. So we could say that a robot is a function that takes a `VillageState` object and returns the name of a nearby place. {{index "runRobot function"}} @@ -205,7 +205,7 @@ function randomRobot(state) { {{index "Math.random function", "Math.floor function", [array, "random element"]}} -Remember that `Math.random()` returns a number between zero and one—but always below one. Multiplying such a number by the length of an array and then applying `Math.floor` to it gives us a random index for the array. +Remember that `Math.random()` returns a number between 0 and 1—but always below 1. Multiplying such a number by the length of an array and then applying `Math.floor` to it gives us a random index for the array. Since this robot does not need to remember anything, it ignores its second argument (remember that JavaScript functions can be called with extra arguments without ill effects) and omits the `memory` property in its returned object. @@ -304,7 +304,7 @@ The problem of finding a route through a ((graph)) is a typical _((search proble The number of possible routes through a graph is infinite. But when searching for a route from _A_ to _B_, we are interested only in the ones that start at _A_. We also don't care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider. -In fact, since we are mostly interested in the _shortest_ route, we want to make sure we look at short routes before we look at longer ones. A good approach would be to "grow" routes from the starting point, exploring every reachable place that hasn't been visited yet until a route reaches the goal. That way, we'll only explore routes that are potentially interesting, and we know that the first route we find is the shortest route (or one of the shortest routes, if there are more than one). +In fact, since we are mostly interested in the _shortest_ route, we want to make sure we look at short routes before we look at longer ones. A good approach would be to "grow" routes from the starting point, exploring every reachable place that hasn't been visited yet until a route reaches the goal. That way, we'll explore only routes that are potentially interesting, and we know that the first route we find is the shortest route (or one of the shortest routes, if there are more than one). {{index "findRoute function"}} @@ -378,7 +378,7 @@ This robot usually finishes the task of delivering 5 parcels in about 16 turns. It's hard to objectively compare ((robot))s by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn't. -Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task. +Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let both of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task. For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot. From 2cf24e2ccf2ad95de46b6edaf46e2180ad27a16e Mon Sep 17 00:00:00 2001 From: hkiame <henrykiame@gmail.com> Date: Wed, 3 Jul 2024 22:54:50 +0300 Subject: [PATCH 361/392] Fix grammatical error in Chapter 20 under 'Modules' section --- 20_node.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20_node.md b/20_node.md index bd85fea4c..5fbf7aefd 100644 --- a/20_node.md +++ b/20_node.md @@ -113,7 +113,7 @@ When importing a module—whether with `require` or `import`—Node has to resol {{index "node_modules directory", directory}} -When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in file system module. Importing `"robot"` might try to load the library found in `node_modules/robot/`. It's common to install such libraries is by using ((NPM)), which we'll return to in a moment. +When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in file system module. Importing `"robot"` might try to load the library found in `node_modules/robot/`. It's common to install such libraries using ((NPM)), which we'll return to in a moment. {{index "import keyword", "Node.js", "garble example"}} From 6092bef908fa7596246cdf42401cc190db49afeb Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 4 Jul 2024 10:07:01 +0200 Subject: [PATCH 362/392] Copyediting chapter 8 --- 08_error.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/08_error.md b/08_error.md index b701fcece..39bbba633 100644 --- a/08_error.md +++ b/08_error.md @@ -54,7 +54,7 @@ canYouSpotTheProblem(); {{index ECMAScript, compatibility}} -Code inside classes and modules (which we will discuss in [Chapter ?](modules)) is automatically strict. The old non-strict behavior still exists only because some old code might depend on it, and the language designers work hard to avoid breaking any existing programs. +Code inside classes and modules (which we will discuss in [Chapter ?](modules)) is automatically strict. The old nonstrict behavior still exists only because some old code might depend on it, and the language designers work hard to avoid breaking any existing programs. {{index "let keyword", [binding, global]}} @@ -86,7 +86,7 @@ let ferdinand = Person("Ferdinand"); // forgot new We are immediately told that something is wrong. This is helpful. -Fortunately, constructors created with the `class` notation will always complain if they are called without `new`, making this less of a problem even in non-strict mode. +Fortunately, constructors created with the `class` notation will always complain if they are called without `new`, making this less of a problem even in nonstrict mode. {{index parameter, [binding, naming], "with statement"}} @@ -121,7 +121,7 @@ One thing about types is that they need to introduce their own complexity to be When the types of a program are known, it is possible for the computer to _check_ them for you, pointing out mistakes before the program is run. There are several JavaScript dialects that add types to the language and check them. The most popular one is called [TypeScript](https://www.typescriptlang.org/). If you are interested in adding more rigor to your programs, I recommend you give it a try. -In this book, we'll continue using raw, dangerous, untyped JavaScript code. +In this book, we will continue using raw, dangerous, untyped JavaScript code. ## Testing @@ -129,7 +129,7 @@ In this book, we'll continue using raw, dangerous, untyped JavaScript code. If the language is not going to do much to help us find mistakes, we'll have to find them the hard way: by running the program and seeing whether it does the right thing. -Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective since it takes too much time to exhaustively test everything every time you make a change. +Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective, since it takes too much time to exhaustively test everything every time you make a change. Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice rather than randomly running into it at some later time. From f4604201481a693469a9191bcfeed4f3bebb4054 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 4 Jul 2024 10:18:47 +0200 Subject: [PATCH 363/392] Copyediting chapter 9 --- 09_regexp.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/09_regexp.md b/09_regexp.md index 162df15f6..03337a6e6 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -153,14 +153,14 @@ By a strange historical accident, `\s` (whitespace) does not have this problem a {{index "character category", [Unicode, property]}} -It is possible to use `\p` in a regular expression to match all characters to which the Unicode standard assigns a given property. This allows us to match things like letters in a more cosmopolitan way. However, again due to compatibility with the original language standards, those are only recognized when you put a `u` character (for ((Unicode))) after the regular expression. +It is possible to use `\p` in a regular expression to match all characters to which the Unicode standard assigns a given property. This allows us to match things like letters in a more cosmopolitan way. However, again due to compatibility with the original language standards, those are recognized only when you put a `u` character (for ((Unicode))) after the regular expression. {{table {cols: [1, 5]}}} | `\p{L}` | Any letter | `\p{N}` | Any numeric character | `\p{P}` | Any punctuation character -| `\P{L}` | Any non-letter (uppercase P inverts) +| `\P{L}` | Any nonletter (uppercase P inverts) | `\p{Script=Hangul}` | Any character from the given script (see [Chapter ?](higher_order#scripts)) Using `\w` for text processing that may need to handle non-English text (or even English text with borrowed words like “cliché”) is a liability, since it won't treat characters like “é” as letters. Though they tend to be a bit more verbose, `\p` property groups are more robust. @@ -251,7 +251,7 @@ The first and second `+` characters apply only to the second `o` in `boo` and `h {{index "case sensitivity", capitalization, ["regular expression", flags]}} -The `i` at the end of the expression in the example makes this regular expression case-insensitive, allowing it to match the uppercase _B_ in the input string, even though the pattern is itself all lowercase. +The `i` at the end of the expression in the example makes this regular expression case insensitive, allowing it to match the uppercase _B_ in the input string, even though the pattern is itself all lowercase. ## Matches and groups @@ -359,7 +359,7 @@ If you give the `Date` constructor a single argument, that argument is treated a {{index "getFullYear method", "getMonth method", "getDate method", "getHours method", "getMinutes method", "getSeconds method", "getYear method"}} -Date objects provide methods such as `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes`, and `getSeconds` to extract their components. Besides `getFullYear` there's also `getYear`, which gives you the year minus 1900 (`98` or `119`) and is mostly useless. +Date objects provide methods such as `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes`, and `getSeconds` to extract their components. Besides `getFullYear` there's also `getYear`, which gives you the year minus 1900 (such as `98` or `125`) and is mostly useless. {{index "capture group", "getDate method", [parentheses, "in regular expressions"]}} @@ -391,7 +391,7 @@ If we want to enforce that the match must span the whole string, we can add the {{index "word boundary", "word character"}} -There is also a `\b` marker that matches _word boundaries_, positions that have a word character on one side, and a non-word character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w`, and are therefore not very reliable. +There is also a `\b` marker that matches _word boundaries_, positions that have a word character on one side, and a nonword character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w` and are therefore not very reliable. Note that these boundary markers don't match any actual characters. They just enforce that a given condition holds at the place where it appears in the pattern. @@ -406,7 +406,7 @@ console.log(/a(?! )/.exec("a b")); // → null ``` -The `e` in the first example is necessary to match, but is not part of the matched string. The `(?! )` notation expresses a _negative_ look-ahead. This only matches if the pattern in the parentheses _doesn't_ match, causing the second example to only match `a` characters that don't have a space after them. +The `e` in the first example is necessary to match, but is not part of the matched string. The `(?! )` notation expresses a _negative_ look-ahead. This matches only if the pattern in the parentheses _doesn't_ match, causing the second example to match only `a` characters that don't have a space after them. ## Choice patterns @@ -538,7 +538,7 @@ console.log(stock.replace(/(\d+) (\p{L}+)/gu, minusOne)); This code takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string that has one less of every such quantity. -The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works since it matched `\d+` earlier—and makes some adjustments in case there is only one or zero left. +The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works, since it matched `\d+` earlier—and makes some adjustments in case there is only one or zero left. ## Greed @@ -597,7 +597,7 @@ console.log(regexp.test("Harry is a dodgy character.")); {{index ["regular expression", flags], ["backslash character", "in regular expressions"]}} -When creating the `\s` part of the string, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the `RegExp` constructor contains the options for the regular expression—in this case, `"gi"` for global and case-insensitive. +When creating the `\s` part of the string, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the `RegExp` constructor contains the options for the regular expression—in this case, `"gi"` for global and case insensitive. But what if the name is `"dea+hl[]rd"` because our user is a ((nerd))y teenager? That would result in a nonsensical regular expression that won't actually match the user's name. @@ -656,7 +656,7 @@ console.log(pattern.lastIndex); {{index "side effect", "lastIndex property"}} -If the match was successful, the call to `exec` automatically updates the `lastIndex` property to point after the match. If no match was found, `lastIndex` is set back to zero, which is also the value it has in a newly constructed regular expression object. +If the match was successful, the call to `exec` automatically updates the `lastIndex` property to point after the match. If no match was found, `lastIndex` is set back to 0, which is also the value it has in a newly constructed regular expression object. The difference between the global and the sticky options is that when sticky is enabled, the match will succeed only if it starts directly at `lastIndex`, whereas with global, it will search ahead for a position where a match can start. @@ -794,11 +794,11 @@ The pattern `if (match = string.match(...))` makes use of the fact that the valu {{index [parentheses, "in regular expressions"]}} -If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression `/^\s*(;|$)/` to match lines that either contain only space, or space followed by a semicolon (making the rest of the line a comment). When a line doesn't match any of the expected forms, the function throws an exception. +If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression `/^\s*(;|$)/` to match lines that either contain only whitespace, or whitespace followed by a semicolon (making the rest of the line a comment). When a line doesn't match any of the expected forms, the function throws an exception. ## Code units and characters -Another design mistake that's been standardized in JavaScript regular expressions is that by default, operators like `.` or `?` work on code units, as discussed in [Chapter ?](higher_order#code_units), not actual characters. This means characters that are composed of two code units behave strangely. +Another design mistake that's been standardized in JavaScript regular expressions is that by default, operators like `.` or `?` work on code units (as discussed in [Chapter ?](higher_order#code_units)), not actual characters. This means characters that are composed of two code units behave strangely. ``` console.log(/🍎{3}/.test("🍎🍎🍎")); @@ -858,7 +858,7 @@ Regular expressions are a sharp ((tool)) with an awkward handle. They simplify s {{index debugging, bug}} -It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression's inexplicable ((behavior)). Sometimes it helps to enter your expression into an online tool like [_debuggex.com_](https://www.debuggex.com/) to see whether its visualization corresponds to what you intended and to ((experiment)) with the way it responds to various input strings. +It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression's inexplicable ((behavior)). Sometimes it helps to enter your expression into an online tool like [_debuggex.com_](https://www.debuggex.com) to see whether its visualization corresponds to what you intended and to ((experiment)) with the way it responds to various input strings. ### Regexp golf From 42c27f7a42ff228097b3cbb9d907588c7536ea57 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 4 Jul 2024 10:33:54 +0200 Subject: [PATCH 364/392] Copyediting chapter 10 --- 10_modules.md | 22 +++++++++++----------- 20_node.md | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/10_modules.md b/10_modules.md index ae9cd89e0..c8e299eff 100644 --- a/10_modules.md +++ b/10_modules.md @@ -18,13 +18,13 @@ Ideally, a program has a clear, straightforward structure. The way it works is e {{index "organic growth"}} -In practice, programs grow organically. Pieces of functionality are added as the programmer identifies new needs. Keeping such a program well-structured requires constant attention and work. This is work that will pay off only in the future, the _next_ time someone works on the program, so it's tempting to neglect it and allow the various parts of the program to become deeply entangled. +In practice, programs grow organically. Pieces of functionality are added as the programmer identifies new needs. Keeping such a program well structured requires constant attention and work. This is work that will pay off only in the future, the _next_ time someone works on the program, so it's tempting to neglect it and allow the various parts of the program to become deeply entangled. {{index readability, reuse, isolation}} This causes two practical issues. First, understanding an entangled system is hard. If everything can touch everything else, it is difficult to look at any given piece in isolation. You are forced to build up a holistic understanding of the entire thing. Second, if you want to use any of the functionality from such a program in another situation, rewriting it may be easier than trying to disentangle it from its context. -The phrase "((big ball of mud))" is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and you only succeed in making a mess. +The phrase "((big ball of mud))" is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and you succeed only in making a mess. ## Modular programs @@ -139,7 +139,7 @@ When a problem is found in a package or a new feature is added, the package is u {{index installation, upgrading, "package manager", download, reuse}} -Working in this way requires ((infrastructure)). We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by ((NPM)) ([_https://npmjs.org_](https://npmjs.org)). +Working in this way requires ((infrastructure)). We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by ((NPM)) ([_https://npmjs.com_](https://npmjs.com)). NPM is two things: an online service where you can download (and upload) packages, and a program (bundled with Node.js) that helps you install and manage them. @@ -250,7 +250,7 @@ console.log(formatDate(new Date(2017, 9, 13), // → Friday the 13th ``` -CommonJS is implemented with a module loader that, when loading a module, wraps its code in a function (giving it its own local scope), and passes the `require` and `exports` bindings to that function as arguments. +CommonJS is implemented with a module loader that, when loading a module, wraps its code in a function (giving it its own local scope) and passes the `require` and `exports` bindings to that function as arguments. {{id require}} @@ -281,13 +281,13 @@ require.cache = Object.create(null); Standard JavaScript provides no such function as `readFile`, but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that `readFile` exists. -To avoid loading the same module multiple times, `require` keeps a store (cache) of already loaded modules. When called, it first checks if the requested module has been loaded and, if not, loads it. This involves reading the module's code, wrapping it in a function, and calling it. +To avoid loading the same module multiple times, `require` keeps a store (cache) of already loaded modules. When called, it first checks whether the requested module has been loaded and, if not, loads it. This involves reading the module's code, wrapping it in a function, and calling it. {{index "ordinal package", "exports object", "module object", [interface, module]}} -By defining `require`, `exports` as ((parameter))s for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module's ((scope)). +By defining `require` and `exports` as ((parameter))s for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module's ((scope)). -An important difference between this system and ES modules is that ES module imports happen before a module's script starts running, whereas `require` is a normal function, invoked when the module is already running. Unlike `import` declarations, `require` calls _can_ appear inside functions, and the name of the dependency can be any expression that evaluates to a string, whereas `import` only allows plain quoted strings. +An important difference between this system and ES modules is that ES module imports happen before a module's script starts running, whereas `require` is a normal function, invoked when the module is already running. Unlike `import` declarations, `require` calls _can_ appear inside functions, and the name of the dependency can be any expression that evaluates to a string, whereas `import` allows only plain quoted strings. The transition of the JavaScript community from CommonJS style to ES modules has been a slow and somewhat rough one. Fortunately we are now at a point where most of the popular packages on NPM provide their code as ES modules, and Node.js allows ES modules to import from CommonJS modules. While CommonJS code is still something you will run across, there is no real reason to write new programs in this style anymore. @@ -307,7 +307,7 @@ And we can go further. Apart from the number of files, the _size_ of the files a {{index pipeline, tool}} -It is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through _multiple_ stages of transformation—converting from modern JavaScript to historic JavaScript, combining the modules into a single file, and minifying the code. We won't go into the details of these tools in this book since there are many of them, and which one is popular changes regularly. Just be aware that such things exist, and look them up when you need them. +It is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through _multiple_ stages of transformation—converting from modern JavaScript to historic JavaScript, combining the modules into a single file, and minifying the code. We won't go into the details of these tools in this book, since there are many of them, and which one is popular changes regularly. Just be aware that such things exist, and look them up when you need them. ## Module design @@ -327,7 +327,7 @@ That may mean following existing conventions. A good example is the `ini` packag {{index "side effect", "hard disk", composability}} -Even if there's no standard function or widely used package to imitate, you can keep your modules predictable by using simple ((data structure))s and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don't have direct file system access, and adds complexity that would have been better addressed by _composing_ the module with some file-reading function. +Even if there's no standard function or widely used package to imitate, you can keep your modules predictable by using simple ((data structure))s and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don't have direct filesystem access, and adds complexity that would have been better addressed by _composing_ the module with some file-reading function. {{index "pure function"}} @@ -347,7 +347,7 @@ There are several different pathfinding packages on ((NPM)), but none of them us For example, there's the `dijkstrajs` package. A well-known approach to pathfinding, quite similar to our `findRoute` function, is called _Dijkstra's algorithm_, after Edsger Dijkstra, who first wrote it down. The `js` suffix is often added to package names to indicate the fact that they are written in JavaScript. This `dijkstrajs` package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges. -If we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn). +If we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight, since our simplified model treats each road as having the same cost (one turn). ``` const {find_path} = require("dijkstrajs"); @@ -368,7 +368,7 @@ This can be a barrier to composition—when various packages are using different {{index design}} -Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much since keeping everything organized can be a big distraction. Once you have something that feels solid, that's a good time to take a step back and organize it. +Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much, since keeping everything organized can be a big distraction. Once you have something that feels solid, that's a good time to take a step back and organize it. ## Summary diff --git a/20_node.md b/20_node.md index 5fbf7aefd..1c959f990 100644 --- a/20_node.md +++ b/20_node.md @@ -220,7 +220,7 @@ A caret character (`^`) in front of the version number for a dependency in `pack The `npm` command is also used to publish new packages or new versions of packages. If you run `npm publish` in a ((directory)) that has a `package.json` file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn't in use yet, since it wouldn't be good if random people could update existing packages. -This book won't delve further into the details of ((NPM)) usage. Refer to [_https://npmjs.org_](https://npmjs.org) for further documentation and a way to search for packages. +This book won't delve further into the details of ((NPM)) usage. Refer to [_https://npmjs.com_](https://npmjs.com) for further documentation and a way to search for packages. ## The file system module From 0b3c8999d5d513462193bb0753dc9427c7dcee22 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 4 Jul 2024 10:43:26 +0200 Subject: [PATCH 365/392] Copyediting chapter 11 --- 11_async.md | 64 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/11_async.md b/11_async.md index 363272618..0055d3948 100644 --- a/11_async.md +++ b/11_async.md @@ -100,11 +100,11 @@ function compareFiles(fileA, fileB, callback) { This style of programming is workable, but the indentation level increases with each asynchronous action because you end up in another function. Doing more complicated things, such as wrapping asynchronous actions in a loop, can get awkward. -In a way, asynchronicity is _contagious_. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error-prone than simply returning a value, so needing to structure large parts of your program that way is not great. +In a way, asynchronicity is _contagious_. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error prone than simply returning a value, so needing to structure large parts of your program that way is not great. ## Promises -A slightly different way to build an asynchronous program is to have asynchronous functions return an object that represents its (future) result instead of passing around callback functions. This way such functions actually return something meaningful, and the shape of the program more closely resembles that of synchronous programs. +A slightly different way to build an asynchronous program is to have asynchronous functions return an object that represents its (future) result instead of passing around callback functions. This way, such functions actually return something meaningful, and the shape of the program more closely resembles that of synchronous programs. {{index "Promise class", "asynchronous programming", "resolving (a promise)", "then method", "callback function"}} @@ -122,7 +122,7 @@ fifteen.then(value => console.log(`Got ${value}`)); {{index "Promise class"}} -To create a promise that does not immediately resolve, you can use `Promise` as a constructor. It has a somewhat odd interface: the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. +To create a promise that does not immediately resolve, you can use `Promise` as a constructor. It has a somewhat odd interface: the constructor expects a function as its argument, which it immediately calls, passing it a function that it can use to resolve the promise. For example, this is how you could create a promise-based interface for the `readTextFile` function: @@ -144,7 +144,7 @@ Note how, in contrast to callback-style functions, this asynchronous function re A useful thing about the `then` method is that it itself returns another promise. This one resolves to the value returned by the callback function or, if that returned value is a promise, to the value that promise resolves to. Thus, you can “chain” multiple calls to `then` together to set up a sequence of asynchronous actions. -This function, which reads a file full of filenames, and returns the content of a random file in that list, shows this kind of asynchronous promise pipeline: +This function, which reads a file full of filenames and returns the content of a random file in that list, shows this kind of asynchronous promise pipeline: ``` function randomFile(listFile) { @@ -155,7 +155,7 @@ function randomFile(listFile) { } ``` -The function returns the result of this chain of `then` calls. The initial promise fetches the list of files as a string. The first `then` call transforms that string into an array of lines, producing a new promise. The second `then` call picks a random line from that, producing a third promise that yields a single filename. The final `then` call reads this file, so that the result of the function as a whole is a promise that returns the content of a random file. +The function returns the result of this chain of `then` calls. The initial promise fetches the list of files as a string. The first `then` call transforms that string into an array of lines, producing a new promise. The second `then` call picks a random line from that, producing a third promise that yields a single filename. The final `then` call reads this file, so the result of the function as a whole is a promise that returns the content of a random file. In this code, the functions used in the first two `then` calls return a regular value that will immediately be passed into the promise returned by `then` when the function returns. The last `then` call returns a promise (`textFile(filename)`), making it an actual asynchronous step. @@ -212,7 +212,7 @@ A function passed to the `Promise` constructor receives a second argument, along {{index "textFile function"}} -When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually check that argument, so that a failure causes the promise it returns to be rejected. +When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually check that argument so that a failure causes the promise it returns to be rejected. ```{includeCode: true} function textFile(filename) { @@ -239,7 +239,7 @@ new Promise((_, reject) => reject(new Error("Fail"))) // → Handler 2: nothing ``` -The first `then` handler function isn't called, because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second `then` handler function. +The first `then` handler function isn't called because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second `then` handler function. {{index "uncaught exception", "exception handling"}} @@ -253,7 +253,7 @@ It's a sunny day in Berlin. The runway of the old, decommissioned airport is tee One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. -Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight towards the roof of the hangar building, disappearing into an air vent. +Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight toward the roof of the hangar building, disappearing into an air vent. Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snacks, half a dozen smartphones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. @@ -282,9 +282,9 @@ function withTimeout(promise, time) { } ``` -This makes use of the fact that a promise can only be resolved or rejected once. If the promise given as argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored. +This makes use of the fact that a promise can be resolved or rejected only once. If the promise given as its argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored. -To find the whole passcode, the program needs to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong, and must try the next digit. If the request times out, we have found another correct digit, and must continue by adding another digit. +To find the whole passcode, the program needs to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong and must try the next digit. If the request times out, we have found another correct digit and must continue by adding another digit. Because you cannot wait for a promise inside a `for` loop, Carla uses a recursive function to drive this process. On each call, this function gets the code as we know it so far, as well as the next digit to try. Depending on what happens, it may return a finished code or call through to itself, to either start cracking the next position in the code or to try again with another digit. @@ -329,7 +329,7 @@ The thing the cracking function actually does is completely linear—it always w {{index "async function", "await keyword"}} -The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An `async` function implicitly returns a promise and can, in its body, `await` other promises in a way that _looks_ synchronous. +The good news is that JavaScript allows you to write pseudosynchronous code to describe asynchronous computation. An `async` function implicitly returns a promise and can, in its body, `await` other promises in a way that _looks_ synchronous. {{index "findInStorage function"}} @@ -356,7 +356,7 @@ async function crackPasscode(networkID) { } ``` -This version more clearly shows the double loop structure of the function (the inner loop tries digit 0 to 9, the outer loop adds digits to the passcode). +This version more clearly shows the double loop structure of the function (the inner loop tries digit 0 to 9 and the outer loop adds digits to the passcode). {{index "async function", "return keyword", "exception handling"}} @@ -437,13 +437,13 @@ One morning, Carla wakes up to unfamiliar noise from the tarmac outside of her h Being a curious crow, Carla takes a closer look at the wall. It appears to consist of a number of large glass-fronted devices wired up to cables. On the back, the devices say “LedTec SIG-5030”. -A quick internet search turns up a user's manual for these devices. They appear to be traffic signs, with a programmable matrix of amber LED lights. The intent is of the humans is probably to display some kind of information on them during their event. Interestingly, the screens can be programmed over a wireless network. Could it be they are connected to the building's local network? +A quick internet search turns up a user manual for these devices. They appear to be traffic signs, with a programmable matrix of amber LED lights. The intent of the humans is probably to display some kind of information on them during their event. Interestingly, the screens can be programmed over a wireless network. Could it be they are connected to the building's local network? Each device on a network gets an _IP address_, which other devices can use to send it messages. We talk more about that in [Chapter ?](browser). Carla notices that her own phones all get addresses like `10.0.0.20` or `10.0.0.33`. It might be worth trying to send messages to all such addresses and see if any one of them responds to the interface described in the manual for the signs. -[Chapter ?](http) shows how to make real requests on real networks. In this chapter, we'll use a simplified dummy function called `request` for network communication. This function takes two arguments—a network address and a message, which may be anything that can be sent as JSON—and returns a promise that either resolves to a response from the machine at the given address, or a rejects if there was a problem. +[Chapter ?](http) shows how to make real requests on real networks. In this chapter, we'll use a simplified dummy function called `request` for network communication. This function takes two arguments—a network address and a message, which may be anything that can be sent as JSON—and returns a promise that either resolves to a response from the machine at the given address, or rejects if there was a problem. -According to the manual, you can change what is displayed on a SIG-5030 sign by sending it a message with content like `{"command": "display", "data": [0, 0, 3, …]}`, where `data` holds one number per LED dot, providing its brightness—0 means off, 3 means maximum brightness. Each sign is 50 lights wide and 30 lights high, so an update command should send 1500 numbers. +According to the manual, you can change what is displayed on a SIG-5030 sign by sending it a message with content like `{"command": "display", "data": [0, 0, 3, …]}`, where `data` holds one number per LED dot, providing its brightness—0 means off, 3 means maximum brightness. Each sign is 50 lights wide and 30 lights high, so an update command should send 1,500 numbers. This code sends a display update message to all addresses on the local network, to see what sticks. Each of the numbers in an IP address can go from 0 to 255. In the data it sends, it activates a number of lights corresponding to the network address's last number. @@ -462,7 +462,7 @@ for (let addr = 1; addr < 256; addr++) { Since most of these addresses won't exist or will not accept such messages, the `catch` call makes sure network errors don't crash the program. The requests are all sent out immediately, without waiting for other requests to finish, in order to not waste time when some of the machines don't answer. -Having fired off her network scan, Carla heads back outside to see the result. To her delight, all of the screens are now showing a stripe of light in their top left corners. They _are_ on the local network, and they _do_ accept commands. She quickly notes the numbers shown on each screen. There are 9 screens, arranged three high and three wide. They have the following network addresses: +Having fired off her network scan, Carla heads back outside to see the result. To her delight, all of the screens are now showing a stripe of light in their upper-left corners. They _are_ on the local network, and they _do_ accept commands. She quickly notes the numbers shown on each screen. There are nine screens, arranged three high and three wide. They have the following network addresses: ```{includeCode: true} const screenAddresses = [ @@ -474,11 +474,11 @@ const screenAddresses = [ Now this opens up possibilities for all kinds of shenanigans. She could show “crows rule, humans drool” on the wall in giant letters. But that feels a bit crude. Instead, she plans to show a video of a flying crow covering all of the screens at night. -Carla finds a fitting video clip, in which a second and a half of footage can be repeated to create a looping video showing a crow's wingbeat. To fit the nine screens (each of which can show 50×30 pixels), Carla cuts and resizes the videos to get a series of 150×90 images, ten per second. Those are then each cut into nine rectangles, and processed so that the dark spots on the video (where the crow is) show a bright light, and the light spots (no crow) are left dark, which should create the effect of an amber crow flying against a black background. +Carla finds a fitting video clip, in which a second and a half of footage can be repeated to create a looping video showing a crow's wingbeat. To fit the nine screens (each of which can show 50×30 pixels), Carla cuts and resizes the videos to get a series of 150×90 images, 10 per second. Those are then each cut into nine rectangles, and processed so that the dark spots on the video (where the crow is) show a bright light, and the light spots (no crow) are left dark, which should create the effect of an amber crow flying against a black background. She has set up the `clipImages` variable to hold an array of frames, where each frame is represented with an array of nine sets of pixels—one for each screen—in the format that the signs expect. -To display a single frame of the video, Carla needs to send a request to all the screens at once. But she also needs to wait for the result of these requests, both in order to not start sending the next frame before the current one has been properly sent, and in order to notice when requests are failing. +To display a single frame of the video, Carla needs to send a request to all the screens at once. But she also needs to wait for the result of these requests, both in order to not start sending the next frame before the current one has been properly sent and in order to notice when requests are failing. {{index "Promise.all function"}} @@ -498,7 +498,7 @@ function displayFrame(frame) { This maps over the images in `frame` (which is an array of display data arrays) to create an array of request promises. It then returns a promise that combines all of those. -In order to be able to stop a playing video, the process is wrapped in a class. This class has an asynchronous `play` method that returns a promise that only resolves when the playback is stopped again via the `stop` method. +In order to be able to stop a playing video, the process is wrapped in a class. This class has an asynchronous `play` method that returns a promise that resolves only when the playback is stopped again via the `stop` method. ```{includeCode: true} function wait(time) { @@ -527,7 +527,7 @@ class VideoPlayer { } ``` -The `wait` function wraps `setTimeout` in a promise that resolves after the given amount of milliseconds. This is useful for controlling the speed of the playback. +The `wait` function wraps `setTimeout` in a promise that resolves after the given number of milliseconds. This is useful for controlling the speed of the playback. ```{startCode: true} let video = new VideoPlayer(clipImages, 100); @@ -638,15 +638,15 @@ Can you work out why? {{index "+= operator"}} -The problem lies in the `+=` operator, which takes the _current_ value of `list` at the time where the statement starts executing and then, when the `await` finishes, sets the `list` binding to be that value plus the added string. +The problem lies in the `+=` operator, which takes the _current_ value of `list` at the time the statement starts executing and then, when the `await` finishes, sets the `list` binding to be that value plus the added string. {{index "await keyword"}} -But between the time where the statement starts executing and the time where it finishes there's an asynchronous gap. The `map` expression runs before anything has been added to the list, so each of the `+=` operators starts from an empty string and ends up, when its storage retrieval finishes, setting `list` to the result of adding its line to the empty string. +But between the time the statement starts executing and the time it finishes, there's an asynchronous gap. The `map` expression runs before anything has been added to the list, so each of the `+=` operators starts from an empty string and ends up, when its storage retrieval finishes, setting `list` to the result of adding its line to the empty string. {{index "side effect"}} -This could have easily been avoided by returning the lines from the mapped promises and calling `join` on the result of `Promise.all`, instead of building up the list by changing a binding. As usual, computing new values is less error-prone than changing existing values. +This could have easily been avoided by returning the lines from the mapped promises and calling `join` on the result of `Promise.all`, instead of building up the list by changing a binding. As usual, computing new values is less error prone than changing existing values. {{index "fileSizes function"}} @@ -678,7 +678,7 @@ There's a security camera near Carla's lab that's activated by a motion sensor. {{index "Date class", "Date.now function", timestamp}} -She's also been logging the times at which the camera is tripped for a while and wants to use this information to visualize which times, in an average week, tend to be quiet, and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line. +She's also been logging the times at which the camera is tripped for a while and wants to use this information to visualize which times, in an average week, tend to be quiet and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line. ```{lang: null} 1695709940692 @@ -686,7 +686,7 @@ She's also been logging the times at which the camera is tripped for a while and 1695701189163 ``` -The `"camera_logs.txt"` file holds a list of log files. Write an asynchronous function `activityTable(day)` that for a given day of the week returns an array of 24 numbers, one for each hour of the day, that hold the number of camera network traffic observations seen in that hour of the day. Days are identified by number using the system used by `Date.getDay`, where Sunday is 0 and Saturday is 6. +The `"camera_logs.txt"` file holds a list of logfiles. Write an asynchronous function `activityTable(day)` that for a given day of the week returns an array of 24 numbers, one for each hour of the day, that hold the number of camera network traffic observations seen in that hour of the day. Days are identified by number using the system used by `Date.getDay`, where Sunday is 0 and Saturday is 6. The `activityGraph` function, provided by the sandbox, summarizes such a table into a string. @@ -694,7 +694,7 @@ The `activityGraph` function, provided by the sandbox, summarizes such a table i To read the files, use the `textFile` function defined earlier—given a filename, it returns a promise that resolves to the file's content. Remember that `new Date(timestamp)` creates a `Date` object for that time, which has `getDay` and `getHours` methods returning the day of the week and the hour of the day. -Both types of files—the list of log files and the log files themselves—have each piece of data on its own line, separated by newline (`"\n"`) characters. +Both types of files—the list of logfiles and the logfiles themselves—have each piece of data on its own line, separated by newline (`"\n"`) characters. {{if interactive @@ -714,9 +714,9 @@ if}} {{index "quiet times (exercise)", "split method", "textFile function", "Date class"}} -You will need to convert the content of these files to an array. The easiest way to do that is to use the `split` method on the string produced by `textFile`. Note that for the log files, that will still give you an array of strings, which you have to convert to numbers before passing them to `new Date`. +You will need to convert the content of these files to an array. The easiest way to do that is to use the `split` method on the string produced by `textFile`. Note that for the logfiles, that will still give you an array of strings, which you have to convert to numbers before passing them to `new Date`. -Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the log files and the numbers in every log file) and for each one, if it happened on the correct day, take the hour it occurred in, and add one to the corresponding number in the table. +Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the logfiles and the numbers in every log file) and for each one, if it happened on the correct day, take the hour it occurred in, and add one to the corresponding number in the table. {{index "async function", "await keyword", "Promise class"}} @@ -746,7 +746,7 @@ if}} {{index "async function", "await keyword", performance}} -In this style, using `Promise.all` will be more convenient than trying to model a loop over the log files. In the `async` function, just using `await` in a loop is simpler. If reading a file takes some time, which of these two approaches will take the least time to run? +In this style, using `Promise.all` will be more convenient than trying to model a loop over the logfiles. In the `async` function, just using `await` in a loop is simpler. If reading a file takes some time, which of these two approaches will take the least time to run? {{index "rejecting (a promise)"}} @@ -756,11 +756,11 @@ If one of the files listed in the file list has a typo, and reading it fails, ho {{index "real promises (exercise)", "then method", "textFile function", "Promise.all function"}} -The most straightforward approach to writing this function is to use a chain of `then` calls. The first promise is produced by reading the list of log files. The first callback can split this list and map `textFile` over it to get an array of promises to pass to `Promise.all`. It can return the object returned by `Promise.all`, so that whatever that returns becomes the result of the return value of this first `then`. +The most straightforward approach to writing this function is to use a chain of `then` calls. The first promise is produced by reading the list of logfiles. The first callback can split this list and map `textFile` over it to get an array of promises to pass to `Promise.all`. It can return the object returned by `Promise.all`, so that whatever that returns becomes the result of the return value of this first `then`. {{index "asynchronous programming"}} -We now have a promise that returns an array of log files. We can call `then` again on that, and put the timestamp-counting logic in there. Something like this: +We now have a promise that returns an array of logfiles. We can call `then` again on that, and put the timestamp-counting logic in there. Something like this: ```{test: no} function activityTable(day) { @@ -805,7 +805,7 @@ As we saw, given an array of ((promise))s, `Promise.all` returns a promise that Implement something like this yourself as a regular function called `Promise_all`. -Remember that after a promise has succeeded or failed, it can't succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle failure of your promise. +Remember that after a promise has succeeded or failed, it can't succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle a failure of your promise. {{if interactive From e1692f5b1ae32bf832c4fd9fde6d2be18cc93b41 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Mon, 8 Jul 2024 11:02:45 +0200 Subject: [PATCH 366/392] Copyediting chapter 12 --- 12_language.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/12_language.md b/12_language.md index a49935394..82df58ad1 100644 --- a/12_language.md +++ b/12_language.md @@ -259,7 +259,7 @@ specialForms.if = (args, scope) => { {{index "conditional execution", "ternary operator", "?: operator", "conditional operator"}} -Egg's `if` construct expects exactly three arguments. It will evaluate the first, and if the result isn't the value `false`, it will evaluate the second. Otherwise, the third gets evaluated. This `if` form is more similar to JavaScript's ternary `?:` operator than to JavaScript's `if`. It is an expression, not a statement, and it produces a value, namely, the result of the second or third argument. +Egg's `if` construct expects exactly three arguments. It will evaluate the first, and if the result isn't the value `false`, it will evaluate the second. Otherwise, the third gets evaluated. This `if` form is more similar to JavaScript's ternary `?:` operator than to JavaScript's `if`. It is an expression, not a statement, and it produces a value—namely, the result of the second or third argument. {{index Boolean}} @@ -281,7 +281,7 @@ specialForms.while = (args, scope) => { } // Since undefined does not exist in Egg, we return false, - // for lack of a meaningful result. + // for lack of a meaningful result return false; }; ``` @@ -383,7 +383,7 @@ do(define(total, 0), {{index "summing example", "Egg language"}} -This is the program we've seen several times before that computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 ((lines of code)). +This is the program we've seen several times before that computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in fewer than 150 ((lines of code)). {{id egg_fun}} From 89c4c733f95ac383ca2b49c4fb06271570152f66 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Mon, 8 Jul 2024 11:05:04 +0200 Subject: [PATCH 367/392] Copyediting chapter 13 --- 13_browser.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/13_browser.md b/13_browser.md index 1927c775c..6bd717d64 100644 --- a/13_browser.md +++ b/13_browser.md @@ -1,6 +1,6 @@ # JavaScript and the Browser -{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A very short personal history", chapter: true} +{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A Very Short Personal Pistory", chapter: true} The dream behind the web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished. @@ -14,7 +14,7 @@ The next chapters of this book will discuss web browsers. Without ((browser))s, {{index decentralization, compatibility}} -Web technology has been decentralized from the start, not just technically but also in terms of the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which were then—sometimes—adopted by others—and finally set down as in ((standards)). +Web technology has been decentralized from the start, not just technically but also in terms of the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which were then—sometimes—adopted by others, and finally set down in ((standards)). This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally, open hostility). On the other hand, the haphazard way in which the web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. @@ -172,7 +172,7 @@ The following document will be treated just like the one shown previously: {{index "title (HTML tag)", "head (HTML tag)", "body (HTML tag)", "html (HTML tag)"}} -The `<html>`, `<head>`, and `<body>` tags are completely gone. The browser knows that `<meta>` and `<title>` belong in the head and that `<h1>` means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone. +The `<html>`, `<head>`, and `<body>` tags are completely gone. The browser knows that `<meta>` and `<title>` belong in the head and that `<h1>` means the body has started. Furthermore, I am no longer explicitly closing the paragraphs, since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone. This book will usually omit the `<html>`, `<head>`, and `<body>` tags from examples to keep them short and free of clutter. I _will_ close tags and include quotes around attributes, though. @@ -244,7 +244,7 @@ The hard part of sandboxing is allowing programs enough room to be useful while {{index leak, exploit, security}} -Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine on which the browser is running. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or criminal organization. +Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine on which the browser is running. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized rather than secretly exploited by some government agency or criminal organization. ## Compatibility and the browser wars From 8f43d32ffda785a4a8e25cfbde791f98bfb9afd4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Mon, 8 Jul 2024 11:08:24 +0200 Subject: [PATCH 368/392] Copyediting chapter 14 --- 14_dom.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/14_dom.md b/14_dom.md index 62e10be84..63492ba52 100644 --- a/14_dom.md +++ b/14_dom.md @@ -53,7 +53,7 @@ The global binding `document` gives us access to these objects. Its `documentEle {{index [nesting, "of objects"]}} -Think back to the ((syntax tree))s from [Chapter ?](language#parsing) for a moment. Their structures are strikingly similar to the structure of a browser's document. Each _((node))_ may refer to other nodes, _children_, which in turn may have their own children. This shape is typical of nested structures where elements can contain subelements that are similar to themselves. +Think back to the ((syntax tree))s from [Chapter ?](language#parsing) for a moment. Their structures are strikingly similar to the structure of a browser's document. Each _((node))_ may refer to other nodes, _children_, which in turn may have their own children. This shape is typical of nested structures, where elements can contain subelements that are similar to themselves. {{index "documentElement property", [DOM, tree]}} @@ -330,7 +330,7 @@ It is recommended to prefix the names of such made-up attributes with `data-` to {{index "getAttribute method", "setAttribute method", "className property", "class attribute"}} -There is a commonly used attribute, `class`, which is a ((keyword)) in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called `className`. You can also access it under its real name, `"class"` with the `getAttribute` and `setAttribute` methods. +There is a commonly used attribute, `class`, which is a ((keyword)) in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called `className`. You can also access it under its real name, `"class"`, with the `getAttribute` and `setAttribute` methods. ## Layout @@ -374,7 +374,7 @@ if}} {{id boundingRect}} -The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want pixel positions relative to the whole document, you must add the current scroll position, which you can find in the `pageXOffset` and `pageYOffset` bindings. +The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the upper left of the screen. If you want pixel positions relative to the whole document, you must add the current scroll position, which you can find in the `pageXOffset` and `pageYOffset` bindings. {{index "offsetHeight property", "getBoundingClientRect method", drawing, laziness, performance, efficiency}} @@ -452,7 +452,7 @@ This text is displayed <strong>inline</strong>, {{index "hidden element"}} -The `block` tag will end up on its own line since ((block element))s are not displayed inline with the text around them. The last tag is not displayed at all—`display: none` prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again later. +The `block` tag will end up on its own line, since ((block element))s are not displayed inline with the text around them. The last tag is not displayed at all—`display: none` prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again later. {{if book @@ -573,7 +573,7 @@ Unlike methods such as `getElementsByTagName`, the object returned by `querySele {{index "querySelector method"}} -The `querySelector` method (without the `All` part) works in a similar way. This one is useful if you want a specific single element. It will return only the first matching element or null when no element matches. +The `querySelector` method (without the `All` part) works in a similar way. This one is useful if you want a specific single element. It will return only the first matching element, or `null` when no element matches. {{id animation}} @@ -581,7 +581,7 @@ The `querySelector` method (without the `All` part) works in a similar way. This {{index "position (CSS)", "relative positioning", "top (CSS)", "left (CSS)", "absolute positioning"}} -The `position` style property influences layout in a powerful way. It has a default value of `static`, meaning the element sits in its normal place in the document. When it is set to `relative`, the element still takes up space in the document, but now the `top` and `left` style properties can be used to move it relative to that normal place. When `position` is set to `absolute`, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Its `top` and `left` properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose `position` property isn't `static`, or relative to the document if no such enclosing element exists. +The `position` style property influences layout in a powerful way. It has a default value of `static`, meaning the element sits in its normal place in the document. When it is set to `relative`, the element still takes up space in the document, but now the `top` and `left` style properties can be used to move it relative to that normal place. When `position` is set to `absolute`, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Its `top` and `left` properties can be used to absolutely position it relative to the upper-left corner of the nearest enclosing element whose `position` property isn't `static`, or relative to the document if no such enclosing element exists. {{index [animation, "spinning cat"]}} @@ -636,7 +636,7 @@ The animation function is passed the current ((time)) as an argument. To ensure {{id sin_cos}} -Moving in ((circle))s is done using the trigonometry functions `Math.cos` and `Math.sin`. For those who aren't familiar with these, I'll briefly introduce them since we will occasionally use them in this book. +Moving in ((circle))s is done using the trigonometry functions `Math.cos` and `Math.sin`. For those who aren't familiar with these, I'll briefly introduce them, since we will occasionally use them in this book. {{index coordinates, pi}} @@ -802,7 +802,7 @@ Or make the hat circle around the cat. Or alter the animation in some other inte {{index "absolute positioning", "top (CSS)", "left (CSS)", "position (CSS)"}} -To make positioning multiple objects easier, you'll probably want to switch to absolute positioning. This means that `top` and `left` are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values. +To make positioning multiple objects easier, you'll probably want to switch to absolute positioning. This means that `top` and `left` are counted relative to the upper left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values. {{if interactive From b1311047c3bad8459262a40bb527128eb5bd1e11 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Mon, 8 Jul 2024 11:15:51 +0200 Subject: [PATCH 369/392] Copyediting chapter 15 --- 15_event.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/15_event.md b/15_event.md index 20cae3b1b..ffb1c832c 100644 --- a/15_event.md +++ b/15_event.md @@ -16,13 +16,13 @@ Some programs work with direct user input, such as mouse and keyboard actions. T {{index polling, button, "real-time"}} -Imagine an interface where the only way to find out whether a key on the ((keyboard)) is being pressed was to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key's state to catch it before it was released again. It would be dangerous to perform other time-intensive computations, since you might miss a keypress. +Imagine an interface where the only way to find out whether a key on the ((keyboard)) is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key's state to catch it before it is released again. It would be dangerous to perform other time-intensive computations, since you might miss a keypress. Some primitive machines handle input like this. A step up from this is for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there. {{index responsiveness, "user experience"}} -Of course, the program has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _((polling))_. Most programmers prefer to avoid it. +Of course, the program has to remember to look at the queue, and to do it often because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _((polling))_. Most programmers prefer to avoid it. {{index "callback function", "event handling"}} @@ -250,7 +250,7 @@ Modifier keys such as [shift]{keyname}, [ctrl]{keyname}, [alt]{keyname}, and [me The DOM node where a key event originates depends on the element that has ((focus)) when the key is pressed. Most nodes cannot have focus unless you give them a `tabindex` attribute, but things like ((link))s, buttons, and form fields can. We'll come back to form ((field))s in [Chapter ?](http#forms). When nothing in particular has focus, `document.body` acts as the target node of key events. -When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the ((virtual keyboard)) on ((Android)) ((phone))s, don't fire key events. But even when you have an old-fashioned keyboard, some types of text input don't match key presses in a straightforward way, such as _input method editor_ (_((IME))_) software used by people whose scripts don't fit on a keyboard, where multiple key strokes are combined to create characters. +When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the ((virtual keyboard)) on ((Android)) ((phone))s, don't fire key events. But even when you have an old-fashioned keyboard, some types of text input don't match keypresses in a straightforward way, such as _input method editor_ (_((IME))_) software used by people whose scripts don't fit on a keyboard, where multiple keystrokes are combined to create characters. To notice when something was typed, elements that you can type into, such as the `<input>` and `<textarea>` tags, fire `"input"` events whenever the user changes their content. To get the actual content that was typed, it is best to directly read it from the focused field, which we discuss in [Chapter ?](http#forms). @@ -274,7 +274,7 @@ If two clicks happen close together, a `"dblclick"` (double-click) event also fi {{index pixel, "clientX property", "clientY property", "pageX property", "pageY property", "event object"}} -To get precise information about the place where a mouse event happened, you can look at its `clientX` and `clientY` properties, which contain the event's ((coordinates)) (in pixels) relative to the top-left corner of the window, or `pageX` and `pageY`, which are relative to the top-left corner of the whole document (which may be different when the window has been scrolled). +To get precise information about the place where a mouse event happened, you can look at its `clientX` and `clientY` properties, which contain the event's ((coordinates)) (in pixels) relative to the upper-left corner of the window, or `pageX` and `pageY`, which are relative to the upper-left corner of the whole document (which may be different when the window has been scrolled). {{index "border-radius (CSS)", "absolute positioning", "drawing program example"}} @@ -360,7 +360,7 @@ Note that the `"mousemove"` handler is registered on the whole ((window)). Even {{index "buttons property", "button property", "bitfield"}} -We must stop resizing the bar when the mouse button is released. For that, we can use the `buttons` property (note the plural), which tells us about the buttons that are currently held down. When it is zero, no buttons are down. When buttons are held, the value of the `buttons` property is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. With the left and right buttons held, for example, the value of `buttons` will be 3. +We must stop resizing the bar when the mouse button is released. For that, we can use the `buttons` property (note the plural), which tells us about the buttons that are currently held down. When it is 0, no buttons are down. When buttons are held, the value of the `buttons` property is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. With the left and right buttons held, for example, the value of `buttons` will be 3. Note that the order of these codes is different from the one used by `button`, where the middle button came before the right one. As mentioned, consistency isn't a strong point of the browser's programming interface. @@ -493,7 +493,7 @@ The following example displays help text for the ((text field)) that currently h {{if book -This screenshot shows the help text for the age field. +This screenshot shows the help text for the age field: {{figure {url: "img/help-field.png", alt: "Screenshot of the help text below the age field", width: "4.4cm"}}} @@ -515,7 +515,7 @@ Elements such as ((image))s and script tags that load an external file also have {{index "beforeunload event", "page reload", "preventDefault method"}} -When you close page or navigate away from it (for example, by following a link), a `"beforeunload"` event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event _and_ set the `returnValue` property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight loss ads, most browsers no longer display them. +When you close page or navigate away from it (for example, by following a link), a `"beforeunload"` event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event _and_ set the `returnValue` property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight-loss ads, most browsers no longer display them. {{id timeline}} From 962602319a3156ef962d4cfd68f689616eedefe1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Mon, 8 Jul 2024 11:19:33 +0200 Subject: [PATCH 370/392] Copyediting chapter 16 --- 16_game.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/16_game.md b/16_game.md index 0cd9d6915..8151890bd 100644 --- a/16_game.md +++ b/16_game.md @@ -14,7 +14,7 @@ quote}} Much of my initial fascination with computers, like that of many nerdy kids, had to do with computer ((game))s. I was drawn into the tiny simulated ((world))s that I could manipulate and in which stories (sort of) unfolded—more, I suppose, because of the way I projected my ((imagination)) into them than because of the possibilities they actually offered. -I don't wish a ((career)) in game programming on anyone. Much like the ((music)) industry, the discrepancy between the number of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing. +I don't wish a ((career)) in game programming on anyone. As with the ((music)) industry, the discrepancy between the number of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing. {{index "jump-and-run game", dimensions}} @@ -134,7 +134,7 @@ So `rows` holds an array of arrays of characters, the rows of the plan. We can d {{index "map method"}} -To create these arrays, we map over the rows and then over their content. Remember that `map` passes the array index as a second argument to the mapping function, which tells us the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the top left being 0,0 and each background square being 1 unit high and wide. +To create these arrays, we map over the rows and then over their content. Remember that `map` passes the array index as a second argument to the mapping function, which tells us the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the upper left being 0,0 and each background square being 1 unit high and wide. {{index "static method"}} @@ -174,7 +174,7 @@ This is again a persistent data structure—updating the game state creates a ne {{index actor, "Vec class", [interface, object]}} -Actor objects represent the current position and state of a given moving element (player, coin, or mobile lava) in our game. All actor objects conform to the same interface. They have `size` and `pos` properties holding the size and the coordinates of the top-left corner of the rectangle representing this actor, and an `update` method. +Actor objects represent the current position and state of a given moving element (player, coin, or mobile lava) in our game. All actor objects conform to the same interface. They have `size` and `pos` properties holding the size and the coordinates of the upper-left corner of the rectangle representing this actor, and an `update` method. This `update` method is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object. @@ -204,7 +204,7 @@ class Vec { The `times` method scales a vector by a given number. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time. -The different types of actors get their own classes since their behavior is very different. Let's define these classes. We'll get to their `update` methods later. +The different types of actors get their own classes, since their behavior is very different. Let's define these classes. We'll get to their `update` methods later. {{index simulation, "Player class"}} @@ -464,13 +464,13 @@ By adding the level's current status as a class name to the wrapper, we can styl {{index player, "box shadow (CSS)"}} -After touching ((lava)), the player's color turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the top left and one to the top right—to create a white halo effect. +After touching ((lava)), the player turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the upper left and one to the upper right—to create a white halo effect. {{id viewport}} {{index "position (CSS)", "max-width (CSS)", "overflow (CSS)", "max-height (CSS)", viewport, scrolling, [DOM, graphics]}} -We can't assume that the level always fits in the _viewport_, the element into which we draw the game. That is why we need the `scrollPlayerIntoView` call: it ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following ((CSS)) gives the game's wrapping DOM element a maximum size and ensures that anything that sticks out of the element's box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level's top-left corner. +We can't assume that the level always fits in the _viewport_, the element into which we draw the game. That is why we need the `scrollPlayerIntoView` call: it ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following ((CSS)) gives the game's wrapping DOM element a maximum size and ensures that anything that sticks out of the element's box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level's upper-left corner. ```{lang: css} .game { @@ -514,7 +514,7 @@ DOMDisplay.prototype.scrollPlayerIntoView = function(state) { {{index center, coordinates, readability}} -The way the player's center is found shows how the methods on our `Vec` type allow computations with objects to be written in a relatively readable way. To find the actor's center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale. +The way the player's center is found shows how the methods on our `Vec` type allow computations with objects to be written in a relatively readable way. To find the actor's center, we add its position (its upper-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale. {{index validation}} @@ -623,11 +623,11 @@ State.prototype.update = function(time, keys) { }; ``` -The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the `update` method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that's the only actor that's controlled by the keyboard. +The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the `update` method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state so that they can base their update on those. Only the player will actually read keys, since that's the only actor that's controlled by the keyboard. If the game is already over, no further processing has to be done (the game can't be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost and we're done. Finally, if the game really is still going on, it sees whether any other actors overlap the player. -Overlap between actors is detected with the `overlap` function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x-axis and along the y-axis. +Overlap between actors is detected with the `overlap` function. It takes two actor objects and returns `true` when they touch—which is the case when they overlap both along the x-axis and along the y-axis. ```{includeCode: true} function overlap(actor1, actor2) { @@ -680,7 +680,7 @@ This `update` method computes a new position by adding the product of the ((time {{index "Coin class", coin, wave}} -Coins use their `update` method to wobble. They ignore collisions with the grid since they are simply wobbling around inside of their own square. +Coins use their `update` method to wobble. They ignore collisions with the grid, since they are simply wobbling around inside of their own square. ```{includeCode: true} const wobbleSpeed = 8, wobbleDist = 0.07; @@ -918,7 +918,7 @@ if}} {{index "pausing (exercise)", "escape key", keyboard, "runLevel function", "event handling"}} -Make it possible to pause (suspend) and unpause the game by pressing the [esc]{keyname} key. You can do this by changing the `runLevel` function to set up a keyboard event handler that interrupts or resumes the animation whenever the [esc]{keyname} key is hit. +Make it possible to pause (suspend) and unpause the game by pressing [esc]{keyname}. You can do this by changing the `runLevel` function to set up a keyboard event handler that interrupts or resumes the animation whenever [esc]{keyname} is hit. {{index "runAnimation function"}} From 3b0fca6271bc30056b46dfb46f3cde35e84064b9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 9 Jul 2024 09:31:12 +0200 Subject: [PATCH 371/392] Copyediting chapter 17 --- 17_canvas.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 1e44a7564..5499741be 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -20,7 +20,7 @@ But we'd be using the DOM for something that it wasn't originally designed for. {{index SVG, "img (HTML tag)"}} -There are two alternatives. The first is DOM-based but utilizes _Scalable Vector Graphics_ (SVG), rather than HTML. Think of SVG as a ((document))-markup dialect that focuses on ((shape))s rather than text. You can embed an SVG document directly in an HTML document or include it with an `<img>` tag. +There are two alternatives. The first is DOM based but utilizes _Scalable Vector Graphics_ (SVG) rather than HTML. Think of SVG as a ((document))-markup dialect that focuses on ((shape))s rather than text. You can embed an SVG document directly in an HTML document or include it with an `<img>` tag. {{index clearing, [DOM graphics], [interface, canvas]}} @@ -94,7 +94,7 @@ You create a ((context)) with the `getContext` method on the `<canvas>` DOM elem </script> ``` -After creating the context object, the example draws a red ((rectangle)) 100 ((pixel))s wide and 50 pixels high, with its top-left corner at coordinates (10,10). +After creating the context object, the example draws a red ((rectangle)) that is 100 ((pixel))s wide and 50 pixels high, with its upper-left corner at coordinates (10,10). {{if book @@ -104,7 +104,7 @@ if}} {{index SVG, coordinates}} -Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-((axis)) goes down from there. This means (10,10) is 10 pixels below and to the right of the top-left corner. +Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the upper-left corner, and the positive y-((axis)) goes down from there. This means (10,10) is 10 pixels below and to the right of the upper-left corner. {{id fill_stroke}} @@ -116,7 +116,7 @@ In the ((canvas)) interface, a shape can be _filled_, meaning its area is given {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. +The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's upper-left corner, then its width, and then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle. {{index [state, "of canvas"]}} @@ -200,7 +200,7 @@ When filling a path (using the `fill` method), each ((shape)) is filled separate </script> ``` -This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be there if you stroked the path. +This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the lower-right corner back to the top, is implied and wouldn't be there if you stroked the path. {{if book @@ -246,11 +246,11 @@ if}} {{index "stroke method"}} -We draw a ((quadratic curve)) from the left to the right, with (60,10) as control point, and then draw two ((line)) segments going through that control point and back to the start of the line. The result somewhat resembles a _((Star Trek))_ insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then ((curve)) toward their target. +We draw a ((quadratic curve)) from the left to the right, with (60,10) as the control point, and then draw two ((line)) segments going through that control point and back to the start of the line. The result somewhat resembles a _((Star Trek))_ insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then ((curve)) toward their target. {{index canvas, "bezierCurveTo method"}} -The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this method has two—one for each of the ((line))'s endpoints. Here is a similar sketch to illustrate the behavior of such a curve: +The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this method has two—one for each of the ((line))'s end points. Here is a similar sketch to illustrate the behavior of such a curve: ```{lang: html} <canvas></canvas> @@ -318,7 +318,7 @@ Like other path-drawing methods, a line drawn with `arc` is connected to the pre {{index "pie chart example"}} -Imagine we've just taken a ((job)) at EconomiCorp, Inc. Your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results. +Imagine you've just taken a ((job)) at EconomiCorp, Inc. Your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results. The `results` binding contains an array of objects that represent the survey responses. @@ -420,7 +420,7 @@ The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This {{index "drawImage method", scaling}} -By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to specify the width and height of the drawn image, when those aren't the same as origin image. +By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to specify the width and height of the drawn image, when those aren't the same as the origin image. When `drawImage` is given _nine_ arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied. @@ -502,11 +502,11 @@ if}} {{index mirroring}} -Scaling will cause everything about the drawn image, including the ((line width)), to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at x position 100 will end up at what used to be position -100. +Scaling will cause everything about the drawn image, including the ((line width)), to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at _x_ position 100 will end up at what used to be position -100. {{index "drawImage method"}} -To turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage`. That would move our picture outside of the ((canvas)), where it won't be visible. We could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn't require the code doing the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens. +To turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage`. That would move our picture outside of the ((canvas)), where it won't be visible. We could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at _x_ position -50 instead of 0. Another solution, which doesn't require the code doing the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens. {{index "rotate method", "translate method", transformation}} @@ -524,7 +524,7 @@ But if we _first_ rotate by 20 degrees and _then_ translate by (50,50), the tran {{index axis, mirroring}} -To flip a picture around the vertical line at a given x position, we can do the following: +To flip a picture around the vertical line at a given _x_ position, we can do the following: ```{includeCode: true} function flipHorizontally(context, around) { @@ -542,7 +542,7 @@ We move the y-((axis)) to where we want our ((mirror)) to be, apply the mirrorin {{index "translate method", "scale method", transformation, canvas}} -This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive x position, it would, by default, be in the place where triangle 1 is. A call to `flipHorizontally` first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second `translate` call fixes this—it "cancels" the initial translation and makes triangle 4 appear exactly where it should. +This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive _x_ position, it would, by default, be in the place where triangle 1 is. A call to `flipHorizontally` first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second `translate` call fixes this—it "cancels" the initial translation and makes triangle 4 appear exactly where it should. We can now draw a mirrored character at position (100,0) by flipping the world around the character's vertical center. @@ -609,7 +609,7 @@ if}} {{index "save method", "restore method", canvas, "rotate method"}} -If the calls to `save` and `restore` were not there, the second recursive call to `branch` would end up with the position and rotation created by the first call. It wouldn't be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree. +If the calls to `save` and `restore` were not there, the second recursive call to `branch` would end up with the position and rotation created by the first call. It would be connected not to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree. {{id canvasdisplay}} @@ -621,7 +621,7 @@ We now know enough about ((canvas)) drawing to start working on a ((canvas))-bas {{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} -We define another display object type called `CanvasDisplay`, supporting the same interface as `DOMDisplay` from [Chapter ?](game#domdisplay), namely, the methods `syncState` and `clear`. +We define another display object type called `CanvasDisplay`, supporting the same interface as `DOMDisplay` from [Chapter ?](game#domdisplay)—namely, the methods `syncState` and `clear`. {{index [state, "in objects"]}} @@ -918,9 +918,9 @@ Write a program that draws the following ((shape))s on a ((canvas)): When drawing the last two shapes, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions. -{{index readability, "hard-coding"}} +{{index readability, "hardcoding"}} -I recommend creating a function for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to hard-code numbers all over your code, tends to make the code needlessly hard to read and modify. +I recommend creating a function for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to hardcode numbers all over your code, tends to make the code needlessly hard to read and modify. {{if interactive @@ -1071,7 +1071,7 @@ hint}} {{index optimization, "bitmap graphics", mirror}} -One unfortunate thing about ((transformation))s is that they slow down the drawing of bitmaps. The position and size of each ((pixel)) has to be transformed, and though it is possible that ((browser))s will get cleverer about transformation in the ((future)), they currently cause a measurable increase in the time it takes to draw a bitmap. +One unfortunate thing about ((transformation))s is that they slow down the drawing of bitmaps. The position and size of each ((pixel)) have to be transformed, and though it is possible that ((browser))s will get cleverer about transformation in the ((future)), they currently cause a measurable increase in the time it takes to draw a bitmap. In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion. From a520916d6fc54abd6285de237546c32ce944b7ad Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 9 Jul 2024 09:40:49 +0200 Subject: [PATCH 372/392] Copyediting chapter 18 --- 18_http.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/18_http.md b/18_http.md index f37d47bbb..698a10920 100644 --- a/18_http.md +++ b/18_http.md @@ -132,7 +132,7 @@ The ((question mark)) indicates the end of the path part of the URL and the star {{index [escaping, "in URLs"], "hexadecimal number", "encodeURIComponent function", "decodeURIComponent function"}} -The actual message encoded in the URL is "Yes?", but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as `%3F`, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called _((URL encoding))_, uses a ((percent sign)) followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the `encodeURIComponent` and `decodeURIComponent` functions to encode and decode this format. +The actual message encoded in the URL is "Yes?" but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as `%3F`, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called _((URL encoding))_, uses a ((percent sign)) followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the `encodeURIComponent` and `decodeURIComponent` functions to encode and decode this format. ``` console.log(encodeURIComponent("Yes?")); @@ -143,7 +143,7 @@ console.log(decodeURIComponent("Yes%3F")); {{index "body (HTTP)", "POST method"}} -If we change the `method` attribute of the HTML form in the example we saw earlier to `POST`, the ((HTTP)) request made to submit the ((form)) will use the `POST` method and put the ((query string)) in the body of the request, rather than adding it to the URL. +If we change the `method` attribute of the HTML form in the example we saw earlier to `POST`, the ((HTTP)) request made to submit the ((form)) will use the `POST` method and put the ((query string)) in the body of the request rather than adding it to the URL. ```{lang: http} POST /example/message.html HTTP/1.1 @@ -176,7 +176,7 @@ fetch("example/data.txt").then(response => { {{index "Response class", "status property", "headers property"}} -Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case-insensitive because header names are not supposed to be case-sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. +Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It can also be rejected if there is a network error or if the ((server)) to which that the request is addressed can't be found. @@ -261,7 +261,7 @@ When thinking in terms of remote procedure calls, HTTP is just a vehicle for com Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/users/larry`), which again returns the document representing the resource. -This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy of a resource on the client for fast access). The concepts used in HTTP, which are well-designed, can provide a helpful set of principles to design your server interface around. +This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy of a resource on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around. ## Security and HTTPS @@ -379,7 +379,7 @@ Whenever the value of a form field changes, it will fire a `"change"` event. {{indexsee "keyboard focus", focus}} -Unlike most elements in HTML documents, form fields can get _keyboard ((focus))_. When clicked, moved to with the [tab]{keyname} key, or activated in some other way, they become the currently active element and the recipient of keyboard ((input)). +Unlike most elements in HTML documents, form fields can get _keyboard ((focus))_. When clicked, moved to with [tab]{keyname}, or activated in some other way, they become the currently active element and the recipient of keyboard ((input)). {{index "option (HTML tag)", "select (HTML tag)"}} @@ -407,7 +407,7 @@ For some pages, the user is expected to want to interact with a form field immed {{index "tab key", keyboard, "tabindex attribute", "a (HTML tag)"}} -Browsers allow the user to move the focus through the document by pressing the [tab]{keyname} key to move to the next focusable element, and [shift-tab]{keyname} to move back to the previous element. By default, elements are visited in the order in which they appear in the document. It is possible to use the `tabindex` attribute to change this order. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first: +Browsers allow the user to move the focus through the document by pressing [tab]{keyname} to move to the next focusable element, and [shift-tab]{keyname} to move back to the previous element. By default, elements are visited in the order in which they appear in the document. It is possible to use the `tabindex` attribute to change this order. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first: ```{lang: html, focus: true} <input type="text" tabindex=1> <a href=".">(help)</a> @@ -614,7 +614,7 @@ Select fields also have a variant more akin to a list of checkboxes rather than {{index "option (HTML tag)", "value attribute"}} -Each `<option>` tag has a value. This value can be defined with a `value` attribute. When that is not given, the ((text)) inside the option will count as its value. The `value` property of a `<select>` element reflects the currently selected option. For a `multiple` field, though, this property doesn't mean much since it will give the value of only _one_ of the currently selected options. +Each `<option>` tag has a value. This value can be defined with a `value` attribute. When that is not given, the ((text)) inside the option will count as its value. The `value` property of a `<select>` element reflects the currently selected option. For a `multiple` field, though, this property doesn't mean much, since it will give the value of only _one_ of the currently selected options. {{index "select (HTML tag)", "options property", "selected attribute"}} @@ -883,7 +883,7 @@ hint}} {{index "JavaScript console", "workbench (exercise)"}} -Build an interface that allows people to type and run pieces of JavaScript code. +Build an interface that allows users to type and run pieces of JavaScript code. {{index "textarea (HTML tag)", "button (HTML tag)", "Function constructor", "error message"}} From e97ec190708ce5a5c786a674465751bba851602a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 9 Jul 2024 09:44:33 +0200 Subject: [PATCH 373/392] Copyediting chapter 19 --- 19_paint.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/19_paint.md b/19_paint.md index 7e2364cad..93d655dca 100644 --- a/19_paint.md +++ b/19_paint.md @@ -174,7 +174,7 @@ The first component we'll define is the part of the interface that displays the {{index "PictureCanvas class", "callback function", "scale constant", "canvas (HTML tag)", "mousedown event", "touchstart event", [state, "of application"]}} -As such, we can define it as a component that only knows about the current picture, not the whole application state. Because it doesn't know how the application as a whole works, it cannot directly dispatch ((action))s. Rather, when responding to pointer events, it calls a callback function provided by the code that created it, which will handle the application-specific parts. +Therefore, we can define it as a component that only knows about the current picture, not the whole application state. Because it doesn't know how the application as a whole works, it cannot directly dispatch ((action))s. Rather, when responding to pointer events, it calls a callback function provided by the code that created it, which will handle the application-specific parts. ```{includeCode: true} const scale = 10; @@ -322,7 +322,7 @@ The pointer handler given to `PictureCanvas` calls the currently selected tool w {{index "reduce method", "map method", [whitespace, "in HTML"], "syncState method"}} -All controls are constructed and stored in `this.controls` so that they can be updated when the application state changes. The call to `reduce` introduces spaces between the controls' DOM elements. That way they don't look so pressed together. +All controls are constructed and stored in `this.controls` so that they can be updated when the application state changes. The call to `reduce` introduces spaces between the controls' DOM elements. That way, they don't look so pressed together. {{index "select (HTML tag)", "change event", "ToolSelect class", "syncState method"}} @@ -620,7 +620,7 @@ The `data` property of the object returned by `getImageData` is an array of colo The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 16^2^ = 256 different numbers. The `toString` method of numbers can be given a base as an argument, so `n.toString(16)` will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the `hex` helper function calls `padStart` to add a leading 0 when necessary. -We can load and save now! That leaves one more feature before we're done. +We can load and save now! That leaves just one more feature before we're done. ## Undo history @@ -774,7 +774,7 @@ Do this by modifying the `PixelEditor` component. Add a `tabIndex` property of 0 {{index "ctrlKey property", "metaKey property", "control key", "command key"}} -Remember that keyboard events have `ctrlKey` and `metaKey` (for the [command]{keyname} key on Mac) properties that you can use to see whether those keys are held down. +Remember that keyboard events have `ctrlKey` and `metaKey` (for [command]{keyname} on Mac) properties that you can use to see whether those keys are held down. {{if interactive @@ -937,7 +937,7 @@ hint}} {{index "proper lines (exercise)", "line drawing"}} -This is a more advanced exercise than the preceding two, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and ((patience)) before starting to work on this exercise, and don't get discouraged by initial failures. +This is a more advanced exercise than the preceding three, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and ((patience)) before starting to work on this exercise, and don't get discouraged by initial failures. {{index "draw function", "mousemove event", "touchmove event"}} From bf867fc35a9185bb1ace49b8ab75833d16eed767 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 9 Jul 2024 09:56:47 +0200 Subject: [PATCH 374/392] Copyediting chapter 20 --- 18_http.md | 4 ++-- 20_node.md | 54 +++++++++++++++++++++++----------------------- 21_skillsharing.md | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/18_http.md b/18_http.md index 698a10920..69da0a36e 100644 --- a/18_http.md +++ b/18_http.md @@ -648,7 +648,7 @@ This example extracts the selected values from a `multiple` select field and use ## File fields -{{index file, "hard drive", "file system", security, "file field", "input (HTML tag)"}} +{{index file, "hard drive", "filesystem", security, "file field", "input (HTML tag)"}} File fields were originally designed as a way to ((upload)) files from the user's machine through a form. In modern browsers, they also provide a way to read such files from JavaScript programs. The field acts as a kind of gatekeeper. The script cannot simply start reading private files from the user's computer, but if the user selects a file in such a field, the browser interprets that action to mean that the script may read the file. @@ -837,7 +837,7 @@ HTML can represent various types of form fields, such as text fields, checkboxes When a form is submitted, a `"submit"` event is fired on it. A JavaScript handler can call `preventDefault` on that event to disable the browser's default behavior. Form field elements may also occur outside of a form tag. -When the user has selected a file from their local file system in a file picker field, the `FileReader` interface can be used to access the content of this file from a JavaScript program. +When the user has selected a file from their local filesystem in a file picker field, the `FileReader` interface can be used to access the content of this file from a JavaScript program. The `localStorage` and `sessionStorage` objects can be used to save information in a way that survives page reloads. The first object saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed. diff --git a/20_node.md b/20_node.md index 1c959f990..d688b2fbf 100644 --- a/20_node.md +++ b/20_node.md @@ -4,7 +4,7 @@ {{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} -A student asked, 'The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?'. Fu-Tzu replied, 'The builders of old used only sticks and clay, yet they made beautiful huts.' +A student asked, 'The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?' Fu-Tzu replied, 'The builders of old used only sticks and clay, yet they made beautiful huts.' quote}} @@ -38,7 +38,7 @@ In such programs, asynchronous programming is often helpful. It allows the progr {{index "programming language", "Node.js", standard}} -Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do input and output. Thus, JavaScript could be fit onto Node's rather eccentric approach to network and file system programming without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the ((community)) around the language was used to an asynchronous programming style. +Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do input and output. Thus, JavaScript could be fit onto Node's rather eccentric approach to network and filesystem programming without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the ((community)) around the language was used to an asynchronous programming style. ## The node command @@ -60,7 +60,7 @@ Hello world {{index "console.log"}} -The `console.log` method in Node does something similar to what it does in the browser. It prints out a piece of text. But in Node, the text will go to the process's ((standard output)) stream, rather than to a browser's ((JavaScript console)). When running `node` from the command line, that means you see the logged values in your ((terminal)). +The `console.log` method in Node does something similar to what it does in the browser. It prints out a piece of text. But in Node, the text will go to the process's ((standard output)) stream rather than to a browser's ((JavaScript console)). When running `node` from the command line, that means you see the logged values in your ((terminal)). {{index "node program", "read-eval-print loop"}} @@ -107,13 +107,13 @@ Node started out using the ((CommonJS)) module system, based on the `require` fu But today, Node also supports the more modern ES module system. When a script's filename ends in `.mjs`, it is considered to be such a module, and you can use `import` and `export` in it (but not `require`). We will use ES modules in this chapter. -{{index [path, "file system"], "relative path", resolution}} +{{index [path, "filesystem"], "relative path", resolution}} -When importing a module—whether with `require` or `import`—Node has to resolve the given string to an actual ((file)) that it can load. Names that start with `/`, `./`, or `../` are resolved as files, relative to the current module's path. Here, `.` stands for the current directory, `../` for one directory up, and `/` for the root of the file system. If you ask for `"./graph.mjs"` from the file `/tmp/robot/robot.mjs`, Node will try to load the file `/tmp/robot/graph.mjs`. +When importing a module—whether with `require` or `import`—Node has to resolve the given string to an actual ((file)) that it can load. Names that start with `/`, `./`, or `../` are resolved as files, relative to the current module's path. Here, `.` stands for the current directory, `../` for one directory up, and `/` for the root of the filesystem. If you ask for `"./graph.mjs"` from the file `/tmp/robot/robot.mjs`, Node will try to load the file `/tmp/robot/graph.mjs`. {{index "node_modules directory", directory}} -When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in file system module. Importing `"robot"` might try to load the library found in `node_modules/robot/`. It's common to install such libraries using ((NPM)), which we'll return to in a moment. +When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in filesystem module. Importing `"robot"` might try to load the library found in `node_modules/robot/`. It's common to install such libraries using ((NPM)), which we'll return to in a moment. {{index "import keyword", "Node.js", "garble example"}} @@ -173,13 +173,13 @@ $ node After running `npm install`, ((NPM)) will have created a directory called `node_modules`. Inside that directory will be an `ini` directory that contains the ((library)). You can open it and look at the code. When we import `"ini"`, this library is loaded, and we can call its `parse` property to parse a configuration file. -By default NPM installs packages under the current directory, rather than in a central place. If you are used to other package managers, this may seem unusual, but it has advantages—it puts each application in full control of the packages it installs and makes it easier to manage versions and clean up when removing an application. +By default, NPM installs packages under the current directory rather than in a central place. If you are used to other package managers, this may seem unusual, but it has advantages—it puts each application in full control of the packages it installs and makes it easier to manage versions and clean up when removing an application. ### Package files {{index "package.json", dependency}} -After running `npm install` to install some package, you will find not only a `node_modules` directory, but also a file called `package.json` in your current directory. It is recommended to have such a file for each project. You can create it manually or run `npm init`. This file contains information about the project, such as its name and ((version)), and lists its dependencies. +After running `npm install` to install some package, you will find not only a `node_modules` directory but also a file called `package.json` in your current directory. It is recommended to have such a file for each project. You can create it manually or run `npm init`. This file contains information about the project, such as its name and ((version)), and lists its dependencies. The robot simulation from [Chapter ?](robot), as modularized in the exercise in [Chapter ?](modules#modular_robot), might have a `package.json` file like this: @@ -210,7 +210,7 @@ A `package.json` file lists both the program's own ((version)) and versions for {{index compatibility}} -NPM demands that its packages follow a schema called _((semantic versioning))_, which encodes some information about which versions are _compatible_ (don't break the old interface) in the version number. A semantic version consists of three numbers, separated by periods, such as `2.3.0`. Every time new functionality is added, the middle number has to be incremented. Every time compatibility is broken, so that existing code that uses the package might not work with the new version, the first number has to be incremented. +NPM demands that its packages follow a schema called _((semantic versioning))_, which encodes some information about which versions are _compatible_ (don't break the old interface) in the version number. A semantic version consists of three numbers separated by periods, such as `2.3.0`. Every time new functionality is added, the middle number has to be incremented. Every time compatibility is broken, so that existing code that uses the package might not work with the new version, the first number has to be incremented. {{index "caret character"}} @@ -222,11 +222,11 @@ The `npm` command is also used to publish new packages or new versions of packag This book won't delve further into the details of ((NPM)) usage. Refer to [_https://npmjs.com_](https://npmjs.com) for further documentation and a way to search for packages. -## The file system module +## The filesystem module {{index directory, "node:fs package", "Node.js", [file, access]}} -One of the most commonly used built-in modules in Node is the `node:fs` module, which stands for _((file system))_. It exports functions for working with files and directories. +One of the most commonly used built-in modules in Node is the `node:fs` module, which stands for _((filesystem))_. It exports functions for working with files and directories. {{index "readFile function", "callback function"}} @@ -253,7 +253,7 @@ readFile("file.txt", (error, buffer) => { }); ``` -{{index "writeFile function", "file system", [file, access]}} +{{index "writeFile function", "filesystem", [file, access]}} A similar function, `writeFile`, is used to write a file to disk. @@ -275,11 +275,11 @@ The `node:fs` module contains many other useful functions: `readdir` will give y {{index ["asynchronous programming", "in Node.js"], "Node.js", "error handling", "callback function"}} -Most of these take a callback function as the last parameter, which they call either with an error (the first argument) or with a successful result (the second). As we saw in [Chapter ?](async), there are downsides to this style of programming—the biggest one being that error handling becomes verbose and error-prone. +Most of these take a callback function as the last parameter, which they call either with an error (the first argument) or with a successful result (the second). As we saw in [Chapter ?](async), there are downsides to this style of programming—the biggest one being that error handling becomes verbose and error prone. {{index "Promise class", "node:fs/promises package"}} -The `node:fs/promises` module exports most of the same functions as the old `node:fs` module, but uses promises rather than callback functions. +The `node:fs/promises` module exports most of the same functions as the old `node:fs` module but uses promises rather than callback functions. ``` import {readFile} from "node:fs/promises"; @@ -330,7 +330,7 @@ If you run this script on your own machine, you can point your web browser at [_ {{index "createServer function", HTTP}} -The function passed as argument to `createServer` is called every time a client connects to the server. The `request` and `response` bindings are objects representing the incoming and outgoing data. The first contains information about the ((request)), such as its `url` property, which tells us to what URL the request was made. +The function passed as the argument to `createServer` is called every time a client connects to the server. The `request` and `response` bindings are objects representing the incoming and outgoing data. The first contains information about the ((request)), such as its `url` property, which tells us to what URL the request was made. When you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser. @@ -340,7 +340,7 @@ To send something to the client, you call methods on the `response` object. The {{index "writable stream", "body (HTTP)", stream, "write method", "end method"}} -Next, the actual response body (the document itself) is sent with `response.write`. You're allowed to call this method multiple times if you want to send the response piece by piece, for example to stream data to the client as it becomes available. Finally, `response.end` signals the end of the response. +Next, the actual response body (the document itself) is sent with `response.write`. You're allowed to call this method multiple times if you want to send the response piece by piece—for example, to stream data to the client as it becomes available. Finally, `response.end` signals the end of the response. {{index "listen method"}} @@ -366,7 +366,7 @@ The response object that the HTTP server could write to is an example of a _writ {{index "createWriteStream function", "writeFile function", [file, stream]}} -It is possible to create a writable stream that points at a file with the `createWriteStream` function from the `node:fs` module. You can then use the `write` method on the resulting object to write the file one piece at a time, rather than in one shot as with `writeFile`. +It is possible to create a writable stream that points at a file with the `createWriteStream` function from the `node:fs` module. You can then use the `write` method on the resulting object to write the file one piece at a time rather than in one shot, as with `writeFile`. {{index "createServer function", "request function", "event handling", "readable stream"}} @@ -414,19 +414,19 @@ fetch("http://localhost:8000/", { {{index "file server example", "Node.js", [HTTP, server]}} -Let's combine our newfound knowledge about HTTP ((server))s and working with the ((file system)) to create a bridge between the two: an HTTP server that allows ((remote access)) to a file system. Such a server has all kinds of uses—it allows web applications to store and share data, or it can give a group of people shared access to a bunch of files. +Let's combine our newfound knowledge about HTTP ((server))s and working with the ((filesystem)) to create a bridge between the two: an HTTP server that allows ((remote access)) to a filesystem. Such a server has all kinds of uses—it allows web applications to store and share data, or it can give a group of people shared access to a bunch of files. {{index [path, URL], "GET method", "PUT method", "DELETE method", [file, resource]}} When we treat files as HTTP ((resource))s, the HTTP methods `GET`, `PUT`, and `DELETE` can be used to read, write, and delete the files, respectively. We will interpret the path in the request as the path of the file that the request refers to. -{{index [path, "file system"], "relative path"}} +{{index [path, "filesystem"], "relative path"}} -We probably don't want to share our whole file system, so we'll interpret these paths as starting in the server's working ((directory)), which is the directory in which it was started. If I ran the server from `/tmp/public/` (or `C:\tmp\public\` on Windows), then a request for `/file.txt` should refer to `/tmp/public/file.txt` (or `C:\tmp\public\file.txt`). +We probably don't want to share our whole filesystem, so we'll interpret these paths as starting in the server's working ((directory)), which is the directory in which it was started. If I ran the server from `/tmp/public/` (or `C:\tmp\public\` on Windows), then a request for `/file.txt` should refer to `/tmp/public/file.txt` (or `C:\tmp\public\file.txt`). {{index "file server example", "Node.js", "methods object", "Promise class"}} -We'll build the program piece by piece, using an object called `methods` to store the functions that handle the various HTTP methods. Method handlers are `async` functions that get the request object as argument and return a promise that resolves to an object that describes the response. +We'll build the program piece by piece, using an object called `methods` to store the functions that handle the various HTTP methods. Method handlers are `async` functions that get the request object as their argument and return a promise that resolves to an object that describes the response. ```{includeCode: ">code/file_server.mjs"} import {createServer} from "node:http"; @@ -490,7 +490,7 @@ function urlPath(url) { } ``` -As soon as you set up a program to accept network requests, you have to start worrying about ((security)). In this case, if we aren't careful, it is likely that we'll accidentally expose our whole ((file system)) to the network. +As soon as you set up a program to accept network requests, you have to start worrying about ((security)). In this case, if we aren't careful, it is likely that we'll accidentally expose our whole ((filesystem)) to the network. File paths are strings in Node. To map such a string to an actual file, there's a nontrivial amount of interpretation going on. Paths may, for example, include `../` to refer to a parent directory. One obvious source of problems would be requests for paths like `/../secret_file`. @@ -614,7 +614,7 @@ We don't need to check whether the file exists this time—if it does, we'll jus {{index "error event", "finish event"}} -When something goes wrong when opening the file, `createWriteStream` will still return a stream, but that stream will fire an `"error"` event. The stream from the request may also fail, for example if the network goes down. So we wire up both streams' `"error"` events to reject the promise. When `pipe` is done, it will close the output stream, which causes it to fire a `"finish"` event. That's the point at which we can successfully resolve the promise (returning nothing). +When something goes wrong when opening the file, `createWriteStream` will still return a stream, but that stream will fire an `"error"` event. The stream from the request may also fail—for example, if the network goes down. So we wire up both streams' `"error"` events to reject the promise. When `pipe` is done, it will close the output stream, which causes it to fire a `"finish"` event. That's the point at which we can successfully resolve the promise (returning nothing). {{index download, "file server example", "Node.js"}} @@ -643,9 +643,9 @@ The first request for `file.txt` fails since the file does not exist yet. The `P Node is a nice, small system that lets us run JavaScript in a nonbrowser context. It was originally designed for network tasks to play the role of a node in a network, but it lends itself to all kinds of scripting tasks. If writing JavaScript is something you enjoy, automating tasks with Node may work well for you. -NPM provides packages for everything you can think of (and quite a few things you'd probably never think of), and it allows you to fetch and install those packages with the `npm` program. Node comes with a number of built-in modules, including the `node:fs` module for working with the file system and the `node:http` module for running HTTP servers. +NPM provides packages for everything you can think of (and quite a few things you'd probably never think of), and it allows you to fetch and install those packages with the `npm` program. Node comes with a number of built-in modules, including the `node:fs` module for working with the filesystem and the `node:http` module for running HTTP servers. -All input and output in Node is done asynchronously, unless you explicitly use a synchronous variant of a function, such as `readFileSync`. Node originally used callbacks for asynchronous functionality, but the `node:fs/promises` package provides a promise-based interface to the file system. +All input and output in Node is done asynchronously, unless you explicitly use a synchronous variant of a function, such as `readFileSync`. Node originally used callbacks for asynchronous functionality, but the `node:fs/promises` package provides a promise-based interface to the filesystem. ## Exercises @@ -661,7 +661,7 @@ When that works, extend it so that when one of the arguments is a ((directory)), {{index ["asynchronous programming", "in Node.js"], "synchronous programming"}} -Use asynchronous or synchronous file system functions as you see fit. Setting things up so that multiple asynchronous actions are requested at the same time might speed things up a little, but not a huge amount, since most file systems can read only one thing at a time. +Use asynchronous or synchronous filesystem functions as you see fit. Setting things up so that multiple asynchronous actions are requested at the same time might speed things up a little, but not a huge amount, since most filesystems can read only one thing at a time. {{hint @@ -679,7 +679,7 @@ To figure out whether something is a directory, you can again use `stat` (or `st {{index "readdir function", "readdirSync function"}} -Exploring a directory is a branching process. You can do it either by using a recursive function or by keeping an array of work (files that still need to be explored). To find the files in a directory, you can call `readdir` or `readdirSync`. Note the strange capitalization—Node's file system function naming is loosely based on standard Unix functions, such as `readdir`, that are all lowercase, but then it adds `Sync` with a capital letter. +Exploring a directory is a branching process. You can do it either by using a recursive function or by keeping an array of work (files that still need to be explored). To find the files in a directory, you can call `readdir` or `readdirSync`. Note the strange capitalization—Node's filesystem function naming is loosely based on standard Unix functions, such as `readdir`, that are all lowercase, but then it adds `Sync` with a capital letter. To go from a filename read with `readdir` to a full path name, you have to combine it with the name of the directory, either putting `sep` from `node:path` between them, or using the `join` function from that same package. diff --git a/21_skillsharing.md b/21_skillsharing.md index 4d929a72e..afe7a71b6 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -782,7 +782,7 @@ Extend the server so that it stores the talk data to disk and automatically relo {{hint -{{index "file system", "writeFile function", "updated method", persistence}} +{{index "filesystem", "writeFile function", "updated method", persistence}} The simplest solution I can come up with is to encode the whole `talks` object as ((JSON)) and dump it to a file with `writeFile`. There is already a method (`updated`) that is called every time the server's data changes. It can be extended to write the new data to disk. From a03349465aa5db4b73d2120fa9bd13553ba47a4a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Sun, 14 Jul 2024 21:52:50 +0200 Subject: [PATCH 375/392] Copyediting chapter 21 --- 21_skillsharing.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/21_skillsharing.md b/21_skillsharing.md index afe7a71b6..23c59e44e 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -148,7 +148,7 @@ Content-Type: application/json ETag: "5" Content-Length: 295 -[....] +[...] ``` {{index security}} @@ -203,7 +203,7 @@ The module exports the `Router` class. A router object allows you to register ha {{index "capture group", "decodeURIComponent function", [escaping, "in URLs"]}} -Handler functions are called with the `context` value given to `resolve`. We will use this to give them access to our server state. Additionally, they receive the match strings for any groups they defined in their ((regular expression)), and the request object. The strings have to be URL-decoded since the raw URL may contain `%20`-style codes. +Handler functions are called with the `context` value given to `resolve`. We will use this to give them access to our server state. Additionally, they receive the match strings for any groups they defined in their ((regular expression)), and the request object. The strings have to be URL-decoded, since the raw URL may contain `%20`-style codes. ### Serving files @@ -272,7 +272,7 @@ async function serveFromRouter(server, request, ### Talks as resources -The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. We'll add some handlers to our router that expose these as HTTP ((resource))s under `/talks/[title]`. +The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. We'll add some handlers to our router that expose these as HTTP ((resource))s under `/talks/<title>`. {{index "GET method", "404 (HTTP status code)" "hasOwn function"}} @@ -390,7 +390,7 @@ SkillShareServer.prototype.talkResponse = function() { {{index "query string", "url package", parsing}} -The handler itself needs to look at the request headers to see whether `If-None-Match` and `Prefer` headers are present. Node stores headers, whose names are specified to be case-insensitive, under their lowercase names. +The handler itself needs to look at the request headers to see whether `If-None-Match` and `Prefer` headers are present. Node stores headers, whose names are specified to be case insensitive, under their lowercase names. ```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} router.add("GET", /^\/talks$/, async (server, request) => { @@ -549,7 +549,7 @@ function talkURL(title) { {{index "error handling", "user experience", "reportError function"}} -When the request fails, we don't want our page to just sit there doing nothing without explanation. The function called `reportError`, which we used as `catch` handler, shows the user a crude dialog to tell them something went wrong. +When the request fails, we don't want our page to just sit there doing nothing without explanation. The function called `reportError`, which we used as the `catch` handler, shows the user a crude dialog to tell them something went wrong. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function reportError(error) { @@ -627,7 +627,7 @@ function renderTalk(talk, dispatch) { The `"submit"` event handler calls `form.reset` to clear the form's content after creating a `"newComment"` action. -When creating moderately complex pieces of DOM, this style of programming starts to look rather messy. To avoid this, people often use a _((templating language))_, which allows you to write your interface as an HTML file with some special markers to indicate where dynamic elements go. Or they use _((JSX))_, a non-standard JavaScript dialect that allows you to write something very close to HTML tags in your program as if they are JavaScript expressions. Both of these approaches use additional tools to pre-process the code before it can be run, which we will avoid in this chapter. +When creating moderately complex pieces of DOM, this style of programming starts to look rather messy. To avoid this, people often use a _((templating language))_, which allows you to write your interface as an HTML file with some special markers to indicate where dynamic elements go. Or they use _((JSX))_, a nonstandard JavaScript dialect that allows you to write something very close to HTML tags in your program as if they are JavaScript expressions. Both of these approaches use additional tools to preprocess the code before it can be run, which we will avoid in this chapter. Comments are simple to render. From afcb0892254496874ebb1c5e055d9ad3e21a6992 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Sun, 14 Jul 2024 22:31:07 +0200 Subject: [PATCH 376/392] Copyediting hint text --- 04_data.md | 12 ++++++------ 05_higher_order.md | 2 +- 06_object.md | 2 +- 07_robot.md | 4 ++-- 10_modules.md | 2 +- 11_async.md | 6 +++--- 12_language.md | 4 ++-- 16_game.md | 4 ++-- 17_canvas.md | 6 +++--- 19_paint.md | 10 +++++----- 20_node.md | 4 ++-- 21_skillsharing.md | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/04_data.md b/04_data.md index d9662bfa6..5ce3b4c0c 100644 --- a/04_data.md +++ b/04_data.md @@ -1021,7 +1021,7 @@ The step parameter can be an optional parameter that defaults (using the `=` ope Having `range` understand negative step values is probably best done by writing two separate loops—one for counting up and one for counting down—because the comparison that checks whether the loop is finished needs to be `>=` rather than `<=` when counting downward. -It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, `range(5, 2)` returns something meaningful, rather than getting stuck in an ((infinite loop)). It is possible to refer to previous parameters in the default value of a parameter. +It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, `range(5, 2)` returns something meaningful rather than getting stuck in an ((infinite loop)). It is possible to refer to previous parameters in the default value of a parameter. hint}} @@ -1057,13 +1057,13 @@ if}} {{index "reversing (exercise)"}} -There are two obvious ways to implement `reverseArray`. The first is to simply go over the input array from front to back and use the `unshift` method on the new array to insert each element at its start. The second is to loop over the input array backwards and use the `push` method. Iterating over an array backwards requires a (somewhat awkward) `for` specification, like `(let i = array.length - 1; i >= 0; i--)`. +There are two obvious ways to implement `reverseArray`. The first is to simply go over the input array from front to back and use the `unshift` method on the new array to insert each element at its start. The second is to loop over the input array backward and use the `push` method. Iterating over an array backward requires a (somewhat awkward) `for` specification, like `(let i = array.length - 1; i >= 0; i--)`. {{index "slice method"}} Reversing the array in place is harder. You have to be careful not to overwrite elements that you will later need. Using `reverseArray` or otherwise copying the whole array (`array.slice()` is a good way to copy an array) works but is cheating. -The trick is to _swap_ the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use `Math.floor` to round down—you don't need to touch the middle element in an array with an odd number of elements) and swapping the element at position `i` with the one at position `array.length - 1 - i`. You can use a local binding to briefly hold on to one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be. +The trick is to _swap_ the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use `Math.floor` to round down—you don't need to touch the middle element in an array with an odd number of elements) and swapping the element at position `i` with the one at position `array.length - 1 - i`. You can use a local binding to briefly hold onto one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be. hint}} @@ -1123,7 +1123,7 @@ if}} {{index "list (exercise)", "linked list"}} -Building up a list is easier when done back to front. So `arrayToList` could iterate over the array backwards (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like `list = {value: X, rest: list}` to add an element. +Building up a list is easier when done back to front. So `arrayToList` could iterate over the array backward (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like `list = {value: X, rest: list}` to add an element. {{index "for loop"}} @@ -1133,7 +1133,7 @@ To run over a list (in `listToArray` and `nth`), a `for` loop specification like for (let node = list; node; node = node.rest) {} ``` -Can you see how that works? Every iteration of the loop, `node` points to the current sublist, and the body can read its `value` property to get the current element. At the end of an iteration, `node` moves to the next sublist. When that is null, we have reached the end of the list, and the loop is finished. +Can you see how that works? Every iteration of the loop, `node` points to the current sublist, and the body can read its `value` property to get the current element. At the end of an iteration, `node` moves to the next sublist. When that is `null`, we have reached the end of the list, and the loop is finished. {{index recursion}} @@ -1187,6 +1187,6 @@ Use `Object.keys` to go over the properties. You need to test whether both objec {{index "return value"}} -Returning the correct value from the function is best done by immediately returning false when a mismatch is found and returning true at the end of the function. +Returning the correct value from the function is best done by immediately returning `false` when a mismatch is found and returning `true` at the end of the function. hint}} diff --git a/05_higher_order.md b/05_higher_order.md index 70c253019..17465d2ee 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -627,7 +627,7 @@ if}} {{index "everything (exercise)", "short-circuit evaluation", "return keyword"}} -Like the `&&` operator, the `every` method can stop evaluating further elements as soon as it has found one that doesn't match. So the loop-based version can jump out of the loop—with `break` or `return`—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return `true`. +Like the `&&` operator, the `every` method can stop evaluating further elements as soon as it has found one that doesn't match. So the loop-based version can jump out of the loop—with `break` or `return`—as soon as it runs into an element for which the predicate function returns `false`. If the loop runs to its end without finding such an element, we know that all elements matched and we should return `true`. To build `every` on top of `some`, we can apply _((De Morgan's laws))_, which state that `a && b` equals `!(!a || !b)`. This can be generalized to arrays, where all elements in the array match if there is no element in the array that does not match. diff --git a/06_object.md b/06_object.md index a3522ffbb..b9c1ba604 100644 --- a/06_object.md +++ b/06_object.md @@ -866,7 +866,7 @@ The easiest way to do this is to store an array of group members in an instance {{index "push method"}} -Your class's ((constructor)) can set the member collection to an empty array. When `add` is called, it must check whether the given value is in the array or add it, for example with `push`, otherwise. +Your class's ((constructor)) can set the member collection to an empty array. When `add` is called, it must check whether the given value is in the array or add it otherwise, possibly using `push`. {{index "filter method"}} diff --git a/07_robot.md b/07_robot.md index 2ac9acb80..3c942cfb3 100644 --- a/07_robot.md +++ b/07_robot.md @@ -474,13 +474,13 @@ if}} {{index "persistent map (exercise)", "Set class", [array, creation], "PGroup class"}} -The most convenient way to represent the set of member values is still as an array since arrays are easy to copy. +The most convenient way to represent the set of member values is still as an array, since arrays are easy to copy. {{index "concat method", "filter method"}} When a value is added to the group, you can create a new group with a copy of the original array that has the value added (for example, using `concat`). When a value is deleted, you filter it from the array. -The class's ((constructor)) can take such an array as argument and store it as the instance's (only) property. This array is never updated. +The class's ((constructor)) can take such an array as its argument and store it as the instance's (only) property. This array is never updated. {{index "static property"}} diff --git a/10_modules.md b/10_modules.md index c8e299eff..238cf2b48 100644 --- a/10_modules.md +++ b/10_modules.md @@ -422,7 +422,7 @@ The `roads` module contains the raw road data (the `roads` array) and the `roadG The `VillageState` class lives in the `state` module. It depends on the `./roads` module because it needs to be able to verify that a given road exists. It also needs `randomPick`. Since that is a three-line function, we could just put it into the `state` module as an internal helper function. But `randomRobot` needs it too. So we'd have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the `random-item` package, a reasonable solution is to just make both modules depend on that. We can add the `runRobot` function to this module as well, since it's small and closely related to state management. The module exports both the `VillageState` class and the `runRobot` function. -Finally, the robots, along with the values they depend on such as `mailRoute`, could go into an `example-robots` module, which depends on `./roads` and exports the robot functions. To make it possible for `goalOrientedRobot` to do route-finding, this module also depends on `dijkstrajs`. +Finally, the robots, along with the values they depend on, such as `mailRoute`, could go into an `example-robots` module, which depends on `./roads` and exports the robot functions. To make it possible for `goalOrientedRobot` to do route-finding, this module also depends on `dijkstrajs`. By offloading some work to ((NPM)) modules, the code became a little smaller. Each individual module does something rather simple and can be read on its own. Dividing code into modules also often suggests further improvements to the program's design. In this case, it seems a little odd that the `VillageState` and the robots depend on a specific road graph. It might be a better idea to make the graph an argument to the state's constructor and make the robots read it from the state object—this reduces dependencies (which is always good) and makes it possible to run simulations on different maps (which is even better). diff --git a/11_async.md b/11_async.md index 0055d3948..ee30fd7fd 100644 --- a/11_async.md +++ b/11_async.md @@ -716,7 +716,7 @@ if}} You will need to convert the content of these files to an array. The easiest way to do that is to use the `split` method on the string produced by `textFile`. Note that for the logfiles, that will still give you an array of strings, which you have to convert to numbers before passing them to `new Date`. -Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the logfiles and the numbers in every log file) and for each one, if it happened on the correct day, take the hour it occurred in, and add one to the corresponding number in the table. +Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the logfiles and the numbers in every logfile) and for each one, if it happened on the correct day, take the hour it occurred in, and add one to the corresponding number in the table. {{index "async function", "await keyword", "Promise class"}} @@ -789,11 +789,11 @@ function activityTable(day) { {{index "await keyword", scheduling}} -Which shows that the way you structure your promises can have a real effect on the way the work is scheduled. A simple loop with `await` in it will make the process completely linear—it waits for each file to load before proceeding. `Promise.all` makes it possible for multiple tasks to conceptually be worked on at the same time, allowing them to make progress while files are still being loaded. This can be faster, but it also makes the order in which things will happen less predictable. In this case, where we're only going to be incrementing numbers in a table, which isn't hard to do in a safe way. For other kinds of problems, it may be a lot more difficult. +This shows that the way you structure your promises can have a real effect on the way the work is scheduled. A simple loop with `await` in it will make the process completely linear—it waits for each file to load before proceeding. `Promise.all` makes it possible for multiple tasks to conceptually be worked on at the same time, allowing them to make progress while files are still being loaded. This can be faster, but it also makes the order in which things will happen less predictable. In this case, we're only going to be incrementing numbers in a table, which isn't hard to do in a safe way. For other kinds of problems, it may be a lot more difficult. {{index "rejecting (a promise)", "then method"}} -When a file in the list doesn't exist, the promise returned by `textFile` will be rejected. Because `Promise.all` rejects if any of the promises given to it fail, the return value of the callback given to the first `then` will also be a rejected promise. That makes the promise returned by `then` fail, so that the callback given to the second `then` isn't even called, and a rejected promise is returned from the function. +When a file in the list doesn't exist, the promise returned by `textFile` will be rejected. Because `Promise.all` rejects if any of the promises given to it fail, the return value of the callback given to the first `then` will also be a rejected promise. That makes the promise returned by `then` fail, so the callback given to the second `then` isn't even called, and a rejected promise is returned from the function. hint}} diff --git a/12_language.md b/12_language.md index 82df58ad1..04a287336 100644 --- a/12_language.md +++ b/12_language.md @@ -598,7 +598,7 @@ if}} {{index "comments in egg (exercise)", [whitespace, syntax]}} -Make sure your solution handles multiple comments in a row, with potentially whitespace between or after them. +Make sure your solution handles multiple comments in a row, with whitespace potentially between or after them. A ((regular expression)) is probably the easiest way to solve this. Write something that matches "whitespace or a comment, zero or more times". Use the `exec` or `match` method and look at the length of the first element in the returned array (the whole match) to find out how many characters to slice off. @@ -649,6 +649,6 @@ You will have to loop through one ((scope)) at a time, using `Object.getPrototyp {{index "global scope", "run-time error"}} -If the outermost scope is reached (`Object.getPrototypeOf` returns null) and we haven't found the binding yet, it doesn't exist, and an error should be thrown. +If the outermost scope is reached (`Object.getPrototypeOf` returns `null`) and we haven't found the binding yet, it doesn't exist, and an error should be thrown. hint}} diff --git a/16_game.md b/16_game.md index 8151890bd..c41e970fb 100644 --- a/16_game.md +++ b/16_game.md @@ -1043,9 +1043,9 @@ if}} {{index "monster (exercise)", "persistent data structure"}} -If you want to implement a type of motion that is stateful, such as bouncing, make sure you store the necessary state in the actor object—include it as constructor argument and add it as a property. +If you want to implement a type of motion that is stateful, such as bouncing, make sure you store the necessary state in the actor object—include it as a constructor argument and add it as a property. -Remember that `update` returns a _new_ object, rather than changing the old one. +Remember that `update` returns a _new_ object rather than changing the old one. {{index "collision detection"}} diff --git a/17_canvas.md b/17_canvas.md index 5499741be..43dee7358 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -951,7 +951,7 @@ Make sure you reset the transformation after drawing any shape that creates one. For the ((zigzag)) (3) it becomes impractical to write a new call to `lineTo` for each line segment. Instead, you should use a ((loop)). You can have each iteration draw either two ((line)) segments (right and then left again) or one, in which case you must use the evenness (`% 2`) of the loop index to determine whether to go left or right. -You'll also need a loop for the ((spiral)) (4). If you draw a series of points, with each point moving further along a circle around the spiral's center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral. +You'll also need a loop for the ((spiral)) (4). If you draw a series of points, with each point moving farther along a circle around the spiral's center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral. {{index "quadraticCurveTo method"}} @@ -1055,11 +1055,11 @@ if}} {{index "strokeRect method", animation, "arc method"}} -A ((box)) is easy to draw with `strokeRect`. Define a binding that holds its size or define two bindings if your box's width and height differ. To create a round ((ball)), start a path and call `arc(x, y, radius, 0, 7)`, which creates an arc going from zero to more than a whole circle. Then fill the path. +A ((box)) is easy to draw with `strokeRect`. Define a binding that holds its size, or define two bindings if your box's width and height differ. To create a round ((ball)), start a path and call `arc(x, y, radius, 0, 7)`, which creates an arc going from zero to more than a whole circle. Then fill the path. {{index "collision detection", "Vec class"}} -To model the ball's position and ((speed)), you can use the `Vec` class from [Chapter ?](game#vector)[ (which is available on this page)]{if interactive}. Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every ((frame)) multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall. +To model the ball's position and ((speed)), you can use the `Vec` class from [Chapter ?](game#vector)[ (which is available on this page)]{if interactive}. Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every ((frame)) multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the _x_ component in its speed. Likewise, invert the _y_ component when it hits a horizontal wall. {{index "clearRect method", clearing}} diff --git a/19_paint.md b/19_paint.md index 93d655dca..339378e5f 100644 --- a/19_paint.md +++ b/19_paint.md @@ -893,7 +893,7 @@ You can either write a new function `updatePicture` or have `drawPicture` take a {{index "width property", "height property", "canvas (HTML tag)"}} -Because the canvas gets cleared when we change its size, you should also avoid touching its `width` and `height` properties when the old picture and the new picture have the same size. If they are different, which will happen when a new picture has been loaded, you can set the binding holding the old picture to null after changing the canvas size because you shouldn't skip any pixels after you've changed the canvas size. +Because the canvas gets cleared when we change its size, you should also avoid touching its `width` and `height` properties when the old picture and the new picture have the same size. If they are different, which will happen when a new picture has been loaded, you can set the binding holding the old picture to `null` after changing the canvas size because you shouldn't skip any pixels after you've changed the canvas size. hint}} @@ -925,7 +925,7 @@ if}} {{index "circles (exercise)", "rectangle function"}} -You can take some inspiration from the `rectangle` tool. Like that tool, you'll want to keep drawing on the _starting_ picture, rather than the current picture, when the pointer moves. +You can take some inspiration from the `rectangle` tool. As with that tool, you'll want to keep drawing on the _starting_ picture, rather than the current picture, when the pointer moves. To figure out which pixels to color, you can use the ((Pythagorean theorem)). First figure out the distance between the current pointer position and the start position by taking the square root (`Math.sqrt`) of the sum of the square (`x ** 2`) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the ((radius)), and color those that are within the circle's radius, again using the Pythagorean formula to figure out their ((distance)) from the center. @@ -987,11 +987,11 @@ if}} The thing about the problem of drawing a pixelated line is that it is really four similar but slightly different problems. Drawing a horizontal line from the left to the right is easy—you loop over the x-coordinates and color a pixel at every step. If the line has a slight slope (less than 45 degrees or ¼π radians), you can interpolate the y-coordinate along the slope. You still need one pixel per _x_ position, with the _y_ position of those pixels determined by the slope. -But as soon as your slope goes across 45 degrees, you need to switch the way you treat the coordinates. You now need one pixel per _y_ position since the line goes up more than it goes left. And then, when you cross 135 degrees, you have to go back to looping over the x-coordinates, but from right to left. +But as soon as your slope goes across 45 degrees, you need to switch the way you treat the coordinates. You now need one pixel per _y_ position, since the line goes up more than it goes left. And then, when you cross 135 degrees, you have to go back to looping over the x-coordinates, but from right to left. You don't actually have to write four loops. Since drawing a line from _A_ to _B_ is the same as drawing a line from _B_ to _A_, you can swap the start and end positions for lines going from right to left and treat them as going left to right. -So you need two different loops. The first thing your line drawing function should do is check whether the difference between the x-coordinates is larger than the difference between the y-coordinates. If it is, this is a horizontal-ish line, and if not, a vertical-ish one. +So you need two different loops. The first thing your line drawing function should do is check whether the difference between the x-coordinates is larger than the difference between the y-coordinates. If it is, this is a horizontalish line, and if not, a verticalish one. {{index "Math.abs function", "absolute value"}} @@ -1007,6 +1007,6 @@ Once you know along which ((axis)) you will be looping, you can check whether th {{index rounding}} -Then you can compute the ((slope)) of the line, which determines the amount the coordinate on the other axis changes for each step you take along your main axis. With that, you can run a loop along the main axis while also tracking the corresponding position on the other axis, and you can draw pixels on every iteration. Make sure you round the non-main axis coordinates since they are likely to be fractional and the `draw` method doesn't respond well to fractional coordinates. +Then you can compute the ((slope)) of the line, which determines the amount the coordinate on the other axis changes for each step you take along your main axis. With that, you can run a loop along the main axis while also tracking the corresponding position on the other axis, and you can draw pixels on every iteration. Make sure you round the nonmain axis coordinates, since they are likely to be fractional and the `draw` method doesn't respond well to fractional coordinates. hint}} diff --git a/20_node.md b/20_node.md index d688b2fbf..ba67c09c6 100644 --- a/20_node.md +++ b/20_node.md @@ -681,7 +681,7 @@ To figure out whether something is a directory, you can again use `stat` (or `st Exploring a directory is a branching process. You can do it either by using a recursive function or by keeping an array of work (files that still need to be explored). To find the files in a directory, you can call `readdir` or `readdirSync`. Note the strange capitalization—Node's filesystem function naming is loosely based on standard Unix functions, such as `readdir`, that are all lowercase, but then it adds `Sync` with a capital letter. -To go from a filename read with `readdir` to a full path name, you have to combine it with the name of the directory, either putting `sep` from `node:path` between them, or using the `join` function from that same package. +To go from a filename read with `readdir` to a full path name, you have to combine it with the name of the directory, either putting `sep` from `node:path` between them or using the `join` function from that same package. hint}} @@ -747,7 +747,7 @@ You can create a `<textarea>` element to hold the content of the file that is be {{index "form (HTML tag)", "submit event", "PUT method"}} -Then, when the user clicks a button (you can use a `<form>` element and `"submit"` event), make a `PUT` request to the same URL, with the content of the `<textarea>` as request body, to save the file. +Then, when the user clicks a button (you can use a `<form>` element and `"submit"` event), make a `PUT` request to the same URL, with the content of the `<textarea>` as the request body, to save the file. {{index "select (HTML tag)", "option (HTML tag)", "change event"}} diff --git a/21_skillsharing.md b/21_skillsharing.md index 23c59e44e..147f6f036 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -806,7 +806,7 @@ When multiple people are adding comments at the same time, this would be annoyin The best way to do this is probably to make the talk component an object, with a `syncState` method, so that they can be updated to show a modified version of the talk. During normal operation, the only way a talk can be changed is by adding more comments, so the `syncState` method can be relatively simple. -The difficult part is that, when a changed list of talks comes in, we have to reconcile the existing list of DOM components with the talks on the new list—deleting components whose talk was deleted and updating components whose talk changed. +The difficult part is that when a changed list of talks comes in, we have to reconcile the existing list of DOM components with the talks on the new list—deleting components whose talk was deleted and updating components whose talk changed. {{index synchronization, "live view"}} From 9da047e2c47ef35028fbe589c85ebe6f95982ffa Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 16 Jul 2024 09:38:29 +0200 Subject: [PATCH 377/392] Second page review --- 00_intro.md | 4 ++-- 01_values.md | 4 ++-- 02_program_structure.md | 2 +- 03_functions.md | 6 +++--- 04_data.md | 2 +- 05_higher_order.md | 12 ++++++------ 06_object.md | 4 +--- 08_error.md | 4 ++-- 10_modules.md | 2 +- 13_browser.md | 4 ++-- 14_dom.md | 4 ++-- 17_canvas.md | 26 +++++++++++++------------- 12 files changed, 36 insertions(+), 38 deletions(-) diff --git a/00_intro.md b/00_intro.md index 503a81d02..814f5c372 100644 --- a/00_intro.md +++ b/00_intro.md @@ -66,7 +66,7 @@ Some programmers believe that this complexity is best managed by using only a sm {{index experiment}} -This is not only boring, it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them at least once so that you understand them. A sense of what a good program looks like is developed with practice, not learned from a list of rules. +This is not only boring—it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them at least once so that you understand them. A sense of what a good program looks like is developed with practice, not learned from a list of rules. ## Why language matters @@ -110,7 +110,7 @@ Each line of the previous program contains a single instruction. It could be wri {{index readability, naming, binding}} -Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps: +Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps. ```{lang: "null"} Set “total” to 0. diff --git a/01_values.md b/01_values.md index e560e1c4d..598216994 100644 --- a/01_values.md +++ b/01_values.md @@ -357,7 +357,7 @@ The difference in meaning between `undefined` and `null` is an accident of JavaS {{index NaN, "type coercion"}} -In the [Introduction](intro), I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: +In the [introduction](intro), I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: ``` console.log(8 * null) @@ -422,7 +422,7 @@ We can use this functionality as a way to fall back on a default value. If you h {{index "?? operator", null, undefined}} -The `??` operator resembles `||`, but returns the value on the right only if the one on the left is null or undefined, not if it is some other value that can be converted to `false`. Often, this is preferable to the behavior of `||`. +The `??` operator resembles `||` but returns the value on the right only if the one on the left is `null` or `undefined`, not if it is some other value that can be converted to `false`. Often, this is preferable to the behavior of `||`. ``` console.log(0 || 100); diff --git a/02_program_structure.md b/02_program_structure.md index 71c19e4ef..7f9db00e2 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -629,7 +629,7 @@ Functions are special values that encapsulate a piece of program. You can invoke {{index exercises}} -If you are unsure how to test your solutions to the exercises, refer to the [Introduction](intro). +If you are unsure how to test your solutions to the exercises, refer to the [introduction](intro). Each exercise starts with a problem description. Read this description and try to solve the exercise. If you run into problems, consider reading the hints [after the exercise]{if interactive}[at the [end of the book](hints)]{if book}. You can find full solutions to the exercises online at [_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code#2). If you want to learn something from the exercises, I recommend looking at the solutions only after you've solved the exercise, or at least after you've attacked it long and hard enough to have a slight headache. diff --git a/03_functions.md b/03_functions.md index 505554fb7..816ed348a 100644 --- a/03_functions.md +++ b/03_functions.md @@ -351,7 +351,7 @@ console.log("C", "O", 2); {{index "call stack", "local binding", [function, "as value"], scope}} -The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question: what happens to local bindings when the function call that created them is no longer active? +The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question: What happens to local bindings when the function call that created them is no longer active? The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding. @@ -371,7 +371,7 @@ console.log(wrap2()); This is allowed and works as you'd hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls don't affect each other's local bindings. -This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about the lifetimes of bindings, it also makes it possible to use function values in some creative ways. +This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about the lifetimes of bindings but also makes it possible to use function values in some creative ways. {{index "multiplier function"}} @@ -620,7 +620,7 @@ The first helper function in the ((farm example)), `printZeroPaddedWithLabel`, i {{index substitution}} -A _pure_ function is a specific kind of value-producing function that not only has no side effects, but also doesn't rely on side effects from other code—for example, it doesn't read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn't do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test. +A _pure_ function is a specific kind of value-producing function that not only has no side effects but also doesn't rely on side effects from other code—for example, it doesn't read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn't do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test. {{index optimization, "console.log"}} diff --git a/04_data.md b/04_data.md index 5ce3b4c0c..313e597b4 100644 --- a/04_data.md +++ b/04_data.md @@ -163,7 +163,7 @@ Back to the weresquirrel. A set of daily log entries can be represented as an ar {{index [syntax, object], [property, definition], [braces, object], "{} (object)"}} -Values of the type _((object))_ are arbitrary collections of properties. One way to create an object is by using braces as an expression: +Values of the type _((object))_ are arbitrary collections of properties. One way to create an object is by using braces as an expression. ``` let day1 = { diff --git a/05_higher_order.md b/05_higher_order.md index 17465d2ee..8a37a8dd8 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -216,7 +216,7 @@ Though I can fluently read only Latin characters, I appreciate the fact that peo {{index "SCRIPTS dataset"}} -The example ((dataset)) contains some pieces of information about the 140 scripts defined in Unicode. It is available in the [coding sandbox](https://eloquentjavascript.net/code#5) for this chapter[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} as the `SCRIPTS` binding. The binding contains an array of objects, each of which describes a script: +The example ((dataset)) contains some pieces of information about the 140 scripts defined in Unicode. It is available in the [coding sandbox](https://eloquentjavascript.net/code#5) for this chapter[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} as the `SCRIPTS` binding. The binding contains an array of objects, each of which describes a script. ```{lang: "json"} @@ -361,7 +361,7 @@ The Han script has more than 89,000 characters assigned to it in the Unicode sta {{index loop, maximum}} -Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse: +Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse. ```{test: no} let biggest = null; @@ -381,7 +381,7 @@ There are a few more bindings, and the program is four lines longer, but it is s {{id average_function}} -The abstractions these functions provide really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the dataset: +The abstractions these functions provide really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the dataset. ``` function average(array) { @@ -398,7 +398,7 @@ console.log(Math.round(average( As you can see, the dead scripts in Unicode are, on average, older than the living ones. This is not a terribly meaningful or surprising statistic. But I hope you'll agree that the code used to compute it isn't hard to read. You can see it as a pipeline: we start with all scripts, filter out the living (or dead) ones, take the years from those, average them, and round the result. -You could definitely also write this computation as one big ((loop)): +You could definitely also write this computation as one big ((loop)). ``` let total = 0, count = 0; @@ -479,7 +479,7 @@ JavaScript's `charCodeAt` method gives you a code unit, not a full character cod {{index "for/of loop", character}} -In the [previous chapter](data#for_of_loop), I mentioned that a `for`/`of` loop can also be used on strings. Like `codePointAt`, this type of loop was introduced at a time when people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units: +In the [previous chapter](data#for_of_loop), I mentioned that a `for`/`of` loop can also be used on strings. Like `codePointAt`, this type of loop was introduced at a time when people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units. ``` let roseDragon = "🌹🐉"; @@ -525,7 +525,7 @@ It uses another array method, `find`, which goes over the elements in the array {{index "textScripts function", "Chinese characters"}} -Using `countBy`, we can write the function that tells us which scripts are used in a piece of text: +Using `countBy`, we can write the function that tells us which scripts are used in a piece of text. ```{includeCode: strip_log, startCode: true} function textScripts(text) { diff --git a/06_object.md b/06_object.md index b9c1ba604..86b92bd50 100644 --- a/06_object.md +++ b/06_object.md @@ -12,9 +12,7 @@ quote}} {{figure {url: "img/chapter_picture_6.jpg", alt: "Illustration of a rabbit next to its prototype, a schematic representation of a rabbit", chapter: framed}}} -[Chapter ?](data) introduced JavaScript's objects as containers that hold other data. - -In programming culture, _((object-oriented programming))_ is a set of techniques that use objects as the central principle of program organization. Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter describes the way these ideas can be applied in JavaScript. +[Chapter ?](data) introduced JavaScript's objects as containers that hold other data. In programming culture, _((object-oriented programming))_ is a set of techniques that use objects as the central principle of program organization. Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter describes the way these ideas can be applied in JavaScript. ## Abstract Data Types diff --git a/08_error.md b/08_error.md index 39bbba633..f191c9ae6 100644 --- a/08_error.md +++ b/08_error.md @@ -129,7 +129,7 @@ In this book, we will continue using raw, dangerous, untyped JavaScript code. If the language is not going to do much to help us find mistakes, we'll have to find them the hard way: by running the program and seeing whether it does the right thing. -Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective, since it takes too much time to exhaustively test everything every time you make a change. +Doing this by hand, again and again, is a really bad idea. Not only is it annoying but it also tends to be ineffective, since it takes too much time to exhaustively test everything every time you make a change. Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice rather than randomly running into it at some later time. @@ -442,7 +442,7 @@ for (;;) { {{index "infinite loop", "for loop", "catch keyword", debugging}} -The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. Unfortunately, we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it "buries" the useful error message about the misspelled binding. +The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. Unfortunately, we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop but it also "buries" the useful error message about the misspelled binding. As a general rule, don't blanket-catch exceptions unless it is for the purpose of "routing" them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information. diff --git a/10_modules.md b/10_modules.md index 238cf2b48..9858cb2d7 100644 --- a/10_modules.md +++ b/10_modules.md @@ -2,7 +2,7 @@ # Modules -{{quote {author: "Tef", title: "Programming is Terrible", chapter: true} +{{quote {author: "Tef", title: "programming is terrible", chapter: true} Write code that is easy to delete, not easy to extend. diff --git a/13_browser.md b/13_browser.md index 6bd717d64..7d040e243 100644 --- a/13_browser.md +++ b/13_browser.md @@ -64,7 +64,7 @@ Such a connection acts as a two-way ((pipe)) through which bits can flow—the m ## The Web -The _((World Wide Web))_ (not to be confused with the ((internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. "Web" refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. +The _((World Wide Web))_ (not to be confused with the ((internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. The word _Web_ refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. To become part of the web, all you need to do is connect a machine to the ((internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. @@ -96,7 +96,7 @@ If you type this URL into your browser's ((address bar)), the browser will try t {{indexsee "HyperText Markup Language", HTML}} -_HTML_, which stands for _HyperText Markup Language_, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. +_HTML_, which stands for HyperText Markup Language, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. A short HTML document might look like this: diff --git a/14_dom.md b/14_dom.md index 63492ba52..aa7380826 100644 --- a/14_dom.md +++ b/14_dom.md @@ -217,7 +217,7 @@ The `replaceChild` method is used to replace a child node with another one. It t {{index "alt attribute", "img (HTML tag)", "createTextNode method"}} -Say we want to write a script that replaces all ((image))s (`<img>` tags) in the document with the text held in their `alt` attributes, which specifies an alternative textual representation of the image. This involves not only removing the images but adding a new text node to replace them. +Say we want to write a script that replaces all ((image))s (`<img>` tags) in the document with the text held in their `alt` attributes, which specifies an alternative textual representation of the image. This involves not only removing the images but also adding a new text node to replace them. ```{lang: html} <p>The <img src="img/cat.png" alt="Cat"> in the @@ -640,7 +640,7 @@ Moving in ((circle))s is done using the trigonometry functions `Math.cos` and `M {{index coordinates, pi}} -`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0,0) with a radius of 1. Both functions interpret their argument as the position on this circle, with 0 denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position, and `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same ((angle)) as _a_. +`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0, 0) with a radius of 1. Both functions interpret their argument as the position on this circle, with 0 denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position, and `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same ((angle)) as _a_. {{index "PI constant"}} diff --git a/17_canvas.md b/17_canvas.md index 43dee7358..096bac537 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -94,7 +94,7 @@ You create a ((context)) with the `getContext` method on the `<canvas>` DOM elem </script> ``` -After creating the context object, the example draws a red ((rectangle)) that is 100 ((pixel))s wide and 50 pixels high, with its upper-left corner at coordinates (10,10). +After creating the context object, the example draws a red ((rectangle)) that is 100 ((pixel))s wide and 50 pixels high, with its upper-left corner at coordinates (10, 10). {{if book @@ -104,7 +104,7 @@ if}} {{index SVG, coordinates}} -Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the upper-left corner, and the positive y-((axis)) goes down from there. This means (10,10) is 10 pixels below and to the right of the upper-left corner. +Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0, 0) at the upper-left corner, and the positive y-((axis)) goes down from there. This means (10, 10) is 10 pixels below and to the right of the upper-left corner. {{id fill_stroke}} @@ -228,7 +228,7 @@ The `quadraticCurveTo` method draws a curve to a given point. To determine the c let cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); - // control=(60,10) goal=(90,90) + // control=(60, 10) goal=(90, 90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); @@ -246,7 +246,7 @@ if}} {{index "stroke method"}} -We draw a ((quadratic curve)) from the left to the right, with (60,10) as the control point, and then draw two ((line)) segments going through that control point and back to the start of the line. The result somewhat resembles a _((Star Trek))_ insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then ((curve)) toward their target. +We draw a ((quadratic curve)) from the left to the right, with (60, 10) as the control point, and then draw two ((line)) segments going through that control point and back to the start of the line. The result somewhat resembles a _((Star Trek))_ insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then ((curve)) toward their target. {{index canvas, "bezierCurveTo method"}} @@ -258,7 +258,7 @@ The `bezierCurveTo` method draws a similar kind of curve. Instead of a single (( let cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); - // control1=(10,10) control2=(90,10) goal=(50,90) + // control1=(10, 10) control2=(90, 10) goal=(50, 90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); @@ -292,9 +292,9 @@ Those last two parameters make it possible to draw only part of the circle. The <script> let cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); - // center=(50,50) radius=40 angle=0 to 7 + // center=(50, 50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); - // center=(150,50) radius=40 angle=0 to ½π + // center=(150, 50) radius=40 angle=0 to ½π cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script> @@ -502,7 +502,7 @@ if}} {{index mirroring}} -Scaling will cause everything about the drawn image, including the ((line width)), to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at _x_ position 100 will end up at what used to be position -100. +Scaling will cause everything about the drawn image, including the ((line width)), to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0, 0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at _x_ position 100 will end up at what used to be position -100. {{index "drawImage method"}} @@ -514,13 +514,13 @@ There are several other methods besides `scale` that influence the coordinate sy {{index "rotate method", "translate method"}} -If we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50,50). +If we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50, 50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50, 50). {{figure {url: "img/transform.svg", alt: "Diagram showing the result of stacking transformations. The first diagram translates and then rotates, causing the translation to happen normally and rotation to happen around the target of the translation. The second diagram first rotates, and then translates, causing the rotation to happen around the origin and the translation direction to be tilted by that rotation.", width: "9cm"}}} {{index coordinates}} -But if we _first_ rotate by 20 degrees and _then_ translate by (50,50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters. +But if we _first_ rotate by 20 degrees and _then_ translate by (50, 50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters. {{index axis, mirroring}} @@ -544,7 +544,7 @@ We move the y-((axis)) to where we want our ((mirror)) to be, apply the mirrorin This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive _x_ position, it would, by default, be in the place where triangle 1 is. A call to `flipHorizontally` first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second `translate` call fixes this—it "cancels" the initial translation and makes triangle 4 appear exactly where it should. -We can now draw a mirrored character at position (100,0) by flipping the world around the character's vertical center. +We can now draw a mirrored character at position (100, 0) by flipping the world around the character's vertical center. ```{lang: html} <canvas></canvas> @@ -820,7 +820,7 @@ When ((drawing)) something that is not the ((player)), we look at its type to fi {{index viewport}} -We have to subtract the viewport's position when computing the actor's position, since (0,0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works. +We have to subtract the viewport's position when computing the actor's position, since (0, 0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works. {{if interactive @@ -943,7 +943,7 @@ The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable center coor {{index "flipHorizontally function", rotation}} -The ((diamond)) (2) can be drawn the straightforward way, with a path, or the interesting way, with a `rotate` ((transformation)). To use rotation, you will have to apply a trick similar to what we did in the `flipHorizontally` function. Because you want to rotate around the center of your rectangle and not around the point (0,0), you must first `translate` to there, then rotate, and then translate back. +The ((diamond)) (2) can be drawn the straightforward way, with a path, or the interesting way, with a `rotate` ((transformation)). To use rotation, you will have to apply a trick similar to what we did in the `flipHorizontally` function. Because you want to rotate around the center of your rectangle and not around the point (0, 0), you must first `translate` to there, then rotate, and then translate back. Make sure you reset the transformation after drawing any shape that creates one. From 39b27388a7c7e4555a722eb0a583e072ba8a69cc Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 24 Jul 2024 21:00:49 +0200 Subject: [PATCH 378/392] Remove unused import --- 20_node.md | 1 - 1 file changed, 1 deletion(-) diff --git a/20_node.md b/20_node.md index ba67c09c6..00f08286f 100644 --- a/20_node.md +++ b/20_node.md @@ -474,7 +474,6 @@ When the value of `body` is a ((readable stream)), it will have a `pipe` method To figure out which file path corresponds to a request URL, the `urlPath` function uses the built-in `URL` class (which also exists in the browser) to parse the URL. This constructor expects a full URL, not just the part starting with the slash that we get from `request.url`, so we give it a dummy domain name to fill in. It extracts its pathname, which will be something like `"/file.txt"`, decodes that to get rid of the `%20`-style escape codes, and resolves it relative to the program's working directory. ```{includeCode: ">code/file_server.mjs"} -import {parse} from "node:url"; import {resolve, sep} from "node:path"; const baseDirectory = process.cwd(); From 38a0e5f489df90ed4b1dd6ec0591ca8e3200165f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Thu, 1 Aug 2024 08:17:00 +0200 Subject: [PATCH 379/392] Minor index tweaks --- 01_values.md | 2 +- 04_data.md | 2 -- 21_skillsharing.md | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/01_values.md b/01_values.md index 598216994..bbf9de95a 100644 --- a/01_values.md +++ b/01_values.md @@ -20,7 +20,7 @@ In the computer's world, there is only data. You can read data, modify data, cre _Bits_ are any kind of two-valued things, usually described as zeros and ones. Inside the computer, they take forms such as a high or low electrical charge, a strong or weak signal, or a shiny or dull spot on the surface of a CD. Any piece of discrete information can be reduced to a sequence of zeros and ones and thus represented in bits. -{{index "binary number", radix, "decimal number"}} +{{index "binary number", "decimal number"}} For example, we can express the number 13 in bits. This works the same way as a decimal number, but instead of 10 different ((digit))s, we have only 2, and the weight of each increases by a factor of 2 from right to left. Here are the bits that make up the number 13, with the weights of the digits shown below them: diff --git a/04_data.md b/04_data.md index 313e597b4..51541b8d2 100644 --- a/04_data.md +++ b/04_data.md @@ -1013,8 +1013,6 @@ Building up an array is most easily done by first initializing a binding to `[]` Since the end boundary is inclusive, you'll need to use the `<=` operator rather than `<` to check for the end of your loop. -{{index "arguments object"}} - The step parameter can be an optional parameter that defaults (using the `=` operator) to 1. {{index "range function", "for loop"}} diff --git a/21_skillsharing.md b/21_skillsharing.md index 147f6f036..1c8456fb3 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -40,7 +40,7 @@ A common solution to this problem is called _((long polling))_, which happens to ## Long polling -{{index firewall, notification, "long polling", network, [browser, security]}} +{{index notification, "long polling", network, [browser, security]}} To be able to immediately notify a client that something changed, we need a ((connection)) to that client. Since web browsers do not traditionally accept connections and clients are often behind ((router))s that would block such connections anyway, having the server initiate this connection is not practical. From c60157720b513fd8477a14f47a778775ebf38c1d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 6 Aug 2024 07:36:35 +0200 Subject: [PATCH 380/392] Changes made as part of final print sign-off --- 00_intro.md | 2 +- 03_functions.md | 2 +- 09_regexp.md | 2 +- 10_modules.md | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/00_intro.md b/00_intro.md index 814f5c372..1d14eef57 100644 --- a/00_intro.md +++ b/00_intro.md @@ -197,7 +197,7 @@ This flexibility also has its advantages, though. It leaves room for techniques There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version during JavaScript's ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on version 4 was abandoned in 2008. A much less ambitious version 5, which made only some uncontroversial improvements, came out in 2009. In 2015, version 6 came out, a major update that included some of the ideas planned for version 4. Since then we've had new, small updates every year. -The fact that JavaScript is evolving means that browsers have to constantly keep up. If you're using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I'm using the 2023 version of JavaScript. +The fact that JavaScript is evolving means that browsers have to constantly keep up. If you're using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I'm using the 2024 version of JavaScript. {{index [JavaScript, "uses of"]}} diff --git a/03_functions.md b/03_functions.md index 816ed348a..9f5ce287f 100644 --- a/03_functions.md +++ b/03_functions.md @@ -165,7 +165,7 @@ if (safeMode) { {{index [function, "higher-order"]}} -In [Chapter ?](higher_order), we'll discuss the interesting things that we can do by passing around function values to other functions. +In [Chapter ?](higher_order), we'll discuss the interesting things that we can do by passing function values to other functions. ## Declaration notation diff --git a/09_regexp.md b/09_regexp.md index 03337a6e6..02eae5bbb 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -950,7 +950,7 @@ if}} {{index "quoting style (exercise)", boundary}} -The most obvious solution is to replace only quotes with a nonletter character on at least one side—something like `/\P{L}'|'\P{L}/`. But you also have to take the start and end of the line into account. +The most obvious solution is to replace only quotes with a nonletter character on at least one side—something like `/\P{L}'|'\P{L}/u`. But you also have to take the start and end of the line into account. {{index grouping, "replace method", [parentheses, "in regular expressions"]}} diff --git a/10_modules.md b/10_modules.md index 9858cb2d7..0f253a7e7 100644 --- a/10_modules.md +++ b/10_modules.md @@ -414,15 +414,15 @@ Here's what I would have done (but again, there is no single _right_ way to desi {{index "dijkstrajs package"}} -The code used to build the road graph lives in the `graph` module. Because I'd rather use `dijkstrajs` from NPM than our own pathfinding code, we'll make this build the kind of graph data that `dijkstrajs` expects. This module exports a single function, `buildGraph`. I'd have `buildGraph` accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format. +The code used to build the road graph lives in the `graph.js` module. Because I'd rather use `dijkstrajs` from NPM than our own pathfinding code, we'll make this build the kind of graph data that `dijkstrajs` expects. This module exports a single function, `buildGraph`. I'd have `buildGraph` accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format. -The `roads` module contains the raw road data (the `roads` array) and the `roadGraph` binding. This module depends on `./graph.js` and exports the road graph. +The `roads.js` module contains the raw road data (the `roads` array) and the `roadGraph` binding. This module depends on `./graph.js` and exports the road graph. {{index "random-item package"}} -The `VillageState` class lives in the `state` module. It depends on the `./roads` module because it needs to be able to verify that a given road exists. It also needs `randomPick`. Since that is a three-line function, we could just put it into the `state` module as an internal helper function. But `randomRobot` needs it too. So we'd have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the `random-item` package, a reasonable solution is to just make both modules depend on that. We can add the `runRobot` function to this module as well, since it's small and closely related to state management. The module exports both the `VillageState` class and the `runRobot` function. +The `VillageState` class lives in the `state.js` module. It depends on the `./roads.js` module because it needs to be able to verify that a given road exists. It also needs `randomPick`. Since that is a three-line function, we could just put it into the `state.js` module as an internal helper function. But `randomRobot` needs it too. So we'd have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the `random-item` package, a reasonable solution is to just make both modules depend on that. We can add the `runRobot` function to this module as well, since it's small and closely related to state management. The module exports both the `VillageState` class and the `runRobot` function. -Finally, the robots, along with the values they depend on, such as `mailRoute`, could go into an `example-robots` module, which depends on `./roads` and exports the robot functions. To make it possible for `goalOrientedRobot` to do route-finding, this module also depends on `dijkstrajs`. +Finally, the robots, along with the values they depend on, such as `mailRoute`, could go into an `example-robots.js` module, which depends on `./roads.js` and exports the robot functions. To make it possible for `goalOrientedRobot` to do route-finding, this module also depends on `dijkstrajs`. By offloading some work to ((NPM)) modules, the code became a little smaller. Each individual module does something rather simple and can be read on its own. Dividing code into modules also often suggests further improvements to the program's design. In this case, it seems a little odd that the `VillageState` and the robots depend on a specific road graph. It might be a better idea to make the graph an argument to the state's constructor and make the robots read it from the state object—this reduces dependencies (which is always good) and makes it possible to run simulations on different maps (which is even better). From b83b6815fc12dfd0d15829765c7071870b841b6b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 6 Aug 2024 07:53:13 +0200 Subject: [PATCH 381/392] Use NoStarch's cover image --- img/cover.jpg | Bin 95097 -> 53251 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/cover.jpg b/img/cover.jpg index a24f31a896f8dacf8ec6de44387dab4cccad45a2..36ac9e8502b8dd2e1197955059c8154c4af3e125 100644 GIT binary patch literal 53251 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 literal 95097 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¬+JP`Cif-ma&H|Lqwt0q1` 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 From 7908784ab5309803ef2adffd3f215be241f568cb Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 6 Aug 2024 07:54:49 +0200 Subject: [PATCH 382/392] Link to 4th edition NoStarch site --- html/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html/index.html b/html/index.html index f6e309b7b..970c32d8c 100644 --- a/html/index.html +++ b/html/index.html @@ -22,7 +22,7 @@ <div id=cover> <p style="margin: 1em 0"> - <a href="https://nostarch.com/ejs3"> + <a href="https://nostarch.com/eloquent-javascript-4th-edition"> <img src="img/cover.jpg" alt="Cover image" style="border: 1px solid black; max-width: 100%; box-shadow: 4px 4px 7px rgba(0, 0, 0, 0.4)"> </a> </p> @@ -31,7 +31,7 @@ <h1>Eloquent JavaScript<div style="font-size: 70%">4th edition (2024)</div></h2> <p>This is a book about JavaScript, programming, and the wonders of the digital. You can read it online here, or buy your own - <a href="https://nostarch.com/ejs3">paperback copy</a> (3rd edition).</p> + <a href="https://nostarch.com/eloquent-javascript-4th-edition">paperback copy</a>.</p> <p>Written by Marijn Haverbeke.</p> @@ -98,7 +98,7 @@ <h3>(Part 3: Node)</h3> <p>A paper version of Eloquent JavaScript, including an additional chapter, is being brought out - by <a href="http://www.nostarch.com/ejs3">No Starch Press</a>.</p> + by <a href="https://nostarch.com/eloquent-javascript-4th-edition">No Starch Press</a>.</p> <h2>Other pages</h2> From bc1ca5ed8ea30b0399d011df5b4c03caca872b8c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 27 Aug 2024 06:46:10 +0200 Subject: [PATCH 383/392] Add note about machine translations to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index dfda90912..25409d27e 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,6 @@ to the same language (and possibly never finish it). (Since translations have to retain the license, it is okay to pick up someone else's translation and continue it, even when they have vanished from the internet.) + +I am not interested in machine translations. Please only ask me to +link your translation when it was done by actual people. From 085d6695811b09eb887ad89f6c3f65626c8c079d Mon Sep 17 00:00:00 2001 From: Sebastian Romero <8213217+sebastianromero@users.noreply.github.com> Date: Sun, 25 Aug 2024 18:55:25 -0400 Subject: [PATCH 384/392] Minify SVG code and fix rendering issue Resolved SVG rendering issues on Kindle 10th generation --- img/controlflow-if.svg | 171 +++-------------------------- img/controlflow-loop.svg | 162 +++------------------------- img/controlflow-nested-if.svg | 196 ++++------------------------------ img/controlflow-straight.svg | 90 ++-------------- 4 files changed, 61 insertions(+), 558 deletions(-) diff --git a/img/controlflow-if.svg b/img/controlflow-if.svg index 618e410e6..a0f793a4a 100644 --- a/img/controlflow-if.svg +++ b/img/controlflow-if.svg @@ -1,155 +1,16 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="205.10103" - height="85.750778" - id="svg3283" - version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="New document 11"> - <defs - id="defs3285"> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="Arrow1Send" - style="overflow:visible"> - <path - id="path3774" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="marker3266" - style="overflow:visible"> - <path - id="path3268" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="marker3270" - style="overflow:visible"> - <path - id="path3272" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="marker3274" - style="overflow:visible"> - <path - id="path3276" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.35" - inkscape:cx="1288.581" - inkscape:cy="-271.41478" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="0" - fit-margin-bottom="0" - inkscape:window-width="1600" - inkscape:window-height="875" - inkscape:window-x="0" - inkscape:window-y="25" - inkscape:window-maximized="0" /> - <metadata - id="metadata3288"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(913.58095,-175.19662)"> - <g - id="g3322" - transform="matrix(0,-1,1,0,-1032.3622,-596.20924)"> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m -815.78349,118.91917 1.01015,27.77919" - id="path4943" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m -807.70227,168.41664 c 0,0 67.6802,38.3858 7.07106,95.9645" - id="path5139" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path5141" - d="m -813.76319,274.48267 1.01015,35.35534" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path5159" - d="m -820.86915,168.41664 c 0,0 -67.68021,38.3858 -7.07106,95.9645" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - transform="translate(-888.97485,115.42425)" - d="m 80.307123,50.755318 c 0,3.347351 -2.713564,6.060915 -6.060915,6.060915 -3.347351,0 -6.060915,-2.713564 -6.060915,-6.060915 0,-3.347351 2.713564,-6.060915 6.060915,-6.060915 3.347351,0 6.060915,2.713564 6.060915,6.060915 z" - sodipodi:ry="6.060915" - sodipodi:rx="6.060915" - sodipodi:cy="50.755318" - sodipodi:cx="74.246208" - id="path3001" - style="fill:#ff0000;fill-opacity:1;stroke:none" - sodipodi:type="arc" /> - </g> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="205.101" height="85.751"> + <defs> + <marker id="a" orient="auto" overflow="visible" refX="0" refY="0"> + <path fill-rule="evenodd" stroke="#000" stroke-width=".2pt" d="m-1.2 0-1 1 3.5-1-3.5-1 1 1z" /> + </marker> + </defs> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="m-815.783 118.92 1.01 27.778" + transform="rotate(-90 -445.094 -326.312)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="M-807.702 168.417s67.68 38.385 7.07 95.964" + transform="rotate(-90 -445.094 -326.312)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="m-813.763 274.483 1.01 35.355" + transform="rotate(-90 -445.094 -326.312)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="M-820.87 168.417s-67.68 38.385-7.07 95.964" + transform="rotate(-90 -445.094 -326.312)" /> + <path fill="red" d="M47.398 37.262a6.06 6.06 0 1 1 0 12.122 6.06 6.06 0 0 1 0-12.122z" /> +</svg> \ No newline at end of file diff --git a/img/controlflow-loop.svg b/img/controlflow-loop.svg index b4ffe3f2f..290b771a0 100644 --- a/img/controlflow-loop.svg +++ b/img/controlflow-loop.svg @@ -1,147 +1,15 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="206.31815" - height="72.428215" - id="svg2" - version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="controlflow-loop.svg"> - <defs - id="defs4"> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="Arrow1Send" - style="overflow:visible"> - <path - id="path3774" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow2Mend" - orient="auto" - refY="0" - refX="0" - id="Arrow2Mend" - style="overflow:visible"> - <path - id="path3786" - style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" - d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" - transform="scale(-0.6,-0.6)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Mend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Mend" - style="overflow:visible"> - <path - id="path3768" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.4,0,0,-0.4,-4,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Lend" - style="overflow:visible"> - <path - id="path3762" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="2.8" - inkscape:cx="30.225462" - inkscape:cy="52.400366" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - showguides="true" - inkscape:guide-bbox="true" - inkscape:window-width="1600" - inkscape:window-height="875" - inkscape:window-x="0" - inkscape:window-y="25" - inkscape:window-maximized="0" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="0" - fit-margin-bottom="0" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(-376.5106,-433.43911)"> - <path - sodipodi:nodetypes="csc" - inkscape:connector-curvature="0" - id="path5161" - d="m 478.22234,451.00514 c 29.28572,23.75 27.61731,51.59627 -8.71166,51.57646 -36.44966,-0.0199 -18.10635,-25.27225 -9.3957,-39.5054" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 379.43917,445.98075 73.1512,-1.01015" - id="path3003" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path3005" - d="m 482.59676,443.96045 87.76125,-1.01015" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - transform="matrix(0,-1,1,0,422.09693,518.66704)" - d="m 80.307123,50.755318 c 0,3.347351 -2.713564,6.060915 -6.060915,6.060915 -3.347351,0 -6.060915,-2.713564 -6.060915,-6.060915 0,-3.347351 2.713564,-6.060915 6.060915,-6.060915 3.347351,0 6.060915,2.713564 6.060915,6.060915 z" - sodipodi:ry="6.060915" - sodipodi:rx="6.060915" - sodipodi:cy="50.755318" - sodipodi:cx="74.246208" - id="path3004" - style="fill:#ff0000;fill-opacity:1;stroke:none" - sodipodi:type="arc" /> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="206.318" height="72.428"> + <defs> + <marker id="a" orient="auto" overflow="visible" refX="0" refY="0"> + <path fill-rule="evenodd" stroke="#000" stroke-width=".2pt" d="m-1.2 0-1 1 3.5-1-3.5-1 1 1z" /> + </marker> + </defs> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" + d="M478.222 451.005c29.286 23.75 27.618 51.596-8.711 51.577-36.45-.02-18.107-25.273-9.396-39.506" + transform="translate(-376.51 -433.44)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="m379.44 445.98 73.15-1.01" + transform="translate(-376.51 -433.44)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="m482.597 443.96 87.761-1.01" + transform="translate(-376.51 -433.44)" /> + <path fill="red" d="M96.342 4.92a6.06 6.06 0 1 1 0 12.123 6.06 6.06 0 0 1 0-12.122z" /> +</svg> \ No newline at end of file diff --git a/img/controlflow-nested-if.svg b/img/controlflow-nested-if.svg index f920ba22e..9ab41a8aa 100644 --- a/img/controlflow-nested-if.svg +++ b/img/controlflow-nested-if.svg @@ -1,175 +1,21 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="238.24316" - height="104.35892" - id="svg2" - version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="controlflow-nested-else.svg"> - <defs - id="defs4"> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="Arrow1Send" - style="overflow:visible"> - <path - id="path3774" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow2Mend" - orient="auto" - refY="0" - refX="0" - id="Arrow2Mend" - style="overflow:visible"> - <path - id="path3786" - style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" - d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" - transform="scale(-0.6,-0.6)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Mend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Mend" - style="overflow:visible"> - <path - id="path3768" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.4,0,0,-0.4,-4,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Lend" - style="overflow:visible"> - <path - id="path3762" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="2.8" - inkscape:cx="182.61224" - inkscape:cy="45.356254" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - showguides="true" - inkscape:guide-bbox="true" - inkscape:window-width="1600" - inkscape:window-height="875" - inkscape:window-x="0" - inkscape:window-y="25" - inkscape:window-maximized="0" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="3" - fit-margin-bottom="0" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(-222.69525,-394.46429)"> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path4943" - d="m 226.69525,431.70744 27.77919,1.01015" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path5139" - d="m 274.76415,441.21724 c 2.14286,43.57143 36.60008,44.46591 47.39307,47.42819" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 411.18732,433.72774 35.35534,1.01015" - id="path5141" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 278.69272,425.19321 c 0,0 60.8858,-56.96593 122.75021,-4.57106" - id="path5159" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:type="arc" - style="fill:#ff0000;fill-opacity:1;stroke:none" - id="path3001" - sodipodi:cx="74.246208" - sodipodi:cy="50.755318" - sodipodi:rx="6.060915" - sodipodi:ry="6.060915" - d="m 80.307123,50.755318 a 6.060915,6.060915 0 1 1 -12.12183,0 6.060915,6.060915 0 1 1 12.12183,0 z" - transform="matrix(0,1,1,0,223.20033,358.51608)" /> - <path - transform="matrix(0,1,1,0,291.77176,418.51608)" - d="m 80.307123,50.755318 a 6.060915,6.060915 0 1 1 -12.12183,0 6.060915,6.060915 0 1 1 12.12183,0 z" - sodipodi:ry="6.060915" - sodipodi:rx="6.060915" - sodipodi:cy="50.755318" - sodipodi:cx="74.246208" - id="path3002" - style="fill:#ff0000;fill-opacity:1;stroke:none" - sodipodi:type="arc" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 341.54987,484.43152 c 0,0 0.96327,-38.82118 55.9645,-46.85751" - id="path3004" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path3006" - d="m 350.83558,490.14581 c 24.92879,-3.90484 44.71588,-30.84607 52.03593,-41.14323" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="238.243" height="104.359"> + <defs> + <marker id="a" orient="auto" overflow="visible" refX="0" refY="0"> + <path fill-rule="evenodd" stroke="#000" stroke-width=".2pt" d="m-1.2 0-1 1 3.5-1-3.5-1 1 1z" /> + </marker> + </defs> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="m226.695 431.707 27.78 1.01" + transform="translate(-222.695 -394.464)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" + d="M274.764 441.217c2.143 43.572 36.6 44.466 47.393 47.428" transform="translate(-222.695 -394.464)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="m411.187 433.728 35.356 1.01" + transform="translate(-222.695 -394.464)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="M278.693 425.193s60.886-56.966 122.75-4.57" + transform="translate(-222.695 -394.464)" /> + <path fill="red" + d="M51.26 44.359a6.06 6.06 0 1 1 0-12.122 6.06 6.06 0 1 1 0 12.122zM119.832 104.359a6.06 6.06 0 1 1 0-12.122 6.06 6.06 0 1 1 0 12.122z" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="M341.55 484.432s.963-38.822 55.964-46.858" + transform="translate(-222.695 -394.464)" /> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" + d="M350.836 490.146c24.928-3.905 44.715-30.846 52.036-41.143" transform="translate(-222.695 -394.464)" /> +</svg> \ No newline at end of file diff --git a/img/controlflow-straight.svg b/img/controlflow-straight.svg index 82fe554d6..f94d671e7 100644 --- a/img/controlflow-straight.svg +++ b/img/controlflow-straight.svg @@ -1,81 +1,9 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="204.48096" - height="19.999929" - id="svg3216" - version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="New document 8"> - <defs - id="defs3218"> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="Arrow1Send" - style="overflow:visible"> - <path - id="path3774" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.35" - inkscape:cx="156.78125" - inkscape:cy="-138.56231" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="0" - fit-margin-bottom="0" - inkscape:window-width="1600" - inkscape:window-height="875" - inkscape:window-x="0" - inkscape:window-y="25" - inkscape:window-maximized="0" /> - <metadata - id="metadata3221"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(-218.21875,-373.79994)"> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 218.236,384.24914 190.41375,0" - id="path5135" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="204.481" height="20"> + <defs> + <marker id="a" orient="auto" overflow="visible" refX="0" refY="0"> + <path fill-rule="evenodd" stroke="#000" stroke-width=".2pt" d="m-1.2 0-1 1 3.5-1-3.5-1 1 1z" /> + </marker> + </defs> + <path fill="none" stroke="#000" stroke-width="8" marker-end="url(#a)" d="M218.236 384.25H408.65" + transform="translate(-218.219 -373.8)" /> +</svg> \ No newline at end of file From 5377284769f475f6907af0889115fd65441ca6f3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 27 Aug 2024 07:31:47 +0200 Subject: [PATCH 385/392] Remove unused svg image --- img/controlflow-else.svg | 175 --------------------------------------- 1 file changed, 175 deletions(-) delete mode 100644 img/controlflow-else.svg diff --git a/img/controlflow-else.svg b/img/controlflow-else.svg deleted file mode 100644 index 4faaf6b12..000000000 --- a/img/controlflow-else.svg +++ /dev/null @@ -1,175 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="108.821" - height="206.31459" - id="svg2" - version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="controlflow.svg"> - <defs - id="defs4"> - <marker - inkscape:stockid="Arrow1Send" - orient="auto" - refY="0" - refX="0" - id="Arrow1Send" - style="overflow:visible"> - <path - id="path3774" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.2,0,0,-0.2,-1.2,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow2Mend" - orient="auto" - refY="0" - refX="0" - id="Arrow2Mend" - style="overflow:visible"> - <path - id="path3786" - style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" - d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" - transform="scale(-0.6,-0.6)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Mend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Mend" - style="overflow:visible"> - <path - id="path3768" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.4,0,0,-0.4,-4,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Lend" - style="overflow:visible"> - <path - id="path3762" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="2.8" - inkscape:cx="133.86544" - inkscape:cy="94.103041" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - showguides="true" - inkscape:guide-bbox="true" - inkscape:window-width="1600" - inkscape:window-height="875" - inkscape:window-x="0" - inkscape:window-y="25" - inkscape:window-maximized="0" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="0" - fit-margin-bottom="0" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(-271.44205,-341.2554)"> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path4943" - d="m 313.14729,345.2554 1.01015,27.77919" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path5139" - d="m 322.65709,393.3243 c 43.57143,2.14286 44.46591,36.60008 47.42819,47.39307" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 315.16759,500.8189 1.01015,35.35534" - id="path5141" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 306.63306,397.25287 c 0,0 -66.25164,35.8858 -5.64249,93.4645" - id="path5159" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:type="arc" - style="fill:#ff0000;fill-opacity:1;stroke:none" - id="path3001" - sodipodi:cx="74.246208" - sodipodi:cy="50.755318" - sodipodi:rx="6.060915" - sodipodi:ry="6.060915" - d="m 80.307123,50.755318 c 0,3.347351 -2.713564,6.060915 -6.060915,6.060915 -3.347351,0 -6.060915,-2.713564 -6.060915,-6.060915 0,-3.347351 2.713564,-6.060915 6.060915,-6.060915 3.347351,0 6.060915,2.713564 6.060915,6.060915 z" - transform="translate(239.95593,341.76048)" /> - <path - transform="translate(299.95593,410.33191)" - d="m 80.307123,50.755318 c 0,3.347351 -2.713564,6.060915 -6.060915,6.060915 -3.347351,0 -6.060915,-2.713564 -6.060915,-6.060915 0,-3.347351 2.713564,-6.060915 6.060915,-6.060915 3.347351,0 6.060915,2.713564 6.060915,6.060915 z" - sodipodi:ry="6.060915" - sodipodi:rx="6.060915" - sodipodi:cy="50.755318" - sodipodi:cx="74.246208" - id="path3002" - style="fill:#ff0000;fill-opacity:1;stroke:none" - sodipodi:type="arc" /> - <path - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" - d="m 365.87137,460.11002 c -16.78571,-0.35714 -26.85751,1.32164 -41.50037,27.75021" - id="path3004" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path3006" - d="m 371.58566,469.39573 c -3.92858,25 -28.64323,27.39307 -37.21466,29.53593" - style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> - </g> -</svg> From f16482fa5e14b57ab5f26d09667adf7d3661d963 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Fri, 15 Nov 2024 17:06:20 +0100 Subject: [PATCH 386/392] Tweak a game level --- code/levels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/levels.js b/code/levels.js index 1695cc7d4..cfe887b0c 100644 --- a/code/levels.js +++ b/code/levels.js @@ -86,7 +86,7 @@ var GAME_LEVELS = [` ++++#.#++++++#.........#+++++#.....................##++++++##..+.....................#######...................... ++++#.#++++++#.........#+++++##.......##############++++++##...+.................................................. ++++#.#++++++#.........#++++++#########++++++++++++++++++##....+.................................................. -++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....++++++++++++....................................... +++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+++++++++++++++++++++++++++++++++++++++++++++++++++ `,` .............................................................................................................. .............................................................................................................. From c6b673a745ae54b39a5a99c63f9b6137a21e6774 Mon Sep 17 00:00:00 2001 From: Hanief Utama <hanief@gmail.com> Date: Tue, 11 Feb 2025 09:47:48 +0700 Subject: [PATCH 387/392] fix third to fourth --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 25409d27e..21b36acfd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Eloquent JavaScript -These are the sources used to build the third edition of Eloquent +These are the sources used to build the fourth edition of Eloquent JavaScript (https://eloquentjavascript.net). Feedback welcome, in the form of issues and pull requests. @@ -18,7 +18,7 @@ set up is a pain): apt-get install texlive texlive-xetex fonts-inconsolata fonts-symbola texlive-lang-chinese inkscape make book.pdf - + ## Translating Translations are very much welcome. The license this book is published From c186818e5abb178f50c4c94c6a76913af36eace6 Mon Sep 17 00:00:00 2001 From: Hanief Utama <hanief@gmail.com> Date: Tue, 11 Feb 2025 10:54:21 +0700 Subject: [PATCH 388/392] remove spaces --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21b36acfd..6702073d4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ set up is a pain): apt-get install texlive texlive-xetex fonts-inconsolata fonts-symbola texlive-lang-chinese inkscape make book.pdf - + ## Translating Translations are very much welcome. The license this book is published From dbfec288977afb8e79f34ce619a44704c0353c74 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 1 Apr 2025 09:21:33 +0200 Subject: [PATCH 389/392] Fix incorrect escaping of code backticks in LaTeX output --- src/render_latex.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/render_latex.mjs b/src/render_latex.mjs index 902a67083..70c8e1c24 100644 --- a/src/render_latex.mjs +++ b/src/render_latex.mjs @@ -45,7 +45,6 @@ function escape(str) { return escapeChar(match) }) } -function miniEscape(str) { return str.replace(/[`]/g, escapeChar) } function escapeIndexChar(ch) { switch (ch) { @@ -145,8 +144,10 @@ let renderer = { code_inline(token) { if (noStarch) return `\\texttt{${escape(token.content)}}` + else if (token.content.indexOf("`")) + return `\\lstinline|${token.content}|` else - return `\\lstinline\`${miniEscape(token.content)}\`` + return `\\lstinline\`${token.content}\`` }, strong_open() { return "\\textbf{" }, From 8d54472dc51762163aa24a40c41c5debe36ba838 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Tue, 1 Apr 2025 09:26:26 +0200 Subject: [PATCH 390/392] Fix prev fix --- src/render_latex.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render_latex.mjs b/src/render_latex.mjs index 70c8e1c24..bd1d75744 100644 --- a/src/render_latex.mjs +++ b/src/render_latex.mjs @@ -144,7 +144,7 @@ let renderer = { code_inline(token) { if (noStarch) return `\\texttt{${escape(token.content)}}` - else if (token.content.indexOf("`")) + else if (token.content.indexOf("`") > -1) return `\\lstinline|${token.content}|` else return `\\lstinline\`${token.content}\`` From 63141ec8295797cb89fbb2571c434f04b2ee4360 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Sun, 20 Apr 2025 19:52:09 +0200 Subject: [PATCH 391/392] Use the proper image path for the robot animation on the code/ page --- code/animatevillage.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/code/animatevillage.js b/code/animatevillage.js index 241d7921d..3a4f2303f 100644 --- a/code/animatevillage.js +++ b/code/animatevillage.js @@ -33,15 +33,15 @@ this.node = outer.appendChild(doc.createElement("div")) this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px" this.map = this.node.appendChild(doc.createElement("img")) - let imgPath = "img/" - if (/\/code($|\/)/.test(outer.ownerDocument.defaultView.location)) imgPath = "../" + imgPath - console.log(outer.ownerDocument.defaultView.location.toString(), /\/code($|\/)/.test(outer.ownerDocument.defaultView.localation), imgPath) - this.map.src = imgPath + "village2x.png" + this.imgPath = "img/" + if (/\/code($|\/)/.test(outer.ownerDocument.defaultView.location)) this.imgPath = "../" + this.imgPath + console.log(outer.ownerDocument.defaultView.location.toString(), /\/code($|\/)/.test(outer.ownerDocument.defaultView.localation), this.imgPath) + this.map.src = this.imgPath + "village2x.png" this.map.style.cssText = "vertical-align: -8px" this.robotElt = this.node.appendChild(doc.createElement("div")) this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;` let robotPic = this.robotElt.appendChild(doc.createElement("img")) - robotPic.src = imgPath + "robot_moving2x.gif" + robotPic.src = this.imgPath + "robot_moving2x.gif" this.parcels = [] this.text = this.node.appendChild(doc.createElement("span")) @@ -75,7 +75,7 @@ heights[place] += 14 let node = document.createElement("div") let offset = placeKeys.indexOf(address) * 16 - node.style.cssText = "position: absolute; height: 16px; width: 16px; background-image: url(img/parcel2x.png); background-position: 0 -" + offset + "px"; + node.style.cssText = `position: absolute; height: 16px; width: 16px; background-image: url(${this.imgPath}parcel2x.png); background-position: 0 -${offset}px`; if (place == this.worldState.place) { node.style.left = "25px" node.style.bottom = (20 + height) + "px" @@ -99,7 +99,7 @@ if (this.worldState.parcels.length == 0) { this.button.remove() this.text.textContent = ` Finished after ${this.turn} turns` - this.robotElt.firstChild.src = "img/robot_idle2x.png" + this.robotElt.firstChild.src = this.imgPath + "robot_idle2x.png" } else { this.schedule() } @@ -113,12 +113,12 @@ if (this.timeout == null) { this.schedule() this.button.textContent = "Stop" - this.robotElt.firstChild.src = "img/robot_moving2x.gif" + this.robotElt.firstChild.src = this.imgPath + "robot_moving2x.gif" } else { clearTimeout(this.timeout) this.timeout = null this.button.textContent = "Start" - this.robotElt.firstChild.src = "img/robot_idle2x.png" + this.robotElt.firstChild.src = this.imgPath + "robot_idle2x.png" } } } From a9401f885ba06c0e9ba5f2660ae2b39ddca625a5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.berlin> Date: Wed, 8 Oct 2025 18:18:18 +0200 Subject: [PATCH 392/392] Fix typo in Chapter 13 quote attribution --- 13_browser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/13_browser.md b/13_browser.md index 7d040e243..272c1709b 100644 --- a/13_browser.md +++ b/13_browser.md @@ -1,6 +1,6 @@ # JavaScript and the Browser -{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A Very Short Personal Pistory", chapter: true} +{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A Very Short Personal History", chapter: true} The dream behind the web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished.