diff --git a/00_intro.md b/00_intro.md index 895eaa40..1d14eef5 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. @@ -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 @@ -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)"}} @@ -110,25 +110,25 @@ 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. - Set “count” to 1. + Set “total” to 0. + Set “count” to 1. [loop] - Set “compare” to “count”. - Subtract 11 from “compare”. - If “compare” is zero, 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"}} -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: @@ -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"]}} @@ -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. diff --git a/01_values.md b/01_values.md index e973c0c3..bbf9de95 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: @@ -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") @@ -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) @@ -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,11 +418,11 @@ 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}} -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 1bd551d2..7f9db00e 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)); @@ -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 10561cb3..9f5ce287 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 @@ -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) { @@ -371,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 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"}} -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) { @@ -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"}} @@ -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. diff --git a/04_data.md b/04_data.md index 07ecfde2..51541b8d 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]; @@ -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 = { @@ -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: @@ -1013,15 +1013,13 @@ 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"}} 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}} @@ -1029,7 +1027,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"}} @@ -1057,13 +1055,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 +1121,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 +1131,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}} @@ -1149,7 +1147,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"}} @@ -1187,6 +1185,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 7701331c..8a37a8dd 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)); @@ -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"} @@ -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) { @@ -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) { @@ -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. diff --git a/06_object.md b/06_object.md index 733c1f87..86b92bd5 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 @@ -22,7 +20,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 +98,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 +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 = { @@ -210,7 +208,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 +279,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 +299,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 +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, 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 +461,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 +529,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 +663,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() { @@ -708,7 +706,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; @@ -836,7 +834,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 @@ -866,7 +864,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 24414657..3c942cfb 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. @@ -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/08_error.md b/08_error.md index b701fcec..f191c9ae 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 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/09_regexp.md b/09_regexp.md index 162df15f..02eae5bb 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 @@ -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 ae9cd89e..0f253a7e 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. @@ -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 @@ -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). diff --git a/11_async.md b/11_async.md index 36327261..ee30fd7f 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 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"}} @@ -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) { @@ -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}} @@ -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 diff --git a/12_language.md b/12_language.md index a4993539..04a28733 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}} @@ -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/13_browser.md b/13_browser.md index 1927c775..7d040e24 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. @@ -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: @@ -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 ``, ``, and `` tags are completely gone. 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. 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 diff --git a/14_dom.md b/14_dom.md index 62e10be8..aa738082 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]}} @@ -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 @@ -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,11 +636,11 @@ 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}} -`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"}} @@ -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 diff --git a/15_event.md b/15_event.md index 20cae3b1..ffb1c832 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}} diff --git a/16_game.md b/16_game.md index 0cd9d691..c41e970f 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"}} @@ -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 1e44a756..096bac53 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 @@ -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,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> @@ -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> @@ -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}} @@ -514,17 +514,17 @@ 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}} -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,9 +542,9 @@ 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. +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> @@ -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"]}} @@ -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 @@ -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 @@ -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. @@ -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}} @@ -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. diff --git a/18_http.md b/18_http.md index f37d47bb..69da0a36 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"}} @@ -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. @@ -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"}} diff --git a/19_paint.md b/19_paint.md index 7e2364ca..339378e5 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 @@ -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. @@ -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"}} @@ -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 bd85fea4..00f08286 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 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 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"}} @@ -220,13 +220,13 @@ 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 +## 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"; @@ -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(); @@ -490,7 +489,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 +613,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 +642,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 +660,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,9 +678,9 @@ 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. +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 +746,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 4d929a72..1c8456fb 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. @@ -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. @@ -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. @@ -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"}} diff --git a/README.md b/README.md index dfda9091..6702073d 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. @@ -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. diff --git a/code/animatevillage.js b/code/animatevillage.js index 241d7921..3a4f2303 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" } } } diff --git a/code/levels.js b/code/levels.js index 1695cc7d..cfe887b0 100644 --- a/code/levels.js +++ b/code/levels.js @@ -86,7 +86,7 @@ var GAME_LEVELS = [` ++++#.#++++++#.........#+++++#.....................##++++++##..+.....................#######...................... ++++#.#++++++#.........#+++++##.......##############++++++##...+.................................................. ++++#.#++++++#.........#++++++#########++++++++++++++++++##....+.................................................. -++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....++++++++++++....................................... +++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+++++++++++++++++++++++++++++++++++++++++++++++++++ `,` .............................................................................................................. .............................................................................................................. diff --git a/code/solutions/06_3_iterable_groups.js b/code/solutions/06_3_iterable_groups.js index 0dfe8f62..557a35fb 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; diff --git a/html/index.html b/html/index.html index f6e309b7..970c32d8 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> diff --git a/img/controlflow-else.svg b/img/controlflow-else.svg deleted file mode 100644 index 4faaf6b1..00000000 --- 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> diff --git a/img/controlflow-if.svg b/img/controlflow-if.svg index 618e410e..a0f793a4 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 b4ffe3f2..290b771a 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 f920ba22..9ab41a8a 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 82fe554d..f94d671e 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 diff --git a/img/cover.jpg b/img/cover.jpg index a24f31a8..36ac9e85 100644 Binary files a/img/cover.jpg and b/img/cover.jpg differ diff --git a/src/render_latex.mjs b/src/render_latex.mjs index 902a6708..bd1d7574 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("`") > -1) + return `\\lstinline|${token.content}|` else - return `\\lstinline\`${miniEscape(token.content)}\`` + return `\\lstinline\`${token.content}\`` }, strong_open() { return "\\textbf{" },