diff --git a/.gitignore b/.gitignore index f0698f3e4..b21cdbaf7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,11 +10,10 @@ /book.pdf /book_mobile.pdf /html/[012]*.html -/html/js/chapter_info.js -/html/js/[012]*.js -/html/js/acorn_codemirror.js +/html/ejs.js /code/chapter/* -/code/file_server.js +/code/chapter_info.js +/code/file_server.mjs /code/skillsharing.zip /code/solutions/20_3_a_public_space_on_the_web.zip /code/skillsharing/* diff --git a/00_intro.md b/00_intro.md index 380511d79..1d14eef57 100644 --- a/00_intro.md +++ b/00_intro.md @@ -2,153 +2,77 @@ # 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. +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. quote}} -{{figure {url: "img/chapter_picture_00.jpg", alt: "Picture of a screwdriver and a circuit board", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_00.jpg", alt: "Illustration of a screwdriver next to a circuit board of about the same size", chapter: "framed"}}} -This is a book about instructing ((computer))s. Computers are about as -common as screwdrivers today, but they are quite a bit more complex, -and making them do what you want them to do isn't always easy. +This is a book about instructing ((computer))s. Computers are about as common as screwdrivers today, but they are quite a bit more complex, and making them do what you want them to do isn't always easy. -If the task you have for your computer is a common, well-understood -one, such as showing you your email or acting like a calculator, you -can open the appropriate ((application)) and get to work. But for -unique or open-ended tasks, there probably is no application. +If the task you have for your computer is a common, well-understood one, such as showing you your email or acting like a calculator, you can open the appropriate ((application)) and get to work. But for unique or open-ended tasks, there often is no appropriate application. -That is where ((programming)) may come in. _Programming_ is the act of -constructing a _program_—a set of precise instructions telling a -computer what to do. Because computers are dumb, pedantic beasts, -programming is fundamentally tedious and frustrating. +That is where ((programming)) may come in. _Programming_ is the act of constructing a _program_—a set of precise instructions telling a computer what to do. Because computers are dumb, pedantic beasts, programming is fundamentally tedious and frustrating. {{index [programming, "joy of"], speed}} -Fortunately, if you can get over that fact, and maybe even enjoy the rigor -of thinking in terms that dumb machines can deal with, programming can -be rewarding. It allows you to do things in seconds that would take -_forever_ by hand. It is a way to make your computer tool -do things that it couldn't do before. And it provides a wonderful -exercise in abstract thinking. - -Most programming is done with ((programming language))s. A _programming -language_ is an artificially constructed language used to instruct -computers. It is interesting that the most effective way we've found -to communicate with a computer borrows so heavily from the way we -communicate with each other. Like human languages, computer languages -allow words and phrases to be combined in new ways, making it possible to -express ever new concepts. +Fortunately, if you can get over that fact—and maybe even enjoy the rigor of thinking in terms that dumb machines can deal with—programming can be rewarding. It allows you to do things in seconds that would take _forever_ by hand. It is a way to make your computer tool do things that it couldn't do before. On top of that, it makes for a wonderful game of puzzle solving and abstract thinking. + +Most programming is done with ((programming language))s. A _programming language_ is an artificially constructed language used to instruct computers. It is interesting that the most effective way we've found to communicate with a computer borrows so heavily from the way we communicate with each other. Like human languages, computer languages allow words and phrases to be combined in new ways, making it possible to express ever new concepts. {{index [JavaScript, "availability of"], "casual computing"}} -At one point language-based interfaces, such as the BASIC and DOS -prompts of the 1980s and 1990s, were the main method of interacting with -computers. They have largely been replaced with visual interfaces, -which are easier to learn but offer less freedom. Computer languages -are still there, if you know where to look. One such language, -JavaScript, is built into every modern web ((browser)) and is thus -available on almost every device. +At one point, language-based interfaces, such as the BASIC and DOS prompts of the 1980s and 1990s, were the main method of interacting with computers. For routine computer use, these have largely been replaced with visual interfaces, which are easier to learn but offer less freedom. But if you know where to look, the languages are still there. One of them, _JavaScript_, is built into every modern web ((browser))—and is thus available on almost every device. {{indexsee "web browser", browser}} -This book will try to make you familiar enough with this language to -do useful and amusing things with it. +This book will try to make you familiar enough with this language to do useful and amusing things with it. ## On programming {{index [programming, "difficulty of"]}} -Besides explaining JavaScript, I will introduce the basic -principles of programming. Programming, it turns out, is hard. The -fundamental rules are simple and clear, but programs built on top of -these rules tend to become complex enough to introduce their own rules -and complexity. You're building your own maze, in a way, and you might -just get lost in it. +Besides explaining JavaScript, I will introduce the basic principles of programming. Programming, it turns out, is hard. The fundamental rules are simple and clear, but programs built on top of these rules tend to become complex enough to introduce their own rules and complexity. You're building your own maze, in a way, and you can easily get lost in it. {{index learning}} -There will be times when reading this book feels terribly frustrating. -If you are new to programming, there will be a lot of new material to -digest. Much of this material will then be _combined_ in ways that -require you to make additional connections. +There will be times when reading this book feels terribly frustrating. If you are new to programming, there will be a lot of new material to digest. Much of this material will then be _combined_ in ways that require you to make additional connections. -It is up to you to make the necessary effort. When you are struggling -to follow the book, do not jump to any conclusions about your own -capabilities. You are fine—you just need to keep at it. Take a break, -reread some material, and make sure you read and understand the -example programs and ((exercises)). Learning is hard work, but -everything you learn is yours and will make subsequent learning -easier. +It is up to you to make the necessary effort. When you are struggling to follow the book, do not jump to any conclusions about your own capabilities. You are fine—you just need to keep at it. Take a break, reread some material, and make sure you read and understand the example programs and ((exercises)). Learning is hard work, but everything you learn is yours and will make further learning easier. {{quote {author: "Ursula K. Le Guin", title: "The Left Hand of Darkness"} {{index "Le Guin, Ursula K."}} -When action grows unprofitable, gather information; when information -grows unprofitable, sleep. +When action grows unprofitable, gather information; when information grows unprofitable, sleep. quote}} {{index [program, "nature of"], data}} -A program is many things. It is a piece of text typed by a programmer, -it is the directing force that makes the computer do what it does, it -is data in the computer's memory, yet it controls the actions -performed on this same memory. Analogies that try to compare programs -to objects we are familiar with tend to fall short. A superficially -fitting one is that of a machine—lots of separate parts tend to be -involved, and to make the whole thing tick, we have to consider the -ways in which these parts interconnect and contribute to the operation -of the whole. - -A ((computer)) is a physical machine that acts as a host for these immaterial -machines. Computers themselves can do only stupidly straightforward -things. The reason they are so useful is that they do these things at -an incredibly high ((speed)). A program can ingeniously combine an -enormous number of these simple actions to do very -complicated things. +A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer's memory, and, at the same time, it controls the actions performed on this memory. Analogies that try to compare programs to familiar objects tend to fall short. A superficially fitting one is to compare a program to a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole. -{{index [programming, "joy of"]}} +A ((computer)) is a physical machine that acts as a host for these immaterial machines. Computers themselves can do only stupidly straightforward things. The reason they are so useful is that they do these things at an incredibly high ((speed)). A program can ingeniously combine an enormous number of these simple actions to do very complicated things. -A program is a building of thought. It is costless to build, it is -weightless, and it grows easily under our typing hands. +{{index [programming, "joy of"]}} -But without care, a program's size and ((complexity)) will grow out of -control, confusing even the person who created it. Keeping programs -under control is the main problem of programming. When a program -works, it is beautiful. The art of programming is the skill of -controlling complexity. The great program is subdued—made simple in -its complexity. +A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands. But as a program grows, so does its ((complexity)). The skill of programming is the skill of building programs that don't confuse the programmer. The best programs are those that manage to do something interesting while still being easy to understand. {{index "programming style", "best practices"}} -Some programmers believe that this complexity is best managed by using -only a small set of well-understood techniques in their programs. They -have composed strict rules ("best practices") prescribing the form -programs should have and carefully stay within their safe little -zone. +Some programmers believe that this complexity is best managed by using only a small set of well-understood techniques in their programs. They have composed strict rules ("best practices") prescribing the form programs should have and carefully stay within their safe little zone. {{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 so that you -understand them. A sense of what a good program looks like is -developed in 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 {{index "programming language", "machine code", "binary data"}} -In the beginning, at the birth of computing, there were no programming -languages. Programs looked something like this: +In the beginning, at the birth of computing, there were no programming languages. Programs looked something like this: ```{lang: null} 00110001 00000000 00000000 @@ -164,33 +88,21 @@ languages. Programs looked something like this: {{index [programming, "history of"], "punch card", complexity}} -That is a program to add the numbers from 1 to 10 together and print -out 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 probably -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)"}} -Of course, manually entering these arcane patterns of bits (the ones -and zeros) did give the programmer a profound sense of being a mighty -wizard. And that has to be worth something in terms of job -satisfaction. +Of course, manually entering these arcane patterns of bits (the ones and zeros) did give the programmer a profound sense of being a mighty wizard. And that has to be worth something in terms of job satisfaction. {{index memory, instruction}} -Each line of the previous program contains a single instruction. It -could be written in English like this: +Each line of the previous program contains a single instruction. It could be written in English like this: 1. Store the number 0 in memory location 0. 2. Store the number 1 in memory location 1. 3. Store the value of memory location 1 in memory location 2. 4. Subtract the number 11 from the value in memory location 2. - 5. If the value in memory location 2 is the number 0, - continue with instruction 9. + 5. If the value in memory location 2 is the number 0, continue with instruction 9. 6. Add the value of memory location 1 to memory location 0. 7. Add the number 1 to the value of memory location 1. 8. Continue with instruction 3. @@ -198,39 +110,25 @@ could be written in English like this: {{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: "text/plain"} - Set “total” to 0. - Set “count” to 1. +```{lang: "null"} + 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 weirdest 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. So it 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 has decided 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: @@ -246,27 +144,15 @@ console.log(total); {{index "while loop", loop, [braces, block]}} -This version gives us a few more improvements. Most important, there -is no need to specify the way we want the program to jump back and -forth anymore. The `while` construct takes care of that. It continues -executing the block (wrapped in braces) below it as long as the -condition it was given holds. That condition is `count <= 10`, which -means “_count_ is less than or equal to 10”. We no longer have to -create a temporary value and compare that to zero, which was just an -uninteresting detail. Part of the power of programming languages is -that they can take care of uninteresting details for us. +This version gives us a few more improvements. Most importantly, there is no need to specify the way we want the program to jump back and forth anymore—the `while` construct takes care of that. It continues executing the block (wrapped in braces) below it as long as the condition it was given holds. That condition is `count <= 10`, which means “the count is less than or equal to 10”. We no longer have to create a temporary value and compare that to zero, which was just an uninteresting detail. Part of the power of programming languages is that they can take care of uninteresting details for us. {{index "console.log"}} -At the end of the program, after the `while` construct has finished, -the `console.log` operation is used to write out the result. +At the end of the program, after the `while` construct has finished, the `console.log` operation is used to write out the result. {{index "sum function", "range function", abstraction, function}} -Finally, here is what the program could look like if we happened to -have the convenient operations `range` and `sum` available, which -respectively create a ((collection)) of numbers within a range and -compute the sum of a collection of numbers: +Finally, here is what the program could look like if we happened to have the convenient operations `range` and `sum` available, which respectively create a ((collection)) of numbers within a range and compute the sum of a collection of numbers: ```{startCode: true} console.log(sum(range(1, 10))); @@ -275,21 +161,11 @@ console.log(sum(range(1, 10))); {{index readability}} -The moral of this story is that the same program can be expressed in -both long and short, unreadable and readable ways. The first version of the -program was extremely obscure, whereas this last one is almost -English: `log` the `sum` of the `range` of numbers from 1 to 10. (We -will see in [later chapters](data) how to define operations like `sum` -and `range`.) +The moral of this story is that the same program can be expressed in both long and short, unreadable and readable ways. The first version of the program was extremely obscure, whereas this last one is almost English: `log` the `sum` of the `range` of numbers from 1 to 10. (We will see in [later chapters](data) how to define operations like `sum` and `range`.) {{index ["programming language", "power of"], composability}} -A good programming language helps the programmer by allowing them to -talk about the actions that the computer has to perform on a higher -level. It helps omit details, provides convenient building blocks -(such as `while` and `console.log`), allows you to define your own -building blocks (such as `sum` and `range`), and makes those blocks -easy to compose. +A good programming language helps the programmer by allowing them to talk about the actions that the computer has to perform on a higher level. It helps omit details, provides convenient building blocks (such as `while` and `console.log`), allows you to define your own building blocks (such as `sum` and `range`), and makes those blocks easy to compose. ## What is JavaScript? @@ -299,111 +175,47 @@ easy to compose. {{indexsee Web, "World Wide Web"}} -JavaScript was introduced in 1995 as a way to add programs to web -pages in the Netscape Navigator browser. The language has since been -adopted by all other major graphical web browsers. It has made modern -web applications possible—applications with which you can interact -directly without doing a page reload for every action. JavaScript is also -used in more traditional websites to provide various forms of -interactivity and cleverness. +JavaScript was introduced in 1995 as a way to add programs to web pages in the Netscape Navigator browser. The language has since been adopted by all other major graphical web browsers. It has made modern web applications possible—that is, applications with which you can interact directly without doing a page reload for every action. JavaScript is also used in more traditional websites to provide various forms of interactivity and cleverness. {{index Java, naming}} -It is important to note that JavaScript has almost nothing to do with -the programming language named Java. The similar name was inspired by -marketing considerations rather than good judgment. When JavaScript -was being introduced, the Java language was being heavily marketed and -was gaining popularity. Someone thought it was a good idea to try to -ride along on this success. Now we are stuck with the name. +It is important to note that JavaScript has almost nothing to do with the programming language named Java. The similar name was inspired by marketing considerations rather than good judgment. When JavaScript was being introduced, the Java language was being heavily marketed and was gaining popularity. Someone thought it was a good idea to try to ride along on this success. Now we are stuck with the name. {{index ECMAScript, compatibility}} -After its adoption outside of Netscape, a ((standard)) document was -written to describe the way the JavaScript language should work so -that the various pieces of software that claimed to support JavaScript -were actually talking about the same language. This is called the -ECMAScript standard, after the Ecma International organization that -did the standardization. In practice, the terms ECMAScript and -JavaScript can be used interchangeably—they are two names for the same -language. +After its adoption outside of Netscape, a ((standard)) document was written to describe the way the JavaScript language should work so that the various pieces of software that claimed to support JavaScript could make sure they actually provided the same language. This is called the ECMAScript standard, after the Ecma International organization that conducted the standardization. In practice, the terms ECMAScript and JavaScript can be used interchangeably—they are two names for the same language. {{index [JavaScript, "weaknesses of"], debugging}} -There are those who will say _terrible_ things about JavaScript. Many -of these things are true. When I was required to write something in -JavaScript for the first time, I quickly came to despise it. It would -accept almost anything I typed but interpret it in a way that was -completely different from what I meant. This had a lot to do with the -fact that I did not have a clue what I was doing, of course, but there -is a real issue here: JavaScript is ridiculously liberal in what it -allows. The idea behind this design was that it would make programming -in JavaScript easier for beginners. In actuality, it mostly makes -finding problems in your programs harder because the system will not -point them out to you. +There are those who will say _terrible_ things about JavaScript. Many of these things are true. When I was required to write something in JavaScript for the first time, I quickly came to despise it. It would accept almost anything I typed but interpret it in a way that was completely different from what I meant. This had a lot to do with the fact that I did not have a clue what I was doing, of course, but there is a real issue here: JavaScript is ridiculously liberal in what it allows. The idea behind this design was that it would make programming in JavaScript easier for beginners. In actuality, it mostly makes finding problems in your programs harder because the system will not point them out to you. {{index [JavaScript, "flexibility of"], flexibility}} -This flexibility also has its advantages, though. It leaves space for -a lot of techniques that are impossible in more rigid languages, and -as you will see (for example in [Chapter ?](modules)), it can be used -to overcome some of JavaScript's shortcomings. After ((learning)) the -language properly and working with it for a while, I have learned to -actually _like_ JavaScript. +This flexibility also has its advantages, though. It leaves room for techniques that are impossible in more rigid languages and makes for a pleasant, informal style of programming. After ((learning)) the language properly and working with it for a while, I have come to actually _like_ JavaScript. {{index future, [JavaScript, "versions of"], ECMAScript, "ECMAScript 6"}} -There have been several versions of JavaScript. ECMAScript version 3 -was the widely supported version in the time of JavaScript's ascent to -dominance, roughly between 2000 and 2010. During this time, work was -underway on an ambitious version 4, which planned a number of radical -improvements and extensions to the language. Changing a living, widely -used language in such a radical way turned out to be politically -difficult, and work on the version 4 was abandoned in 2008, leading to -a much less ambitious version 5, which made only some uncontroversial -improvements, coming out in 2009. Then 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 the language is evolving means that browsers have to -constantly keep up, and 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 2017 version of -JavaScript. +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 2024 version of JavaScript. {{index [JavaScript, "uses of"]}} -Web browsers are not the only platforms on which JavaScript is used. -Some databases, such as MongoDB and CouchDB, use JavaScript as their -scripting and query language. Several platforms for desktop and server -programming, most notably the ((Node.js)) project (the subject of -[Chapter ?](node)), provide an environment for programming JavaScript -outside of the browser. +Web browsers are not the only platforms on which JavaScript is used. Some databases, such as MongoDB and CouchDB, use JavaScript as their scripting and query language. Several platforms for desktop and server programming, most notably the ((Node.js)) project (the subject of [Chapter ?](node)), provide an environment for programming JavaScript outside of the browser. ## Code, and what to do with it {{index "reading code", "writing code"}} -_Code_ is the text that makes up programs. Most chapters in this book -contain quite a lot of code. I believe reading code and writing ((code)) -are indispensable parts of ((learning)) to program. Try to not just -glance over the examples—read them attentively and understand them. -This may be slow and confusing at first, but I promise that you'll -quickly get the hang of it. The same goes for the ((exercises)). Don't -assume you understand them until you've actually written a working -solution. +_Code_ is the text that makes up programs. Most chapters in this book contain quite a lot of code. I believe reading code and writing ((code)) are indispensable parts of ((learning)) to program. Try to not just glance over the examples—read them attentively and understand them. This may be slow and confusing at first, but I promise that you'll quickly get the hang of it. The same goes for the ((exercises)). Don't assume you understand them until you've actually written a working solution. {{index interpretation}} -I recommend you try your solutions to exercises in an actual -JavaScript interpreter. That way, you'll get immediate feedback on -whether what you are doing is working, and, I hope, you'll be tempted -to ((experiment)) and go beyond the exercises. +I recommend you try your solutions to exercises in an actual JavaScript interpreter. That way, you'll get immediate feedback on whether what you are doing is working, and, I hope, you'll be tempted to ((experiment)) and go beyond the exercises. {{if interactive -When reading this book in your browser, you can edit (and run) all -example programs by clicking them. +When reading this book in your browser, you can edit (and run) all example programs by clicking them. if}} @@ -411,76 +223,29 @@ if}} {{index download, sandbox, "running code"}} -The easiest way to run the example code in the book, and to experiment -with it, is to look it up in the online version of the book at -[_https://eloquentjavascript.net_](https://eloquentjavascript.net/). There, -you can click any code example to edit and run it and to see the -output it produces. To work on the exercises, go to -[_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code), -which provides starting code for each coding exercise and allows you -to look at the solutions. +The easiest way to run the example code in the book—and to experiment with it—is to look it up in the online version of the book at [_https://eloquentjavascript.net_](https://eloquentjavascript.net/). There, you can click any code example to edit and run it and to see the output it produces. To work on the exercises, go to [_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code), which provides starting code for each coding exercise and allows you to look at the solutions. if}} {{index "developer tools", "JavaScript console"}} -If you want to run the programs defined in this book outside of the -book's website, some care will be required. Many examples stand on their -own and should work in any JavaScript environment. But code in later -chapters is often written for a specific environment (the browser or -Node.js) and can run only there. In addition, many chapters define -bigger programs, and the pieces of code that appear in them depend on -each other or on external files. The -[sandbox](https://eloquentjavascript.net/code) on the website provides -links to Zip files containing all the scripts and data files -necessary to run the code for a given chapter. +Running the programs defined in this book outside of the book's website requires some care. Many examples stand on their own and should work in any JavaScript environment. But code in later chapters is often written for a specific environment (the browser or Node.js) and can run only there. In addition, many chapters define bigger programs, and the pieces of code that appear in them depend on each other or on external files. The [sandbox](https://eloquentjavascript.net/code) on the website provides links to ZIP files containing all the scripts and data files necessary to run the code for a given chapter. ## Overview of this book -This book contains roughly three parts. The first 12 chapters discuss -the JavaScript language. The next seven chapters are about web -((browsers)) and the way JavaScript is used to program them. Finally, -two chapters are devoted to ((Node.js)), another environment to -program JavaScript in. - -Throughout the book, there are five _project chapters_, which describe -larger example programs to give you a taste of actual programming. In -order of appearance, we will work through building a [delivery -robot](robot), a [programming language](language), a [platform -game](game), a [pixel paint program](paint), and a [dynamic -website](skillsharing). - -The language part of the book starts with four chapters that introduce -the basic structure of the JavaScript language. They introduce -[control structures](program_structure) (such as the `while` word you -saw in this introduction), [functions](functions) (writing your own -building blocks), and [data structures](data). After these, you will -be able to write basic programs. Next, Chapters [?](higher_order) and -[?](object) introduce techniques to use functions and objects to write -more _abstract_ code and keep complexity under control. - -After a [first project chapter](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) concludes the first part -of the book. - -The second part, 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. - -After that, [Chapter ?](node) describes Node.js, and [Chapter -?](skillsharing) builds a small website using that tool. +This book contains roughly three parts. The first 12 chapters discuss the JavaScript language. The next seven chapters are about web ((browsers)) and the way JavaScript is used to program them. Finally, two chapters are devoted to ((Node.js)), another environment to program JavaScript in. There are five _project chapters_ in the book that describe larger example programs to give you a taste of actual programming. + +The language part of the book starts with four chapters that introduce the basic structure of the JavaScript language. They discuss [control structures](program_structure) (such as the `while` word you saw in this introduction), [functions](functions) (writing your own building blocks), and [data structures](data). After these, you will be able to write basic programs. Next, Chapters [?](higher_order) and [?](object) introduce techniques to use functions and objects to write more _abstract_ code and keep complexity under control. + +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). + +[Chapter ?](node) describes Node.js, and [Chapter ?](skillsharing) builds a small website using that tool. {{if commercial -Finally, [Chapter ?](fast) describes some of the considerations that -come up when optimizing JavaScript programs for speed. +Finally, [Chapter ?](fast) describes some of the considerations that come up when optimizing JavaScript programs for speed. if}} @@ -488,10 +253,7 @@ if}} {{index "factorial function"}} -In this book, text written in a `monospaced` font will represent -elements of programs—sometimes they are self-sufficient fragments, and -sometimes they just refer to part of a nearby program. Programs (of -which you have already seen a few) are written as follows: +In this book, text written in a `monospaced` font will represent elements of programs. Sometimes these are self-sufficient fragments, and sometimes they just refer to part of a nearby program. Programs (of which you have already seen a few) are written as follows: ``` function factorial(n) { @@ -505,9 +267,7 @@ function factorial(n) { {{index "console.log"}} -Sometimes, to show the output that a program produces, the -expected output is written after it, with two slashes and an arrow in -front. +Sometimes, to show the output that a program produces, the expected output is written after it, with two slashes and an arrow in front. ``` console.log(factorial(8)); diff --git a/01_values.md b/01_values.md index 63cb2533d..bbf9de95a 100644 --- a/01_values.md +++ b/01_values.md @@ -4,87 +4,52 @@ {{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} -Below the surface of the machine, the program moves. Without effort, -it expands and contracts. In great harmony, electrons scatter and -regroup. The forms on the monitor are but ripples on the water. The -essence stays invisibly below. +Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below. quote}} {{index "Yuan-Ma", "Book of Programming"}} -{{figure {url: "img/chapter_picture_1.jpg", alt: "Picture of a sea of bits", chapter: framed}}} +{{figure {url: "img/chapter_picture_1.jpg", alt: "Illustration of a sea of dark and bright dots (bits) with islands in it", chapter: framed}}} {{index "binary data", data, bit, memory}} -Inside the computer's world, there is only data. You can read data, -modify data, create new data—but that which isn't data cannot be -mentioned. All this data is stored as long sequences of bits and is -thus fundamentally alike. +In the computer's world, there is only data. You can read data, modify data, create new data—but that which isn't data cannot be mentioned. All this data is stored as long sequences of bits and is thus fundamentally alike. {{index CD, signal}} -_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. +_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. It works the same -way as a decimal number, but instead of 10 different ((digit))s, you -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: +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: ```{lang: null} 0 0 0 0 1 1 0 1 128 64 32 16 8 4 2 1 ``` -So 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 {{index [memory, organization], "volatile data storage", "hard drive"}} -Imagine a sea of bits—an ocean of them. A typical modern computer has -more than 30 billion bits in its volatile data storage (working -memory). Nonvolatile storage (the hard disk or equivalent) tends to -have yet a few orders of magnitude more. +Imagine a sea of bits—an ocean of them. A typical modern computer has more than 100 billion bits in its volatile data storage (working memory). Nonvolatile storage (the hard disk or equivalent) tends to have yet a few orders of magnitude more. -To be able to work with such quantities of bits without getting lost, -we must separate them into chunks that represent pieces of -information. In a JavaScript environment, those chunks are called -_((value))s_. Though all values are made of bits, they play different -roles. Every value has a ((type)) that determines its role. Some -values are numbers, some values are pieces of text, some values are -functions, and so on. +To be able to work with such quantities of bits without getting lost, we separate them into chunks that represent pieces of information. In a JavaScript environment, those chunks are called _((value))s_. Though all values are made of bits, they play different roles. Every value has a ((type)) that determines its role. Some values are numbers, some values are pieces of text, some values are functions, and so on. {{index "garbage collection"}} -To create a value, you must merely invoke its name. This is -convenient. You don't have to gather building material for your values -or pay for them. You just call for one, and _whoosh_, you have it. They -are not really created from thin air, of course. Every value has to be -stored somewhere, and if you want to use a gigantic amount of them at -the same time, you might run out of memory. Fortunately, this is a -problem only if you need them all simultaneously. As soon as you no -longer use a value, it will dissipate, leaving behind its bits to be -recycled as building material for the next generation of values. +To create a value, you must merely invoke its name. This is convenient. You don't have to gather building material for your values or pay for them. You just call for one, and _whoosh_, you have it. Of course, values are not really created from thin air. Each one has to be stored somewhere, and if you want to use a gigantic number of them at the same time, you might run out of computer memory. Fortunately, this is a problem only if you need them all simultaneously. As soon as you no longer use a value, it will dissipate, leaving behind its bits to be recycled as building material for the next generation of values. -This chapter introduces the atomic elements of JavaScript programs, -that is, the simple value types and the operators that can act on such -values. +The remainder of this chapter introduces the atomic elements of JavaScript programs, that is, the simple value types and the operators that can act on such values. ## Numbers {{index [syntax, number], number, [number, notation]}} -Values of the _number_ type are, unsurprisingly, numeric values. In a -JavaScript program, they are written as follows: +Values of the _number_ type are, unsurprisingly, numeric values. In a JavaScript program, they are written as follows: ``` 13 @@ -92,40 +57,21 @@ JavaScript program, they are written as follows: {{index "binary number"}} -Use that in a program, and it will cause the bit pattern for the -number 13 to come into existence inside the computer's memory. +Using that in a program will cause the bit pattern for the number 13 to come into existence inside the computer's memory. {{index [number, representation], bit}} -JavaScript uses a fixed number of bits, 64 of them, to store a -single number value. There are only so many patterns you can make with -64 bits, which means that the number of different numbers that can be -represented is limited. With _N_ decimal ((digit))s, you can represent -10^N^ numbers. Similarly, given 64 binary -digits, you can represent 2^64^ different numbers, which is about 18 -quintillion (an 18 with 18 zeros after it). That's a lot. - -Computer memory used to be much smaller, and people tended to use -groups of 8 or 16 bits to represent their numbers. It was easy to -accidentally _((overflow))_ such small numbers—to end up with a number -that did not fit into the given number of bits. Today, even computers -that fit in your pocket have plenty of memory, so you are free to use -64-bit chunks, and you need to worry about overflow only when dealing -with truly astronomical numbers. +JavaScript uses a fixed number of bits, 64 of them, to store a single number value. There are only so many patterns you can make with 64 bits, which limits the number of different numbers that can be represented. With _N_ decimal ((digit))s, you can represent 10^N^ numbers. Similarly, given 64 binary digits, you can represent 2^64^ different numbers, which is about 18 quintillion (an 18 with 18 zeros after it). That's a lot. + +Computer memory used to be much smaller, and people tended to use groups of 8 or 16 bits to represent their numbers. It was easy to accidentally _((overflow))_ such small numbers—to end up with a number that did not fit into the given number of bits. Today, even computers that fit in your pocket have plenty of memory, so you are free to use 64-bit chunks, and you need to worry about overflow only when dealing with truly astronomical numbers. {{index sign, "floating-point number", "sign bit"}} -Not all whole numbers less than 18 quintillion fit in a JavaScript number, -though. Those bits also store negative numbers, so one bit indicates -the sign of the number. A bigger issue is that nonwhole numbers must -also be represented. To do this, some of the bits are used to store -the position of the decimal point. The actual maximum whole number -that can be stored is more in the range of 9 quadrillion (15 -zeros)—which is still pleasantly huge. +Not all whole numbers less than 18 quintillion fit in a JavaScript number, though. Those bits also store negative numbers, so one bit indicates the sign of the number. A bigger issue is representing nonwhole numbers. To do this, some of the bits are used to store the position of the decimal point. The actual maximum whole number that can be stored is more in the range of 9 quadrillion (15 zeros)—which is still pleasantly huge. {{index [number, notation], "fractional number"}} -Fractional numbers are written by using a dot. +Fractional numbers are written using a dot: ``` 9.81 @@ -133,105 +79,61 @@ Fractional numbers are written by 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 ``` -That is 2.998 × 10^8^ = 299,800,000. +That's 2.998 × 10^8^ = 299,800,000. {{index pi, [number, "precision of"], "floating-point number"}} -Calculations with whole numbers (also called _((integer))s_) smaller -than the aforementioned 9 quadrillion are guaranteed to always be -precise. Unfortunately, calculations with fractional numbers are -generally not. Just as π (pi) cannot be precisely expressed by a -finite number of decimal digits, many numbers lose some precision when -only 64 bits are available to store them. This is a shame, but it -causes practical problems only in specific situations. The important -thing is to be aware of it and treat fractional digital numbers as -approximations, not as precise values. +Calculations with whole numbers (also called _((integer))s_) that are smaller than the aforementioned 9 quadrillion are guaranteed to always be precise. Unfortunately, calculations with fractional numbers are generally not. Just as π (pi) cannot be precisely expressed by a finite number of decimal digits, many numbers lose some precision when only 64 bits are available to store them. This is a shame, but it causes practical problems only in specific situations. The important thing is to be aware of it and treat fractional digital numbers as approximations, not as precise values. ### Arithmetic {{index [syntax, operator], operator, "binary operator", arithmetic, addition, multiplication}} -The main thing to do with numbers is arithmetic. Arithmetic operations -such as addition or multiplication take two number values and produce -a new number from them. Here is what they look like in JavaScript: +The main thing to do with numbers is arithmetic. Arithmetic operations such as addition or multiplication take two number values and produce a new number from them. Here is what they look like in JavaScript: -``` +```{meta: "expr"} 100 + 4 * 11 ``` {{index [operator, application], asterisk, "plus character", "* operator", "+ operator"}} -The `+` and `*` symbols are called _operators_. The first stands for -addition, and the second stands for multiplication. Putting an -operator between two values will apply it to those values and produce -a new value. +The `+` and `*` symbols are called _operators_. The first stands for addition and the second stands for multiplication. Putting an operator between two values will apply it to those values and produce a new value. {{index grouping, parentheses, precedence}} -But does the 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. But 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 ``` {{index "hyphen character", "slash character", division, subtraction, minus, "- operator", "/ operator"}} -For subtraction, there is the `-` operator, and division can be done -with the `/` operator. +For subtraction, there is the `-` operator. Division can be done with the `/` operator. -When operators appear together without parentheses, the order in which -they are applied is determined by the _((precedence))_ of the -operators. The example shows that multiplication comes before -addition. The `/` operator has the same precedence as `*`. Likewise -for `+` and `-`. When multiple operators with the same precedence -appear next to each other, as in `1 - 2 + 1`, they are applied left to -right: `(1 - 2) + 1`. +When operators appear together without parentheses, the order in which they are applied is determined by the _((precedence))_ of the operators. The example shows that multiplication comes before addition. The `/` operator has the same precedence as `*`. Likewise, `+` and `-` have the same precedence. When multiple operators with the same precedence appear next to each other, as in `1 - 2 + 1`, they are applied left to right: `(1 - 2) + 1`. -These rules of precedence are not something you should worry about. -When in doubt, just add parentheses. +Don't worry too much about these precedence rules. When in doubt, just add parentheses. {{index "modulo operator", division, "remainder operator", "% operator"}} -There is one more arithmetic operator, which you might not immediately -recognize. The `%` symbol is used to represent the _remainder_ -operation. `X % Y` is the remainder of dividing `X` by `Y`. For -example, `314 % 100` produces `14`, and `144 % 12` gives `0`. -The remainder operator's precedence is the same as that of multiplication and -division. You'll also often see this operator referred to as _modulo_. +There is one more arithmetic operator, which you might not immediately recognize. The `%` symbol is used to represent the _remainder_ operation. `X % Y` is the remainder of dividing `X` by `Y`. For example, `314 % 100` produces `14`, and `144 % 12` gives `0`. The remainder operator's precedence is the same as that of multiplication and division. You'll also often see this operator referred to as _modulo_. ### Special numbers -{{index [number, "special values"]}} - -There are three special values in JavaScript that are considered -numbers but don't behave like normal numbers. +{{index [number, "special values"], infinity}} -{{index infinity}} - -The first two are `Infinity` and `-Infinity`, which represent the -positive and negative infinities. `Infinity - 1` is still `Infinity`, -and so on. Don't put too much trust in infinity-based computation, -though. It isn't mathematically sound, and it will quickly lead to the -next special number: `NaN`. +There are three special values in JavaScript that are considered numbers but don't behave like normal numbers. The first two are `Infinity` and `-Infinity`, which represent the positive and negative infinities. `Infinity - 1` is still `Infinity`, and so on. Don't put too much trust in infinity-based computation, though. It isn't mathematically sound, and it will quickly lead to the next special number: `NaN`. {{index NaN, "not a number", "division by zero"}} -`NaN` stands for "not a number", even though it _is_ a value of the -number type. You'll get this result when you, for example, try to -calculate `0 / 0` (zero divided by zero), `Infinity - Infinity`, or -any number of other numeric operations that don't yield a meaningful -result. +`NaN` stands for "not a number", even though it _is_ a value of the number type. You'll get this result when you, for example, try to calculate `0 / 0` (zero divided by zero), `Infinity - Infinity`, or any number of other numeric operations that don't yield a meaningful result. ## Strings @@ -239,8 +141,7 @@ result. {{index [syntax, string], text, character, [string, notation], "single-quote character", "double-quote character", "quotation mark", backtick}} -The next basic data type is the _((string))_. Strings are used to -represent text. They are written by enclosing their content in quotes. +The next basic data type is the _((string))_. Strings are used to represent text. They are written by enclosing their content in quotes. ``` `Down on the sea` @@ -248,46 +149,28 @@ represent text. They are written by enclosing their content in quotes. 'Float on the ocean' ``` -You can use single quotes, double quotes, or backticks to mark -strings, as long as the quotes at the start and the end of the string -match. +You can use single quotes, double quotes, or backticks to mark strings, as long as the quotes at the start and the end of the string match. {{index "line break", "newline character"}} -Almost anything can be put between quotes, and JavaScript will make a -string value out of it. But a few characters are more difficult. You -can imagine how putting quotes between quotes might be hard. -_Newlines_ (the characters you get when you press [enter]{keyname}) can be -included without escaping only when the string is quoted with backticks -(`` ` ``). +You can put almost anything between quotes to have JavaScript make a string value out of it. But a few characters are more difficult. You can imagine how putting quotes between quotes might be hard, since they will look like the end of the string. _Newlines_ (the characters you get when you press [enter]{keyname}) can be included only when the string is quoted with backticks (`` ` ``). {{index [escaping, "in strings"], ["backslash character", "in strings"]}} -To make it possible to include such characters in a string, the -following notation is used: whenever a backslash (`\`) is found inside -quoted text, it indicates that the character after it has a special -meaning. This is called _escaping_ the character. A quote that is -preceded by a backslash will not end the string but be part of it. -When an `n` character occurs after a backslash, it is interpreted as a -newline. Similarly, a `t` after a backslash means a ((tab character)). -Take the following string: +To make it possible to include such characters in a string, the following notation is used: a backslash (`\`) inside quoted text indicates that the character after it has a special meaning. This is called _escaping_ the character. A quote that is preceded by a backslash will not end the string but be part of it. When an `n` character occurs after a backslash, it is interpreted as a newline. Similarly, a `t` after a backslash means a ((tab character)). Take the following string: ``` "This is the first line\nAnd this is the second" ``` -The actual text contained is this: +This is the actual text in that string: ```{lang: null} This is the first line And this is the second ``` -There are, of course, situations where you want a backslash in a -string to be just a backslash, not a special code. If two backslashes -follow each other, they will collapse together, and only one will be -left in the resulting string value. This is how the string "_A newline -character is written like `"`\n`"`._" can be expressed: +There are, of course, situations where you want a backslash in a string to be just a backslash, not a special code. If two backslashes follow each other, they will collapse together, and only one will be left in the resulting string value. This is how the string "_A newline character is written like `"`\n`"`._" can be expressed: ``` "A newline character is written like \"\\n\"." @@ -297,62 +180,37 @@ character is written like `"`\n`"`._" can be expressed: {{index [string, representation], Unicode, character}} -Strings, too, have to be modeled as a series of bits to be able to -exist inside the computer. The way JavaScript does this is based on -the _((Unicode))_ standard. This standard assigns a number to -virtually every character you would ever need, including characters -from Greek, Arabic, Japanese, Armenian, and so on. If we have a number -for every character, a string can be described by a sequence of -numbers. +Strings, too, have to be modeled as a series of bits to be able to exist inside the computer. The way JavaScript does this is based on the _((Unicode))_ standard. This standard assigns a number to virtually every character you would ever need, including characters from Greek, Arabic, Japanese, Armenian, and so on. If we have a number for every character, a string can be described by a sequence of numbers. And that's what JavaScript does. {{index "UTF-16", emoji}} -And that's what JavaScript does. But there's a complication: -JavaScript's representation uses 16 bits per string element, which can -describe up to 2^16^ different characters. But Unicode defines more -characters than that—about twice as many, at this point. So some -characters, such as many emoji, take up two "character positions" in -JavaScript strings. We'll come back to this in [Chapter -?](higher_order#code_units). +There's a complication though: JavaScript's representation uses 16 bits per string element, which can describe up to 2^16^ different characters. However, Unicode defines more characters than that—about twice as many, at this point. So some characters, such as many emoji, take up two "character positions" in JavaScript strings. We'll come back to this in [Chapter ?](higher_order#code_units). {{index "+ operator", concatenation}} -Strings cannot be divided, multiplied, or subtracted, but the `+` -operator _can_ be used on them. It does not add, but it -_concatenates_—it glues two strings together. The following line will -produce the string `"concatenate"`: +Strings cannot be divided, multiplied, or subtracted. The `+` operator _can_ be used on them, not to add, but to _concatenate_—to glue two strings together. The following line will produce the string `"concatenate"`: -``` +```{meta: "expr"} "con" + "cat" + "e" + "nate" ``` -String values have a number of associated functions (_methods_) that -can be used to perform other operations on them. I'll say more about -these in [Chapter ?](data#methods). +String values have a number of associated functions (_methods_) that can be used to perform other operations on them. I'll say more about these in [Chapter ?](data#methods). {{index interpolation, backtick}} -Strings written with single or double quotes behave very much the -same—the only difference is in which type of quote you need to escape -inside of them. Backtick-quoted strings, usually called _((template -literals))_, can do a few more tricks. Apart from being able to span -lines, they can also embed other values. +Strings written with single or double quotes behave very much the same—the only difference lies in which type of quote you need to escape inside of them. Backtick-quoted strings, usually called _((template literals))_, can do a few more tricks. Apart from being able to span lines, they can also embed other values. -``` +```{meta: "expr"} `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. The 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 {{index operator, "typeof operator", type}} -Not all operators are symbols. Some are written as words. One example -is the `typeof` operator, which produces a string value naming the -type of the value you give it. +Not all operators are symbols. Some are written as words. One example is the `typeof` operator, which produces a string value naming the type of the value you give it. ``` console.log(typeof 4.5) @@ -365,17 +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 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)) @@ -386,10 +238,7 @@ console.log(- (10 - 2)) {{index Boolean, operator, true, false, bit}} -It is often useful to have a value that distinguishes between only two -possibilities, like "yes" and "no" or "on" and "off". For this -purpose, JavaScript has a _Boolean_ type, which has just two values, -true and false, which are written as those words. +It is often useful to have a value that distinguishes between only two possibilities, like "yes" and "no" or "on" and "off". For this purpose, JavaScript has a _Boolean_ type, which has just two values, true and false, written as those words. ### Comparison @@ -406,10 +255,7 @@ console.log(3 < 2) {{index [comparison, "of numbers"], "> operator", "< operator", "greater than", "less than"}} -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. +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. @@ -420,51 +266,39 @@ console.log("Aardvark" < "Zoroaster") {{index [comparison, "of strings"]}} -The way strings are ordered is roughly alphabetic but not really what -you'd expect to see in a dictionary: uppercase letters are always -"less" than lowercase ones, so `"Z" < "a"`, and nonalphabetic -characters (!, -, and so on) are also included in the ordering. When -comparing strings, JavaScript goes over the characters from left to -right, comparing the ((Unicode)) codes one by one. +The way strings are ordered is roughly alphabetic but not really what you'd expect to see in a dictionary: uppercase letters are always "less" than lowercase ones, so `"Z" < "a"`, and nonalphabetic characters (!, -, and so on) are also included in the ordering. When comparing strings, JavaScript goes over the characters from left to right, comparing the ((Unicode)) codes one by one. {{index equality, ">= operator", "<= operator", "== operator", "!= operator"}} -Other similar operators are `>=` (greater than or equal to), `<=` -(less than or equal to), `==` (equal to), and `!=` (not equal to). +Other similar operators are `>=` (greater than or equal to), `<=` (less than or equal to), `==` (equal to), and `!=` (not equal to). ``` -console.log("Itchy" != "Scratchy") +console.log("Garnet" != "Ruby") // → true -console.log("Apple" == "Orange") +console.log("Pearl" == "Amethyst") // → false ``` {{index [comparison, "of NaN"], NaN}} -There is only one value in JavaScript that is not equal to itself, and -that is `NaN` ("not a number"). +There is only one value in JavaScript that is not equal to itself, and that is `NaN` ("not a number"). ``` console.log(NaN == NaN) // → false ``` -`NaN` is supposed to denote the result of a nonsensical computation, -and as such, it isn't equal to the result of any _other_ nonsensical -computations. +`NaN` is supposed to denote the result of a nonsensical computation, and as such, it isn't equal to the result of any _other_ nonsensical computations. ### Logical operators {{index reasoning, "logical operators"}} -There are also some operations that can be applied to Boolean values -themselves. JavaScript supports three logical operators: _and_, _or_, -and _not_. These can be used to "reason" about Booleans. +There are also some operations that can be applied to Boolean values themselves. JavaScript supports three logical operators: _and_, _or_, and _not_. These can be used to "reason" about Booleans. {{index "&& operator", "logical and"}} -The `&&` operator represents logical _and_. It is a binary operator, -and its result is true only if both the values given to it are true. +The `&&` operator represents logical _and_. It is a binary operator, and its result is true only if both the values given to it are true. ``` console.log(true && false) @@ -475,8 +309,7 @@ console.log(true && true) {{index "|| operator", "logical or"}} -The `||` operator denotes logical _or_. It produces true if either of -the values given to it is true. +The `||` operator denotes logical _or_. It produces true if either of the values given to it is true. ``` console.log(false || true) @@ -487,29 +320,19 @@ console.log(false || false) {{index negation, "! operator"}} -_Not_ is written as an exclamation mark (`!`). It is a unary operator -that flips the value given to it—`!true` produces `false`, and `!false` -gives `true`. +_Not_ is written as an exclamation mark (`!`). It is a unary operator that flips the value given to it—`!true` produces `false` and `!false` gives `true`. {{index precedence}} -When mixing these Boolean operators with arithmetic and other -operators, it is not always obvious when parentheses are needed. In -practice, you can usually get by with knowing that of the operators we -have seen so far, `||` has the lowest precedence, then comes `&&`, -then the comparison operators (`>`, `==`, and so on), and then the -rest. This order has been chosen such that, in typical expressions -like the following one, as few parentheses as possible are necessary: +When mixing these Boolean operators with arithmetic and other operators, it is not always obvious when parentheses are needed. In practice, you can usually get by with knowing that of the operators we have seen so far, `||` has the lowest precedence, then comes `&&`, then the comparison operators (`>`, `==`, and so on), and then the rest. This order has been chosen such that, in typical expressions like the following one, as few parentheses as possible are necessary: -``` +```{meta: "expr"} 1 + 1 == 2 && 10 * 10 > 50 ``` {{index "conditional execution", "ternary operator", "?: operator", "conditional operator", "colon character", "question mark"}} -The last logical operator I will discuss is not unary, not binary, but -_ternary_, operating on three values. It is written with a question -mark and a colon, like this: +The last logical operator we will look at is not unary, not binary, but _ternary_, operating on three values. It is written with a question mark and a colon, like this: ``` console.log(true ? 1 : 2); @@ -518,36 +341,23 @@ console.log(false ? 1 : 2); // → 2 ``` -This one is called the _conditional_ operator (or sometimes just -the _ternary_ operator since it is the only such operator in the -language). The value on the left of the question mark "picks" which of -the other two values will come out. When it is true, it chooses the -middle value, and when it is false, it chooses the value on the right. +This one is called the _conditional_ operator (or sometimes just _the ternary operator_ since it is the only such operator in the language). The operator uses the value to the left of the question mark to decide which of the two other values to "pick". If you write `a ? b : c`, the result will be `b` when `a` is true and `c` otherwise. ## Empty values {{index undefined, null}} -There are two special values, written `null` and `undefined`, that are -used to denote the absence of a _meaningful_ value. They are -themselves values, but they carry no information. +There are two special values, written `null` and `undefined`, that are used to denote the absence of a _meaningful_ value. They are themselves values, but they carry no information. -Many operations in the language that don't produce a meaningful value -(you'll see some later) yield `undefined` simply because they have to -yield _some_ value. +Many operations in the language that don't produce a meaningful value yield `undefined` simply because they have to yield _some_ value. -The difference in meaning between `undefined` and `null` is an accident -of JavaScript's design, and it doesn't matter most of the time. In cases -where you actually have to concern yourself with these values, I -recommend treating them as mostly interchangeable. +The difference in meaning between `undefined` and `null` is an accident of JavaScript's design, and it doesn't matter most of the time. In cases where you actually have to concern yourself with these values, I recommend treating them as mostly interchangeable. ## Automatic type conversion {{index NaN, "type coercion"}} -In the Introduction, I mentioned that JavaScript goes out of its way -to accept almost any program you give it, even programs that do odd -things. This is nicely demonstrated by the following expressions: +In the [introduction](intro), I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: ``` console.log(8 * null) @@ -564,33 +374,15 @@ console.log(false == 0) {{index "+ operator", arithmetic, "* operator", "- operator"}} -When an operator is applied to the "wrong" type of value, JavaScript -will quietly convert that value to the type it needs, using a set of -rules that often aren't what you want or expect. This is called -_((type coercion))_. The `null` in the first expression becomes `0`, -and the `"5"` in the second expression becomes `5` (from string to -number). Yet in the third expression, `+` tries string concatenation -before numeric addition, so the `1` is converted to `"1"` (from number -to string). +When an operator is applied to the "wrong" type of value, JavaScript will quietly convert that value to the type it needs, using a set of rules that often aren't what you want or expect. This is called _((type coercion))_. The `null` in the first expression becomes `0` and the `"5"` in the second expression becomes `5` (from string to number). Yet in the third expression, `+` tries string concatenation before numeric addition, so the `1` is converted to `"1"` (from number to string). {{index "type coercion", [number, "conversion to"]}} -When something that doesn't map to a number in an obvious way (such as -`"five"` or `undefined`) is converted to a number, you get the value -`NaN`. Further arithmetic operations on `NaN` keep producing `NaN`, so -if you find yourself getting one of those in an unexpected place, look -for accidental type conversions. +When something that doesn't map to a number in an obvious way (such as `"five"` or `undefined`) is converted to a number, you get the value `NaN`. Further arithmetic operations on `NaN` keep producing `NaN`, so if you find yourself getting one of those in an unexpected place, look for accidental type conversions. {{index null, undefined, [comparison, "of undefined values"], "== operator"}} -When comparing values of the same type using `==`, the outcome is easy -to predict: you should get true when both values are the same, except -in the case of `NaN`. But when the types differ, JavaScript uses a -complicated and confusing set of rules to determine what to do. In -most cases, it just tries to convert one of the values to the other -value's type. However, when `null` or `undefined` occurs on either -side of the operator, it produces true only if both sides are one of -`null` or `undefined`. +When comparing values of the same type using the `==` operator, the outcome is easy to predict: you should get true when both values are the same, except in the case of `NaN`. But when the types differ, JavaScript uses a complicated and confusing set of rules to determine what to do. In most cases, it just tries to convert one of the values to the other value's type. However, when `null` or `undefined` occurs on either side of the operator, it produces true only if both sides are one of `null` or `undefined`. ``` console.log(null == undefined); @@ -599,41 +391,23 @@ console.log(null == 0); // → false ``` -That behavior is often useful. When you want to test whether a value -has a real value instead of `null` or `undefined`, you can compare it -to `null` with the `==` (or `!=`) operator. +That behavior is often useful. When you want to test whether a value has a real value instead of `null` or `undefined`, you can compare it to `null` with the `==` or `!=` operator. {{index "type coercion", [Boolean, "conversion to"], "=== operator", "!== operator", comparison}} -But 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. -So `"" === 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. +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. ### Short-circuiting of logical operators {{index "type coercion", [Boolean, "conversion to"], operator}} -The logical operators `&&` and `||` handle values of different types -in a peculiar way. They will convert the value on their left side to -Boolean type in order to decide what to do, but depending on the -operator and the result of that conversion, they will return either the -_original_ left-hand value or the right-hand value. +The logical operators `&&` and `||` handle values of different types in a peculiar way. They will convert the value on their left side to Boolean type in order to decide what to do, but depending on the operator and the result of that conversion, they will return either the _original_ left-hand value or the right-hand value. {{index "|| operator"}} -The `||` operator, for example, will return the value to its left when -that can be converted to true and will return the value on its right -otherwise. This has the expected effect when the values are Boolean -and does something analogous for values of other types. +The `||` operator, for example, will return the value to its left when that value can be converted to true and will return the value on its right otherwise. This has the expected effect when the values are Boolean and does something analogous for values of other types. ``` console.log(null || "user") @@ -644,47 +418,35 @@ 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`. So `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 `||`. + +``` +console.log(0 || 100); +// → 100 +console.log(0 ?? 100); +// → 0 +console.log(null ?? 100); +// → 100 +``` {{index "&& operator"}} -The `&&` operator works similarly but the other way around. When the -value to its left is something that converts to false, it returns that -value, and otherwise it returns the value on its right. +The `&&` operator works similarly but the other way around. When the value to its left is something that converts to false, it returns that value, and otherwise it returns the value on its right. -Another important property of these two operators is that the part to -their right is evaluated only when necessary. In the case of `true || -X`, no matter what `X` is—even if it's a piece of program that does -something _terrible_—the result will be true, and `X` is never -evaluated. The same goes for `false && X`, which is false and will -ignore `X`. This is called _((short-circuit evaluation))_. +Another important property of these two operators is that the part to their right is evaluated only when necessary. In the case of `true || X`, no matter what `X` is—even if it's a piece of program that does something _terrible_—the result will be true, and `X` is never evaluated. The same goes for `false && X`, which is false and will ignore `X`. This is called _((short-circuit evaluation))_. {{index "ternary operator", "?: operator", "conditional operator"}} -The conditional operator works in a similar way. Of the second and -third values, only the one that is selected is evaluated. +The conditional operator works in a similar way. Of the second and third values, only the one that is selected is evaluated. ## Summary -We looked at four types of JavaScript values in this chapter: numbers, -strings, Booleans, and undefined values. - -Such values are created by typing in their name (`true`, `null`) or -value (`13`, `"abc"`). You can combine and transform values with -operators. We saw binary operators for arithmetic (`+`, `-`, `*`, `/`, -and `%`), string concatenation (`+`), comparison (`==`, `!=`, `===`, -`!==`, `<`, `>`, `<=`, `>=`), and logic (`&&`, `||`), as well as -several unary operators (`-` to negate a number, `!` to negate -logically, and `typeof` to find a value's type) and a ternary operator -(`?:`) to pick one of two values based on a third value. - -This gives you enough information to use JavaScript as a pocket -calculator but not much more. The [next -chapter](program_structure) will start tying -these expressions together into basic programs. +We looked at four types of JavaScript values in this chapter: numbers, strings, Booleans, and undefined values. Such values are created by typing in their name (`true`, `null`) or value (`13`, `"abc"`). + +You can combine and transform values with operators. We saw binary operators for arithmetic (`+`, `-`, `*`, `/`, and `%`), string concatenation (`+`), comparison (`==`, `!=`, `===`, `!==`, `<`, `>`, `<=`, `>=`), and logic (`&&`, `||`, `??`), as well as several unary operators (`-` to negate a number, `!` to negate logically, and `typeof` to find a value's type) and a ternary operator (`?:`) to pick one of two values based on a third value. + +This gives you enough information to use JavaScript as a pocket calculator but not much more. The [next chapter](program_structure) will start tying these expressions together into basic programs. diff --git a/02_program_structure.md b/02_program_structure.md index a2ee7a377..7f9db00e2 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -2,93 +2,55 @@ {{quote {author: "_why", title: "Why's (Poignant) Guide to Ruby", chapter: true} -And my heart glows bright red under my filmy, translucent skin and -they have to administer 10cc of JavaScript to get me to come back. (I -respond well to toxins in the blood.) Man, that stuff will kick the -peaches right out your gills! +And my heart glows bright red under my filmy, translucent skin and they have to administer 10cc of JavaScript to get me to come back. (I respond well to toxins in the blood.) Man, that stuff will kick the peaches right out your gills! quote}} {{index why, "Poignant Guide"}} -{{figure {url: "img/chapter_picture_2.jpg", alt: "Picture of tentacles holding objects", chapter: framed}}} +{{figure {url: "img/chapter_picture_2.jpg", alt: "Illustration showing a number of tentacles holding chess pieces", chapter: framed}}} -In this chapter, we will start to do things that can actually be called -_programming_. We will expand our command of the JavaScript language -beyond the nouns and sentence fragments we've seen so far, to the -point where we can express meaningful prose. +In this chapter, we will start to do things that can actually be called _programming_. We will expand our command of the JavaScript language beyond the nouns and sentence fragments we've seen so far to the point where we can express meaningful prose. ## Expressions and statements {{index grammar, [syntax, expression], [code, "structure of"], grammar, [JavaScript, syntax]}} -In [Chapter ?](values), we made values and applied operators to them -to get new values. Creating values like this is the main substance of -any JavaScript program. But that substance has to be framed in a -larger structure to be useful. So that's what we'll cover next. +In [Chapter ?](values), we made values and applied operators to them to get new values. Creating values like this is the main substance of any JavaScript program. But that substance has to be framed in a larger structure to be useful. That's what we'll cover in this chapter. {{index "literal expression", [parentheses, expression]}} -A fragment of code that produces a value is called an -_((expression))_. Every value that is written literally (such as `22` -or `"psychoanalysis"`) is an expression. An expression between -parentheses is also an expression, as is a ((binary operator)) -applied to two expressions or a ((unary operator)) applied to one. +A fragment of code that produces a value is called an _((expression))_. Every value that is written literally (such as `22` or `"psychoanalysis"`) is an expression. An expression between parentheses is also an expression, as is a ((binary operator)) applied to two expressions or a ((unary operator)) applied to one. {{index [nesting, "of expressions"], "human language"}} -This shows part of the beauty of a language-based interface. -Expressions can contain other expressions in a way similar to how subsentences in human languages are nested—a subsentence can -contain its own subsentences, and so on. This allows us to build -expressions that describe arbitrarily complex computations. +This shows part of the beauty of a language-based interface. Expressions can contain other expressions in a way similar to how subsentences in human languages are nested—a subsentence can contain its own subsentences, and so on. This allows us to build expressions that describe arbitrarily complex computations. {{index statement, semicolon, program}} -If an expression corresponds to a sentence fragment, a JavaScript -_statement_ corresponds to a full sentence. A program is a list of -statements. +If an expression corresponds to a sentence fragment, a JavaScript _statement_ corresponds to a full sentence. A program is a list of statements. {{index [syntax, statement]}} -The simplest kind of statement is an expression with a semicolon after -it. This is a program: +The simplest kind of statement is an expression with a semicolon after it. This is a program: ``` 1; !false; ``` -It is a useless program, though. An ((expression)) can be content to -just produce a value, which can then be used by the enclosing code. A -((statement)) stands on its own, so it amounts to something only if it -affects the world. It could display something on the screen—that -counts as changing the world—or it could change the internal state of -the machine in a way that will affect the statements that come after -it. These changes are called _((side effect))s_. The statements in the -previous example just produce the values `1` and `true` and then -immediately throw them away. This leaves no impression on the world at -all. When you run this program, nothing observable happens. +It is a useless program, though. An ((expression)) can be content to just produce a value, which can then be used by the enclosing code. However, a ((statement)) stands on its own, so if it doesn't affect the world, it's useless. It may display something on the screen, as with `console.log`, or change the state of the machine in a way that will affect the statements that come after it. These changes are called _((side effect))s_. The statements in the previous example just produce the values `1` and `true` and then immediately throw them away. This leaves no impression on the world at all. When you run this program, nothing observable happens. {{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 has to be -immediately used 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; @@ -96,17 +58,11 @@ let caught = 5 * 5; {{index "let keyword"}} -That's a second kind of ((statement)). The special word -(_((keyword))_) `let` indicates that this sentence is going to define -a binding. It is followed by the name of the binding and, if we want -to immediately give it a value, by an `=` operator and an expression. +That gives us a second kind of ((statement)). The special word (_((keyword))_) `let` indicates that this sentence is going to define a binding. It is followed by the name of the binding and, if we want to immediately give it a value, by an `=` operator and an expression. -The previous statement creates a binding called `caught` and uses it -to grab hold of the number that is produced by multiplying 5 by 5. +The example creates a binding called `caught` and uses it to grab hold of the number that is produced by multiplying 5 by 5. -After a binding has been defined, its name can be used as an -((expression)). The value of such an expression is the value the -binding currently holds. Here's an example: +After a binding has been defined, its name can be used as an ((expression)). The value of such an expression is the value the binding currently holds. Here's an example: ``` let ten = 10; @@ -116,10 +72,7 @@ console.log(ten * ten); {{index "= operator", assignment, [binding, assignment]}} -When a binding points at a value, that does not mean it is tied to -that value forever. The `=` operator can be used at any time on -existing bindings to disconnect them from their current value and have -them point to a new one. +When a binding points at a value, that does not mean it is tied to that value forever. The `=` operator can be used at any time on existing bindings to disconnect them from their current value and have them point to a new one: ``` let mood = "light"; @@ -132,15 +85,9 @@ console.log(mood); {{index [binding, "model of"], "tentacle (analogy)"}} -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 that it still has a -reference to. When you need to remember something, you grow a tentacle -to hold on to it or you reattach one of your existing tentacles to it. +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. And then 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; @@ -151,14 +98,11 @@ console.log(luigisDebt); {{index undefined}} -When you define a binding without giving it a value, the tentacle has -nothing to grasp, so it ends in thin air. If you ask for the value of -an empty binding, you'll get the value `undefined`. +When you define a binding without giving it a value, the tentacle has nothing to grasp, so it ends in thin air. If you ask for the value of an empty binding, you'll get the value `undefined`. {{index "let keyword"}} -A single `let` statement may define multiple bindings. The -definitions must be separated by commas. +A single `let` statement may define multiple bindings. The definitions must be separated by commas: ``` let one = 1, two = 2; @@ -166,8 +110,7 @@ console.log(one + two); // → 3 ``` -The words `var` and `const` can also be used to create bindings, in a -way similar to `let`. +The words `var` and `const` can also be used to create bindings, in a similar fashion to `let`. ``` var name = "Ayda"; @@ -178,37 +121,23 @@ console.log(greeting + name); {{index "var keyword"}} -The first, `var` (short for "variable"), is the way bindings were -declared in pre-2015 JavaScript. I'll get back to the precise way it -differs from `let` in the [next chapter](functions). For now, -remember that it mostly does the same thing, but we'll rarely use it -in this book because it has some confusing properties. +The first of these, `var` (short for "variable"), is the way bindings were declared in pre-2015 JavaScript, when `let` didn't exist yet. I'll get back to the precise way it differs from `let` in the [next chapter](functions). For now, remember that it mostly does the same thing, but we'll rarely use it in this book because it behaves oddly in some situations. {{index "const keyword", naming}} -The word `const` stands for _((constant))_. It defines a constant -binding, which points at the same value for as long as it lives. This -is useful for bindings that give a name to a value so that you can -easily refer to it later. +The word `const` stands for _((constant))_. It defines a constant binding, which points at the same value for as long as it lives. This is useful for bindings that just give a name to a value so that you can easily refer to it later. ## Binding names {{index "underscore character", "dollar sign", [binding, naming]}} -Binding names can be any word. Digits can be part of binding -names—`catch22` is a valid name, for example—but the name must not -start with a digit. A binding name may include dollar signs (`$`) or -underscores (`_`) but no other punctuation or special characters. +Binding names can be any sequence of one or more letters. Digits can be part of binding names—`catch22` is a valid name, for example—but the name must not start with a digit. A binding name may include dollar signs (`$`) or underscores (`_`) but no other punctuation or special characters. {{index [syntax, identifier], "implements (reserved word)", "interface (reserved word)", "package (reserved word)", "private (reserved word)", "protected (reserved word)", "public (reserved word)", "static (reserved word)", "void operator", "yield (reserved word)", "enum (reserved word)", "reserved word", [binding, naming]}} -Words with a special meaning, such as `let`, are _((keyword))s_, and -they may not be used as binding names. There are also a number of -words that are "reserved for use" in ((future)) versions of -JavaScript, which also can't be used as binding names. The full list -of keywords and reserved words is rather long. +Words with a special meaning, such as `let`, are _((keyword))s_, and may not be used as binding names. There are also a number of words that are "reserved for use" in ((future)) versions of JavaScript, which also can't be used as binding names. The full list of keywords and reserved words is rather long: -```{lang: "text/plain"} +```{lang: "null"} break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import interface in instanceof let @@ -218,21 +147,13 @@ switch this throw true try typeof var void while with yield {{index [syntax, error]}} -Don't worry about memorizing this list. When creating a binding produces -an unexpected syntax error, see whether you're trying to define a -reserved word. +Don't worry about memorizing this list. When creating a binding produces an unexpected syntax error, check whether you're trying to define a reserved word. ## The environment {{index "standard environment", [browser, environment]}} -The collection of bindings and their values that exist at a given time -is called the _((environment))_. When a program starts up, this -environment is not empty. It always contains bindings that are part of -the language ((standard)), and most of the time, it also has bindings -that provide ways to interact with the surrounding system. For -example, in a browser, there are functions to interact with the -currently loaded website and to read ((mouse)) and ((keyboard)) input. +The collection of bindings and their values that exist at a given time is called the _((environment))_. When a program starts up, this environment is not empty. It always contains bindings that are part of the language ((standard)), and most of the time, it also has bindings that provide ways to interact with the surrounding system. For example, in a browser, there are functions to interact with the currently loaded website and to read ((mouse)) and ((keyboard)) input. ## Functions @@ -241,53 +162,29 @@ currently loaded website and to read ((mouse)) and ((keyboard)) input. {{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"); ``` -{{figure {url: "img/prompt.png", alt: "A prompt dialog", width: "8cm"}}} +{{figure {url: "img/prompt.png", alt: "A prompt dialog that says 'enter passcode'", width: "8cm"}}} {{index parameter, [function, application], [parentheses, arguments]}} -Executing a function is called _invoking_, _calling_, or _applying_ -it. You can call a function by putting parentheses after an -expression that produces a function value. Usually you'll directly use -the name of the binding that holds the function. The values between -the parentheses are given to the program inside the function. In the -example, the `prompt` function uses the string that we give it as the -text to show in the dialog box. Values given to functions are called -_((argument))s_. Different functions might need a different number or -different types of arguments. +Executing a function is called _invoking_, _calling_, or _applying_ it. You can call a function by putting parentheses after an expression that produces a function value. Usually you'll directly use the name of the binding that holds the function. The values between the parentheses are given to the program inside the function. In the example, the `prompt` function uses the string that we give it as the text to show in the dialog box. Values given to functions are called _((argument))s_. Different functions might need a different number or different types of arguments. -The `prompt` function isn't used much in modern web programming, -mostly because you have no control over the way the resulting dialog -looks, but can be helpful in toy programs and experiments. +The `prompt` function isn't used much in modern web programming, mostly because you have no control over the way the resulting dialog looks, but it can be helpful in toy programs and experiments. ## The console.log function {{index "JavaScript console", "developer tools", "Node.js", "console.log", output, [browser, environment]}} -In the examples, I used `console.log` to output values. Most JavaScript -systems (including all modern web browsers and Node.js) provide a -`console.log` function that writes out its arguments to _some_ text -output device. In browsers, the output lands in the ((JavaScript -console)). This part of the browser interface is hidden by default, -but most browsers open it when you press F12 or, on a Mac, [command]{keyname}-[option]{keyname}-I. -If that does not work, search through the menus for an item named Developer -Tools or similar. +In the examples, I used `console.log` to output values. Most JavaScript systems (including all modern web browsers and Node.js) provide a `console.log` function that writes out its arguments to _some_ text output device. In browsers, the output lands in the ((JavaScript console)). This part of the browser interface is hidden by default, but most browsers open it when you press F12 or, on a Mac, [command]{keyname}-[option]{keyname}-I. If that does not work, search through the menus for an item named Developer Tools or similar. {{if interactive -When running the examples (or your own code) on the pages of this -book, `console.log` output will be shown after the example, instead of -in the browser's JavaScript console. +When running the examples (or your own code) on the pages of this book, `console.log` output will be shown after the example, instead of in the browser's JavaScript console. ``` let x = 30; @@ -299,23 +196,14 @@ if}} {{index [object, property], [property, access]}} -Though binding names cannot contain ((period character))s, -`console.log` does have one. This is because `console.log` isn't a -simple binding. It is actually an expression that retrieves the `log` -property from the value held by the `console` binding. We'll -find out exactly what this means in [Chapter ?](data#properties). +Though binding names cannot contain ((period character))s, `console.log` does have one. This is because `console.log` isn't a simple binding, but an expression that retrieves the `log` property from the value held by the `console` binding. We'll find out exactly what this means in [Chapter ?](data#properties). {{id return_values}} ## Return values {{index [comparison, "of numbers"], "return value", "Math.max function", maximum}} -Showing a dialog box or writing text to the screen is a _((side -effect))_. A lot of 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)); @@ -324,29 +212,20 @@ console.log(Math.max(2, 4)); {{index [function, application], minimum, "Math.min function"}} -When a function produces a value, it is said to _return_ that value. -Anything that produces a value is an ((expression)) in JavaScript, -which means function calls can be used within larger expressions. Here -a call to `Math.min`, which is the opposite of `Math.max`, is used as -part of a plus expression: +When a function produces a value, it is said to _return_ that value. Anything that produces a value is an ((expression)) in JavaScript, which means that function calls can be used within larger expressions. In the following code, a call to `Math.min`, which is the opposite of `Math.max`, is used as part of a plus expression: ``` console.log(Math.min(2, 4) + 100); // → 102 ``` -The [next chapter](functions) explains how to write your own -functions. +[Chapter ?](functions) will explain how to write your own functions. ## Control flow {{index "execution order", program, "control flow"}} -When your program contains more than one ((statement)), the statements -are executed as if they are a story, from top to bottom. This example -program has two statements. The first one asks the user for a number, -and the second, which is executed after the first, shows the -((square)) of that number. +When your program contains more than one ((statement)), the statements are executed as though they were a story, from top to bottom. For example, the following program has two statements. The first asks the user for a number, and the second, which is executed after the first, shows the ((square)) of that number: ``` let theNumber = Number(prompt("Pick a number")); @@ -356,33 +235,23 @@ console.log("Your number is the square root of " + {{index [number, "conversion to"], "type coercion", "Number function", "String function", "Boolean function", [Boolean, "conversion to"]}} -The function `Number` converts a value to a number. We need that -conversion because the result of `prompt` is a string value, and we -want a number. There are similar functions called `String` and -`Boolean` that convert values to those types. +The function `Number` converts a value to a number. We need that conversion because the result of `prompt` is a string value, and we want a number. There are similar functions called `String` and `Boolean` that convert values to those types. -Here is the rather trivial schematic representation of straight-line -control flow: +Here is the rather trivial schematic representation of straight-line control flow: -{{figure {url: "img/controlflow-straight.svg", alt: "Trivial control flow", width: "4cm"}}} +{{figure {url: "img/controlflow-straight.svg", alt: "Diagram showing a straight arrow", width: "4cm"}}} ## Conditional execution {{index Boolean, ["control flow", conditional]}} -Not all programs are straight roads. We may, for example, want to -create a branching road, where the program takes the proper branch -based on the situation at hand. This is called _((conditional -execution))_. +Not all programs are straight roads. We may, for example, want to create a branching road where the program takes the proper branch based on the situation at hand. This is called _((conditional execution))_. -{{figure {url: "img/controlflow-if.svg", alt: "Conditional control flow",width: "4cm"}}} +{{figure {url: "img/controlflow-if.svg", alt: "Diagram of an arrow that splits in two, and then rejoins again",width: "4cm"}}} {{index [syntax, statement], "Number function", "if keyword"}} -Conditional execution is created with the `if` keyword in JavaScript. -In the simple case, we want some code to be executed if, and only if, -a certain condition holds. We might, for example, want to show the -square of the input only if the input is actually a number. +Conditional execution is created with the `if` keyword in JavaScript. In the simple case, we want some code to be executed if, and only if, a certain condition holds. We might, for example, want to show the square of the input only if the input is actually a number: ```{test: wrap} let theNumber = Number(prompt("Pick a number")); @@ -396,28 +265,15 @@ With this modification, if you enter "parrot", no output is shown. {{index [parentheses, statement]}} -The `if` keyword executes or skips a statement depending on the value -of a Boolean expression. The deciding expression is written after the -keyword, between parentheses, followed by the statement to -execute. +The `if` keyword executes or skips a statement depending on the value of a Boolean expression. The deciding expression is written after the keyword, between parentheses, followed by the statement to execute. {{index "Number.isNaN function"}} -The `Number.isNaN` function is a standard JavaScript function that -returns `true` only if the argument it is given is `NaN`. The `Number` -function happens to return `NaN` when you give it a string that -doesn't represent a valid number. Thus, the condition translates to -"unless `theNumber` is not-a-number, do this". +The `Number.isNaN` function is a standard JavaScript function that returns `true` only if the argument it is given is `NaN`. The `Number` function happens to return `NaN` when you give it a string that doesn't represent a valid number. Thus, the condition translates to "unless `theNumber` is not-a-number, do this". {{index grouping, "{} (block)", [braces, "block"]}} -The statement after the `if` is wrapped in braces (`{` and -`}`) in this example. The braces can be used to group any number of -statements into a single statement, called a _((block))_. You could -also have omitted them in this case, since they hold only a single -statement, but to avoid having to think about whether they are needed, most JavaScript programmers use them in every wrapped -statement like this. We'll mostly follow that convention in this book, -except for the occasional one-liner. +The statement after the `if` is wrapped in braces (`{` and `}`) in this example. The braces can be used to group any number of statements into a single statement, called a _((block))_. You could also have omitted them in this case, since they hold only a single statement, but to avoid having to think about whether they are needed, most JavaScript programmers use them in every wrapped statement like this. We'll mostly follow that convention in this book, except for the occasional one-liner. ``` if (1 + 1 == 2) console.log("It's true"); @@ -426,10 +282,7 @@ if (1 + 1 == 2) console.log("It's true"); {{index "else keyword"}} -You often won't just have code that executes when a condition holds -true, but also code that handles the other case. This alternate path -is represented by the second arrow in the diagram. You can use the `else` keyword, together with `if`, to create two separate, alternative -execution paths. +You often won't just have code that executes when a condition holds true, but also code that handles the other case. This alternate path is represented by the second arrow in the diagram. You can use the `else` keyword, together with `if`, to create two separate, alternative execution paths: ```{test: wrap} let theNumber = Number(prompt("Pick a number")); @@ -443,8 +296,7 @@ if (!Number.isNaN(theNumber)) { {{index ["if keyword", chaining]}} -If you have more than two paths to choose from, you can "chain" multiple `if`/`else` -pairs together. Here's an example: +If you have more than two paths to choose from, you can "chain" multiple `if`/`else` pairs together. Here's an example: ``` let num = Number(prompt("Pick a number")); @@ -458,22 +310,16 @@ if (num < 10) { } ``` -The program will first check whether `num` is less than 10. If it is, -it chooses that branch, shows `"Small"`, and is done. If it isn't, it -takes the `else` branch, which itself contains a second `if`. If the -second condition (`< 100`) holds, that means the number is between 10 -and 100, and `"Medium"` is shown. If it doesn't, the second and last -`else` branch is chosen. +The program will first check whether `num` is less than 10. If it is, it chooses that branch, shows `"Small"`, and is done. If it isn't, it takes the `else` branch, which itself contains a second `if`. If the second condition (`< 100`) holds, that means the number is at least 10 but below 100, and `"Medium"` is shown. If it doesn't, the second and last `else` branch is chosen. The schema for this program looks something like this: -{{figure {url: "img/controlflow-nested-if.svg", alt: "Nested if control flow", width: "4cm"}}} +{{figure {url: "img/controlflow-nested-if.svg", alt: "Diagram showing arrow that splits in two, with on the branches splitting again, before all branches rejoin again", width: "4cm"}}} {{id loops}} ## while and do loops -Consider a program that outputs all ((even number))s from 0 to 12. One -way to write this is as follows: +Consider a program that outputs all ((even number))s from 0 to 12. One way to write this is as follows: ``` console.log(0); @@ -487,20 +333,13 @@ console.log(12); {{index ["control flow", loop]}} -That works, but the idea of writing a program is to make something -_less_ work, not more. If we needed all even numbers less than 1,000, -this approach would be unworkable. What we need is a way to run a -piece of code multiple times. This form of control flow is called a -_((loop))_. +That works, but the idea of writing a program is to make something _less_ work, not more. If we needed all even numbers less than 1,000, this approach would be unworkable. What we need is a way to run a piece of code multiple times. This form of control flow is called a _((loop))_. -{{figure {url: "img/controlflow-loop.svg", alt: "Loop control flow",width: "4cm"}}} +{{figure {url: "img/controlflow-loop.svg", alt: "Diagram showing an arrow to a point which has a cyclic arrow going back to itself and another arrow going further", width: "4cm"}}} {{index [syntax, statement], "counter variable"}} -Looping control flow allows us to go back to some point in the program -where we were before and repeat it with our current program state. If -we combine this with a binding that counts, we can do something like -this: +Looping control flow allows us to go back to some point in the program where we were before and repeat it with our current program state. If we combine this with a binding that counts, we can do something like this: ``` let number = 0; @@ -515,28 +354,15 @@ while (number <= 12) { {{index "while loop", Boolean, [parentheses, statement]}} -A ((statement)) starting with the keyword `while` creates a loop. The -word `while` is followed by an ((expression)) in parentheses and -then a statement, much like `if`. The loop keeps entering that -statement as long as the expression produces a value that gives `true` -when converted to Boolean. +A ((statement)) starting with the keyword `while` creates a loop. The word `while` is followed by an ((expression)) in parentheses and then a statement, much like `if`. The loop keeps entering that statement as long as the expression produces a value that gives `true` when converted to Boolean. {{index [state, in binding], [binding, as state]}} -The `number` binding demonstrates the way a ((binding)) can track the -progress of a program. Every time the loop repeats, `number` gets a -value that is 2 more than its previous value. At the beginning of -every repetition, it is compared with the number 12 to decide whether -the program's work is finished. +The `number` binding demonstrates the way a ((binding)) can track the progress of a program. Every time the loop repeats, `number` gets a value that is 2 more than its previous value. At the beginning of every repetition, it is compared with the number 12 to decide whether the program's work is finished. {{index exponentiation}} -As an example that actually does something useful, we can now write a -program that calculates and shows the value of 2^10^ (2 to the 10th -power). We use two bindings: one to keep track of our result and one -to count how often we have multiplied this result by 2. The loop tests -whether the second binding has reached 10 yet and, if not, updates -both bindings. +As an example that actually does something useful, we can now write a program that calculates and shows the value of 2^10^ (2 to the 10th power). We use two bindings: one to keep track of our result and one to count how often we have multiplied this result by 2. The loop tests whether the second binding has reached 10 yet and, if not, updates both bindings. ``` let result = 1; @@ -549,53 +375,35 @@ console.log(result); // → 1024 ``` -The counter could also have started at `1` and checked for `<= 10`, -but for reasons that will become apparent in [Chapter -?](data#array_indexing), it is a good idea to get used to -counting from 0. +The counter could also have started at `1` and checked for `<= 10`, but for reasons that will become apparent in [Chapter ?](data#array_indexing), it is a good idea to get used to counting from 0. + +{{index "** operator"}} + +Note that JavaScript also has an operator for exponentiation (`2 ** 10`), which you would use to compute this in real code—but that would have ruined the example. {{index "loop body", "do loop", ["control flow", loop]}} -A `do` loop is a control structure similar to a `while` loop. It -differs only on one point: a `do` loop always executes its body at -least once, and it starts testing whether it should stop only after -that first execution. To reflect this, the test appears after the body -of the loop. +A `do` loop is a control structure similar to a `while` loop. It differs only on one point: a `do` loop always executes its body at least once, and it starts testing whether it should stop only after that first execution. To reflect this, the test appears after the body of the loop: ``` let yourName; do { yourName = prompt("Who are you?"); } while (!yourName); -console.log(yourName); +console.log("Hello " + yourName); ``` {{index [Boolean, "conversion to"], "! operator"}} -This program will force you to enter a name. It will ask again and -again until it gets something that is not an empty string. Applying -the `!` operator will convert a value to Boolean type before negating -it, and all strings except `""` convert to `true`. This means the loop -continues going round until you provide a non-empty name. +This program will force you to enter a name. It will ask again and again until it gets something that is not an empty string. Applying the `!` operator will convert a value to Boolean type before negating it, and all strings except `""` convert to `true`. This means the loop continues going round until you provide a non-empty name. ## Indenting Code {{index [code, "structure of"], [whitespace, indentation], "programming style"}} -In the examples, I've been adding spaces in front of statements that -are part of some larger statement. These spaces are not required—the computer -will accept the program just fine without them. In fact, even the -((line)) breaks in programs are optional. You could write a program as -a single long line if you felt like it. +In the examples, I've been adding spaces in front of statements that are part of some larger statement. These spaces are not required—the computer will accept the program just fine without them. In fact, even the ((line)) breaks in programs are optional. You could write a program as a single long line if you felt like it. -The role of this ((indentation)) inside ((block))s is to make the -structure of the code stand out. In code where new blocks are opened -inside other blocks, it can become hard to see where one block ends -and another begins. With proper indentation, the visual shape of a -program corresponds to the shape of the blocks inside it. I like to -use two spaces for every open block, but tastes differ—some people use -four spaces, and some people use ((tab character))s. The important -thing is that each new block adds the same amount of space. +The role of this ((indentation)) inside ((block))s is to make the structure of the code stand out to human readers. In code where new blocks are opened inside other blocks, it can become hard to see where one block ends and another begins. With proper indentation, the visual shape of a program corresponds to the shape of the blocks inside it. I like to use two spaces for every open block, but tastes differ—some people use four spaces, and some people use ((tab character))s. The important thing is that each new block adds the same amount of space. ``` if (false != true) { @@ -606,25 +414,17 @@ if (false != true) { } ``` -Most code ((editor)) programs[ (including the one in this book)]{if -interactive} will help by automatically indenting new lines the proper -amount. +Most code ((editor)) programs[ (including the one in this book)]{if interactive} will help by automatically indenting new lines the proper amount. ## for loops {{index [syntax, statement], "while loop", "counter variable"}} -Many loops follow the pattern shown in the `while` examples. First a -"counter" binding is created to track the progress of the loop. Then -comes a `while` loop, usually with a test expression that checks whether the -counter has reached its end value. At the end of the loop body, the -counter is updated to track progress. +Many loops follow the pattern shown in the `while` examples. First a "counter" binding is created to track the progress of the loop. Then comes a `while` loop, usually with a test expression that checks whether the counter has reached its end value. At the end of the loop body, the counter is updated to track progress. {{index "for loop", loop}} -Because this pattern is so common, JavaScript and similar languages -provide a slightly shorter and more comprehensive form, the `for` -loop. +Because this pattern is so common, JavaScript and similar languages provide a slightly shorter and more comprehensive form, the `for` loop: ``` for (let number = 0; number <= 12; number = number + 2) { @@ -637,19 +437,11 @@ for (let number = 0; number <= 12; number = number + 2) { {{index ["control flow", loop], state}} -This program is exactly equivalent to the -[earlier](program_structure#loops) even-number-printing example. The -only change is that all the ((statement))s that are related to the -"state" of the loop are grouped together after `for`. +This program is exactly equivalent to the [earlier](program_structure#loops) even-number-printing example. The only change is that all the ((statement))s that are related to the "state" of the loop are grouped together after `for`. {{index [binding, as state], [parentheses, statement]}} -The parentheses after a `for` keyword must contain two -((semicolon))s. The part before the first semicolon _initializes_ the -loop, usually by defining a binding. The second part is the -((expression)) that _checks_ whether the loop must continue. The final -part _updates_ the state of the loop after every iteration. In most -cases, this is shorter and clearer than a `while` construct. +The parentheses after a `for` keyword must contain two ((semicolon))s. The part before the first semicolon _initializes_ the loop, usually by defining a binding. The second part is the ((expression)) that _checks_ whether the loop must continue. The final part _updates_ the state of the loop after every iteration. In most cases, this is shorter and clearer than a `while` construct. {{index exponentiation}} @@ -668,12 +460,7 @@ console.log(result); {{index [loop, "termination of"], "break keyword"}} -Having the looping condition produce `false` is not the only way a -loop can finish. There is a special statement called `break` that has -the effect of immediately jumping out of the enclosing loop. - -This program illustrates the `break` statement. It finds the first number -that is both greater than or equal to 20 and divisible by 7. +Having the looping condition produce `false` is not the only way a loop can finish. The `break` statement has the effect of immediately jumping out of the enclosing loop. Its use is demonstrated in the following program, which finds the first number that is both greater than or equal to 20 and divisible by 7: ``` for (let current = 20; ; current = current + 1) { @@ -687,58 +474,43 @@ for (let current = 20; ; current = current + 1) { {{index "remainder operator", "% operator"}} -Using the remainder (`%`) operator is an easy way to test whether a -number is divisible by another number. If it is, the remainder of -their division is zero. +Using the remainder (`%`) operator is an easy way to test whether a number is divisible by another number. If it is, the remainder of their division is zero. {{index "for loop"}} -The `for` construct in the example does not have a part that checks -for the end of the loop. This means that the loop will never stop -unless the `break` statement inside is executed. +The `for` construct in the example does not have a part that checks for the end of the loop. This means that the loop will never stop unless the `break` statement inside is executed. -If you were to remove that `break` statement or you accidentally write -an end condition that always produces `true`, your program would get -stuck in an _((infinite loop))_. A program stuck in an infinite loop -will never finish running, which is usually a bad thing. +If you were to remove that `break` statement or you accidentally write an end condition that always produces `true`, your program would get stuck in an _((infinite loop))_. A program stuck in an infinite loop will never finish running, which is usually a bad thing. {{if interactive -If you create an infinite loop in one of the examples on these pages, -you'll usually be asked whether you want to stop the script after a -few seconds. If that fails, you will have to close the tab that you're -working in, or on some browsers close your whole browser, to recover. +If you create an infinite loop in one of the examples on these pages, you'll usually be asked whether you want to stop the script after a few seconds. If that fails, you will have to close the tab that you're working in to recover. if}} {{index "continue keyword"}} -The `continue` keyword is similar to `break`, in that it influences -the progress of a loop. When `continue` is encountered in a loop body, -control jumps out of the body and continues with the loop's next -iteration. +The `continue` keyword is similar to `break` in that it influences the progress of a loop. When `continue` is encountered in a loop body, control jumps out of the body and continues with the loop's next iteration. ## Updating bindings succinctly {{index assignment, "+= operator", "-= operator", "/= operator", "*= operator", [state, in binding], "side effect"}} -Especially when looping, a program often needs to "update" a binding -to hold a value based on that binding's previous value. +Especially when looping, a program often needs to "update" a binding to hold a value based on that binding's previous value. ```{test: no} counter = counter + 1; ``` -JavaScript provides a shortcut for this. +JavaScript provides a shortcut for this: ```{test: no} counter += 1; ``` -Similar shortcuts work for many other operators, such as `result *= 2` -to double `result` or `counter -= 1` to count downward. +Similar shortcuts work for many other operators, such as `result *= 2` to double `result` or `counter -= 1` to count downward. -This allows us to shorten our counting example a little more. +This allows us to further shorten our counting example: ``` for (let number = 0; number <= 12; number += 2) { @@ -748,8 +520,7 @@ for (let number = 0; number <= 12; number += 2) { {{index "++ operator", "-- operator"}} -For `counter += 1` and `counter -= 1`, there are even shorter -equivalents: `counter++` and `counter--`. +For `counter += 1` and `counter -= 1`, there are even shorter equivalents: `counter++` and `counter--`. ## Dispatching on a value with switch @@ -766,11 +537,7 @@ else defaultAction(); {{index "colon character", "switch keyword"}} -There is a construct called `switch` that is intended to express such -a "dispatch" in a more direct way. Unfortunately, the syntax -JavaScript uses for this (which it inherited from the C/Java line of -programming languages) is somewhat awkward—a chain of `if` statements -may look better. Here is an example: +There is a construct called `switch` that is intended to express such a "dispatch" in a more direct way. Unfortunately, the syntax JavaScript uses for this (which it inherited from the C/Java line of programming languages) is somewhat awkward—a chain of `if` statements may look better. Here is an example: ``` switch (prompt("What is the weather like?")) { @@ -790,25 +557,13 @@ switch (prompt("What is the weather like?")) { {{index fallthrough, "break keyword", "case keyword", "default keyword"}} -You may put any number of `case` labels inside the block opened by -`switch`. The program will start executing at the label that -corresponds to the value that `switch` was given, or at `default` if -no matching value is found. It will continue executing, even across -other labels, until it reaches a `break` statement. In some cases, -such as the `"sunny"` case in the example, this can be used to share -some code between cases (it recommends going outside for both sunny -and cloudy weather). But be careful—it is easy to forget such a -`break`, which will cause the program to execute code you do not want -executed. +You may put any number of `case` labels inside the block opened by `switch`. The program will start executing at the label that corresponds to the value that `switch` was given, or at `default` if no matching value is found. It will continue executing, even across other labels, until it reaches a `break` statement. In some cases, such as the `"sunny"` case in the example, this can be used to share some code between cases (it recommends going outside for both sunny and cloudy weather). Be careful, though—it is easy to forget such a `break`, which will cause the program to execute code you do not want executed. ## Capitalization {{index capitalization, [binding, naming], [whitespace, syntax]}} -Binding names may not contain spaces, yet it is often helpful to use -multiple words to clearly describe what the binding represents. These -are pretty much your choices for writing a binding name with several -words in it: +Binding names may not contain spaces, yet it is often helpful to use multiple words to clearly describe what the binding represents. These are pretty much your choices for writing a binding name with several words in it: ```{lang: null} fuzzylittleturtle @@ -819,38 +574,21 @@ fuzzyLittleTurtle {{index "camel case", "programming style", "underscore character"}} -The first style can be hard to read. I rather like the look of the -underscores, though that style is a little painful to type. The -((standard)) JavaScript functions, and most JavaScript programmers, -follow the bottom style—they capitalize every word except the first. -It is not hard to get used to little things like that, and code with -mixed naming styles can be jarring to read, so we follow this -((convention)). +The first style can be hard to read. I rather like the look of the underscores, though that style is a little painful to type. The ((standard)) JavaScript functions, and most JavaScript programmers, follow the final style—they capitalize every word except the first. It is not hard to get used to little things like that, and code with mixed naming styles can be jarring to read, so we follow this ((convention)). {{index "Number function", constructor}} -In a few cases, such as the `Number` function, the first letter of a -binding is also capitalized. This was done to mark this function as a -constructor. What a constructor is will become clear in [Chapter -?](object#constructors). For now, the important thing is not -to be bothered by this apparent lack of ((consistency)). +In a few cases, such as the `Number` function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. It will become clear what a constructor is in [Chapter ?](object#constructors). For now, the important thing is to not be bothered by this apparent lack of ((consistency)). ## Comments {{index readability}} -Often, raw code does not convey all the information you want a program -to convey to human readers, or it conveys it in such a cryptic way -that people might not understand it. At other times, you might just -want to include some related thoughts as part of your program. This is -what _((comment))s_ are for. +Often, raw code does not convey all the information you want a program to convey to human readers, or it conveys it in such a cryptic way that people might not understand it. At other times, you might just want to include some related thoughts as part of your program. This is what _((comment))s_ are for. {{index "slash character", "line comment"}} -A comment is a piece of text that is part of a program but is -completely ignored by the computer. JavaScript has two ways of writing -comments. To write a single-line comment, you can use two slash -characters (`//`) and then the comment text after it. +A comment is a piece of text that is part of a program but is completely ignored by the computer. JavaScript has two ways of writing comments. To write a single-line comment, you can use two slash characters (`//`) and then the comment text after it: ```{test: no} let accountBalance = calculateBalance(account); @@ -865,66 +603,41 @@ addToReport(accountBalance, report); {{index "block comment"}} -A `//` comment goes only to the end of the line. A section of text -between `/*` and `*/` will be ignored in its entirety, regardless of -whether it contains line breaks. This is useful for adding blocks of -information about a file or a chunk of program. +A `//` comment goes only to the end of the line. A section of text between `/*` and `*/` will be ignored in its entirety, regardless of whether it contains line breaks. This is useful for adding blocks of information about a file or a chunk of program: ``` /* - I first found this number scrawled on the back of an old notebook. - Since then, it has often dropped by, showing up in phone numbers - and the serial numbers of products that I've bought. It obviously - likes me, so I've decided to keep it. + I first found this number scrawled on the back of an old + notebook. Since then, it has often dropped by, showing up in + phone numbers and the serial numbers of products that I've + bought. It obviously likes me, so I've decided to keep it. */ const myNumber = 11213; ``` ## Summary -You now know that a program is built out of statements, which -themselves sometimes contain more statements. Statements tend to -contain expressions, which themselves can be built out of smaller -expressions. +You now know that a program is built out of statements, which themselves sometimes contain more statements. Statements tend to contain expressions, which themselves can be built out of smaller expressions. -Putting statements after one another gives you a program that is -executed from top to bottom. You can introduce disturbances in the -flow of control by using conditional (`if`, `else`, and `switch`) and -looping (`while`, `do`, and `for`) statements. +Putting statements after one another gives you a program that is executed from top to bottom. You can introduce disturbances in the flow of control by using conditional (`if`, `else`, and `switch`) and looping (`while`, `do`, and `for`) statements. -Bindings can be used to file pieces of data under a name, and they are -useful for tracking state in your program. The environment is the set -of bindings that are defined. JavaScript systems always put a number -of useful standard bindings into your environment. +Bindings can be used to file pieces of data under a name, and they are useful for tracking state in your program. The environment is the set of bindings that are defined. JavaScript systems always put a number of useful standard bindings into your environment. -Functions are special values that encapsulate a piece of program. You -can invoke them by writing `functionName(argument1, argument2)`. Such -a function call is an expression and may produce a value. +Functions are special values that encapsulate a piece of program. You can invoke them by writing `functionName(argument1, argument2)`. Such a function call is an expression and may produce a value. ## Exercises {{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}. Full solutions to the exercises are -not included in this book, but you can find them 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. +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. ### Looping a triangle {{index "triangle (exercise)"}} -Write a ((loop)) that makes seven calls to `console.log` to output the -following triangle: +Write a ((loop)) that makes seven calls to `console.log` to output the following triangle: ```{lang: null} # @@ -938,8 +651,7 @@ following triangle: {{index [string, length]}} -It may be useful to know that you can find the length of a string by -writing `.length` after it. +It may be useful to know that you can find the length of a string by writing `.length` after it. ``` let abc = "abc"; @@ -949,8 +661,7 @@ console.log(abc.length); {{if interactive -Most exercises contain a piece of code that you can modify to solve -the exercise. Remember that you can click code blocks to edit them. +Most exercises contain a piece of code that you can modify to solve the exercise. Remember that you can click code blocks to edit them. ``` // Your code here. @@ -961,15 +672,9 @@ if}} {{index "triangle (exercise)"}} -You can start with a program that prints out the numbers 1 to 7, which -you can derive by making a few modifications to the [even number -printing example](program_structure#loops) given earlier in the -chapter, where the `for` loop was introduced. +You can start with a program that prints out the numbers 1 to 7, which you can derive by making a few modifications to the [even number printing example](program_structure#loops) given earlier in the chapter, where the `for` loop was introduced. -Now consider the equivalence between numbers and strings of hash -characters. You can go from 1 to 2 by adding 1 (`+= 1`). You can go -from `"#"` to `"##"` by adding a character (`+= "#"`). Thus, your -solution can closely follow the number-printing program. +Now consider the equivalence between numbers and strings of hash characters. You can go from 1 to 2 by adding 1 (`+= 1`). You can go from `"#"` to `"##"` by adding a character (`+= "#"`). Thus, your solution can closely follow the number-printing program. hint}} @@ -977,18 +682,11 @@ hint}} {{index "FizzBuzz (exercise)", loop, "conditional execution"}} -Write a program that uses `console.log` to print all the numbers from -1 to 100, with two exceptions. For numbers divisible by 3, print -`"Fizz"` instead of the number, and for numbers divisible by 5 (and -not 3), print `"Buzz"` instead. +Write a program that uses `console.log` to print all the numbers from 1 to 100, with two exceptions. For numbers divisible by 3, print `"Fizz"` instead of the number, and for numbers divisible by 5 (and not 3), print `"Buzz"` instead. -When you have that working, modify your program to print `"FizzBuzz"` -for numbers that are divisible by both 3 and 5 (and still print -`"Fizz"` or `"Buzz"` for numbers divisible by only one of those). +When you have that working, modify your program to print `"FizzBuzz"` for numbers that are divisible by both 3 and 5 (and still print `"Fizz"` or `"Buzz"` for numbers divisible by only one of those). -(This is actually an ((interview question)) that has been claimed to -weed out a significant percentage of programmer candidates. So if you -solved it, your labor market value just went up.) +(This is actually an ((interview question)) that has been claimed to weed out a significant percentage of programmer candidates. So if you solved it, your labor market value just went up.) {{if interactive ``` @@ -1000,22 +698,13 @@ if}} {{index "FizzBuzz (exercise)", "remainder operator", "% operator"}} -Going over the numbers is clearly a looping job, and selecting what to -print is a matter of conditional execution. Remember the trick of -using the remainder (`%`) operator for checking whether a number is -divisible by another number (has a remainder of zero). +Going over the numbers is clearly a looping job, and selecting what to print is a matter of conditional execution. Remember the trick of using the remainder (`%`) operator for checking whether a number is divisible by another number (has a remainder of zero). -In the first version, there are three possible outcomes for every -number, so you'll have to create an `if`/`else if`/`else` chain. +In the first version, there are three possible outcomes for every number, so you'll have to create an `if`/`else if`/`else` chain. {{index "|| operator", ["if keyword", chaining]}} -The second version of the program has a straightforward solution and a -clever one. The simple solution is to add another conditional "branch" to -precisely test the given condition. For the clever solution, build up a -string containing the word or words to output and print either this -word or the number if there is no word, potentially by making good use -of the `||` operator. +The second version of the program has a straightforward solution and a clever one. The simple solution is to add another conditional "branch" to precisely test the given condition. For the clever solution, build up a string containing the word or words to output and print either this word or the number if there is no word, potentially by making good use of the `||` operator. hint}} @@ -1023,10 +712,7 @@ hint}} {{index "chessboard (exercise)", loop, [nesting, "of loops"], "newline character"}} -Write a program that creates a string that represents an 8×8 grid, -using newline characters to separate lines. At each position of the -grid there is either a space or a "#" character. The characters should -form a chessboard. +Write a program that creates a string that represents an 8×8 grid, using newline characters to separate lines. At each position of the grid there is either a space or a "#" character. The characters should form a chessboard. Passing this string to `console.log` should show something like this: @@ -1041,9 +727,7 @@ Passing this string to `console.log` should show something like this: # # # # ``` -When you have a program that generates this pattern, define a -binding `size = 8` and change the program so that it works for -any `size`, outputting a grid of the given width and height. +When you have a program that generates this pattern, define a binding `size = 8` and change the program so that it works for any `size`, outputting a grid of the given width and height. {{if interactive ``` @@ -1055,26 +739,16 @@ if}} {{index "chess board (exercise)"}} -You can build the string by starting with an empty one (`""`) and -repeatedly adding characters. A newline character is written `"\n"`. +You can build the string by starting with an empty one (`""`) and repeatedly adding characters. A newline character is written `"\n"`. {{index [nesting, "of loops"], [braces, "block"]}} -To work with two ((dimensions)), you will need a ((loop)) inside of a -loop. Put braces around the bodies of both loops to make it -easy to see where they start and end. Try to properly indent these -bodies. The order of the loops must follow the order in which we build -up the string (line by line, left to right, top to bottom). So the -outer loop handles the lines, and the inner loop handles the characters -on a line. +To work with two ((dimensions)), you will need a ((loop)) inside of a loop. Put braces around the bodies of both loops to make it easy to see where they start and end. Try to properly indent these bodies. The order of the loops must follow the order in which we build up the string (line by line, left to right, top to bottom). So the outer loop handles the lines, and the inner loop handles the characters on a line. {{index "counter variable", "remainder operator", "% operator"}} -You'll need two bindings to track your progress. To know whether to -put a space or a hash sign at a given position, you could test whether -the sum of the two counters is even (`% 2`). +You'll need two bindings to track your progress. To know whether to put a space or a hash sign at a given position, you could test whether the sum of the two counters is even (`% 2`). -Terminating a line by adding a newline character must happen after the -line has been built up, so do this after the inner loop but inside the outer loop. +Terminating a line by adding a newline character must happen after the line has been built up, so do this after the inner loop but inside the outer loop. hint}} diff --git a/03_functions.md b/03_functions.md index 730ec57a3..9f5ce287f 100644 --- a/03_functions.md +++ b/03_functions.md @@ -2,44 +2,29 @@ {{quote {author: "Donald Knuth", chapter: true} -People think that computer science is the art of geniuses but the -actual reality is the opposite, just many people doing things that -build on each other, like a wall of mini stones. +People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones. quote}} {{index "Knuth, Donald"}} -{{figure {url: "img/chapter_picture_3.jpg", alt: "Picture of fern leaves with a fractal shape", chapter: framed}}} +{{figure {url: "img/chapter_picture_3.jpg", alt: "Illustration of fern leaves with a fractal shape, bees in the background", chapter: framed}}} {{index function, [code, "structure of"]}} -Functions are the bread and butter of JavaScript programming. The -concept of wrapping a piece of program in a value has many uses. It -gives us a way to structure larger programs, to reduce repetition, to -associate names with subprograms, and to isolate these subprograms -from each other. +Functions are one of the most central tools in JavaScript programming. The concept of wrapping a piece of program in a value has many uses. It gives us a way to structure larger programs, to reduce repetition, to associate names with subprograms, and to isolate these subprograms from each other. -The most obvious application of functions is defining new -((vocabulary)). Creating new words in prose is usually bad style. But -in programming, it is indispensable. +The most obvious application of functions is defining new ((vocabulary)). Creating new words in prose is usually bad style, but in programming, it is indispensable. {{index abstraction, vocabulary}} -Typical adult English speakers have some 20,000 words in their -vocabulary. Few programming languages come with 20,000 commands built -in. And the vocabulary that _is_ available tends to be more precisely -defined, and thus less flexible, than in human language. Therefore, we -usually _have_ to introduce new concepts to avoid repeating ourselves -too much. +Typical adult English speakers have some 20,000 words in their vocabulary. Few programming languages come with 20,000 commands built in. And the vocabulary that _is_ available tends to be more precisely defined, and thus less flexible, than in human language. Therefore, we _have_ to introduce new words to avoid excessive verbosity. ## Defining a function {{index "square example", [function, definition], [binding, definition]}} -A function definition is a regular binding where the value of the -binding is a function. For example, this code defines `square` to -refer to a function that produces the square of a given number: +A function definition is a regular binding where the value of the binding is a function. For example, this code defines `square` to refer to a function that produces the square of a given number: ``` const square = function(x) { @@ -53,18 +38,11 @@ console.log(square(12)); {{indexsee "curly braces", braces}} {{index [braces, "function body"], block, [syntax, function], "function keyword", [function, body], [function, "as value"], [parentheses, arguments]}} -A function is created with an expression that starts with the keyword -`function`. Functions have a set of _((parameter))s_ (in this case, -only `x`) and a _body_, which contains the statements that are to be -executed when the function is called. The function body of a function -created this way must always be wrapped in braces, even when it -consists of only a single ((statement)). +A function is created with an expression that starts with the keyword `function`. Functions have a set of _((parameter))s_ (in this case, only `x`) and a _body_, which contains the statements that are to be executed when the function is called. The body of a function created this way must always be wrapped in braces, even when it consists of only a single ((statement)). -{{index "power example"}} +{{index "roundTo example"}} -A function can have multiple parameters or no parameters at all. In -the following example, `makeNoise` does not list any parameter names, -whereas `power` lists two: +A function can have multiple parameters or no parameters at all. In the following example, `makeNoise` does not list any parameter names, whereas `roundTo` (which rounds `n` to the nearest multiple of `step`) lists two: ``` const makeNoise = function() { @@ -74,87 +52,49 @@ const makeNoise = function() { makeNoise(); // → Pling! -const power = function(base, exponent) { - let result = 1; - for (let count = 0; count < exponent; count++) { - result *= base; - } - return result; +const roundTo = function(n, step) { + let remainder = n % step; + return n - remainder + (remainder < step / 2 ? 0 : step); }; -console.log(power(2, 10)); -// → 1024 +console.log(roundTo(23, 10)); +// → 20 ``` {{index "return value", "return keyword", undefined}} -Some functions produce a value, such as `power` and `square`, and some -don't, such as `makeNoise`, whose only result is a ((side effect)). A -`return` statement determines the value the function returns. When -control comes across such a statement, it immediately jumps out of the -current function and gives the returned value to the code that called -the function. A `return` keyword without an expression after it will -cause the function to return `undefined`. Functions that don't have a -`return` statement at all, such as `makeNoise`, similarly return -`undefined`. +Some functions, such as `roundTo` and `square`, produce a value, and some don't, such as `makeNoise`, whose only result is a ((side effect)). A `return` statement determines the value the function returns. When control comes across such a statement, it immediately jumps out of the current function and gives the returned value to the code that called the function. A `return` keyword without an expression after it will cause the function to return `undefined`. Functions that don't have a `return` statement at all, such as `makeNoise`, similarly return `undefined`. {{index parameter, [function, application], [binding, "from parameter"]}} -Parameters to a function behave like regular bindings, but their -initial values are given by the _caller_ of the function, not the code -in the function itself. +Parameters to a function behave like regular bindings, but their initial values are given by the _caller_ of the function, not the code in the function itself. ## Bindings and scopes {{indexsee "top-level scope", "global scope"}} {{index "var keyword", "global scope", [binding, global], [binding, "scope of"]}} -Each binding has a _((scope))_, which is the part of the program -in which the binding is visible. For bindings defined outside of any -function or block, the scope is the whole program—you can refer to -such bindings wherever you want. These are called _global_. +Each binding has a _((scope))_, which is the part of the program in which the binding is visible. For bindings defined outside of any function, block, or module (see [Chapter ?](modules)), the scope is the whole program—you can refer to such bindings wherever you want. These are called _global_. {{index "local scope", [binding, local]}} -But bindings created for function ((parameter))s or declared inside a -function can be referenced only in that function, so they are known as -_local_ bindings. Every time the function is called, new instances of these -bindings are created. This provides some isolation between -functions—each function call acts in its own little world (its local -environment) and can often be understood without knowing a lot about -what's going on in the global environment. +Bindings created for function ((parameter))s or declared inside a function can be referenced only in that function, so they are known as _local_ bindings. Every time the function is called, new instances of these bindings are created. This provides some isolation between functions—each function call acts in its own little world (its local environment) and can often be understood without knowing a lot about what's going on in the global environment. {{index "let keyword", "const keyword", "var keyword"}} -Bindings declared with `let` and `const` are in fact local to the -_((block))_ that they are declared in, so if you create one of those -inside of a loop, the code before and after the loop cannot "see" it. -In pre-2015 JavaScript, only functions created new scopes, so -old-style bindings, created with the `var` keyword, are visible -throughout the whole function that they appear in—or throughout the -global scope, if they are not in a function. +Bindings declared with `let` and `const` are in fact local to the _((block))_ in which they are declared, so if you create one of those inside of a loop, the code before and after the loop cannot "see" it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the `var` keyword, are visible throughout the whole function in which they appear—or throughout the global scope, if they are not in a function. ``` -let x = 10; +let x = 10; // global if (true) { - let y = 20; - var z = 30; - console.log(x + y + z); - // → 60 + let y = 20; // local to block + var z = 30; // also global } -// y is not visible here -console.log(x + z); -// → 40 ``` {{index [binding, visibility]}} -Each ((scope)) can "look out" into the scope around it, so `x` is -visible inside the block in the example. The exception is when -multiple bindings have the same name—in that case, code can see only -the innermost one. For example, when the code inside the `halve` -function refers to `n`, it is seeing its _own_ `n`, not the global -`n`. +Each ((scope)) can "look out" into the scope around it, so `x` is visible inside the block in the example. The exception is when multiple bindings have the same name—in that case, code can see only the innermost one. For example, when the code inside the `halve` function refers to `n`, it is seeing its _own_ `n`, not the global `n`. ``` const halve = function(n) { @@ -170,18 +110,15 @@ console.log(n); {{id scoping}} -### Nested scope +## Nested scope {{index [nesting, "of functions"], [nesting, "of scope"], scope, "inner function", "lexical scoping"}} -JavaScript distinguishes not just _global_ and _local_ -bindings. Blocks and functions can be created inside other blocks and -functions, producing multiple degrees of locality. +JavaScript distinguishes not just global and local bindings. Blocks and functions can be created inside other blocks and functions, producing multiple degrees of locality. {{index "landscape example"}} -For example, this function—which outputs the ingredients needed to -make a batch of hummus—has another function inside it: +For example, this function—which outputs the ingredients needed to make a batch of hummus—has another function inside it: ``` const hummus = function(factor) { @@ -203,31 +140,19 @@ const hummus = function(factor) { {{index [function, scope], scope}} -The code inside the `ingredient` function can see the `factor` binding -from the outer function. But its local bindings, such as `unit` or -`ingredientAmount`, are not visible in the outer function. +The code inside the `ingredient` function can see the `factor` binding from the outer function, but its local bindings, such as `unit` or `ingredientAmount`, are not visible in the outer function. -The set of bindings visible inside a block is determined by the place of -that block in the program text. Each local scope can also see all the -local scopes that contain it, and all scopes can see the global scope. -This approach to binding visibility is called _((lexical scoping))_. +The set of bindings visible inside a block is determined by the place of that block in the program text. Each local scope can also see all the local scopes that contain it, and all scopes can see the global scope. This approach to binding visibility is called _((lexical scoping))_. ## Functions as values {{index [function, "as value"], [binding, definition]}} -A function binding usually simply acts as a name for a specific piece -of the program. Such a binding is defined once and never changed. This -makes it easy to confuse the function and its name. +A function binding usually simply acts as a name for a specific piece of the program. Such a binding is defined once and never changed. This makes it easy to confuse the function and its name. {{index [binding, assignment]}} -But the two are different. A function value can do all the things that -other values can do—you can use it in arbitrary ((expression))s, not -just call it. It is possible to store a function value in a new -binding, pass it as an argument to a function, and so on. Similarly, a -binding that holds a function is still just a regular binding and can, -if not constant, be assigned a new value, like so: +But the two are different. A function value can do all the things that other values can do—you can use it in arbitrary ((expression))s, not just call it. It is possible to store a function value in a new binding, pass it as an argument to a function, and so on. Similarly, a binding that holds a function is still just a regular binding and can, if not constant, be assigned a new value, like so: ```{test: no} let launchMissiles = function() { @@ -240,16 +165,13 @@ if (safeMode) { {{index [function, "higher-order"]}} -In [Chapter ?](higher_order), we will discuss the interesting things -that can be done by passing around function values to other functions. +In [Chapter ?](higher_order), we'll discuss the interesting things that we can do by passing function values to other functions. ## Declaration notation {{index [syntax, function], "function keyword", "square example", [function, definition], [function, declaration]}} -There is a slightly shorter way to create a function binding. When the -`function` keyword is used at the start of a statement, it works -differently. +There is a slightly shorter way to create a function binding. When the `function` keyword is used at the start of a statement, it works differently: ```{test: wrap} function square(x) { @@ -259,9 +181,7 @@ function square(x) { {{index future, "execution order"}} -This is a function _declaration_. The statement defines the binding -`square` and points it at the given function. It is slightly easier -to write and doesn't require a semicolon after the function. +This is a function _declaration_. The statement defines the binding `square` and points it at the given function. It is slightly easier to write and doesn't require a semicolon after the function. There is one subtlety with this form of function definition. @@ -273,47 +193,28 @@ function future() { } ``` -The preceding code works, even though the function is defined _below_ the code -that uses it. Function declarations are not part of the regular -top-to-bottom flow of control. They are conceptually moved to the top -of their scope and can be used by all the code in that scope. This is -sometimes useful because it offers the freedom to order code in a -way that seems meaningful, without worrying about having to define all -functions before they are used. +The preceding code works, even though the function is defined _below_ the code that uses it. Function declarations are not part of the regular top-to-bottom flow of control. They are conceptually moved to the top of their scope and can be used by all the code in that scope. This is sometimes useful because it offers the freedom to order code in a way that seems the clearest, without worrying about having to define all functions before they are used. ## Arrow functions {{index function, "arrow function"}} -There's a third notation for functions, which looks very different -from the others. Instead of the `function` keyword, it uses an arrow -(`=>`) made up of an equal sign and a greater-than character (not to be -confused with the greater-than-or-equal operator, which is written -`>=`). +There's a third notation for functions, which looks very different from the others. Instead of the `function` keyword, it uses an arrow (`=>`) made up of an equal sign and a greater-than character (not to be confused with the greater-than-or-equal operator, which is written `>=`): ```{test: wrap} -const power = (base, exponent) => { - let result = 1; - for (let count = 0; count < exponent; count++) { - result *= base; - } - return result; +const roundTo = (n, step) => { + let remainder = n % step; + return n - remainder + (remainder < step / 2 ? 0 : step); }; ``` {{index [function, body]}} -The arrow comes _after_ the list of parameters and is followed by the -function's body. It expresses something like "this input (the -((parameter))s) produces this result (the body)". +The arrow comes _after_ the list of parameters and is followed by the function's body. It expresses something like "this input (the ((parameter))s) produces this result (the body)". {{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; }; @@ -322,8 +223,7 @@ const square2 = x => x * x; {{index [parentheses, arguments]}} -When an arrow function has no parameters at all, its parameter list is -just an empty set of parentheses. +When an arrow function has no parameters at all, its parameter list is just an empty set of parentheses. ``` const horn = () => { @@ -333,12 +233,7 @@ const horn = () => { {{index verbosity}} -There's no deep reason to have both arrow functions and -`function` expressions in the language. Apart from a minor detail, -which we'll discuss in [Chapter ?](object), they do the same thing. -Arrow functions were added in 2015, mostly to make it possible to -write small function expressions in a less verbose way. We'll be using -them a lot in [Chapter ?](higher_order). +There's no deep reason to have both arrow functions and `function` expressions in the language. Apart from a minor detail, which we'll discuss in [Chapter ?](object), they do the same thing. Arrow functions were added in 2015, mostly to make it possible to write small function expressions in a less verbose way. We'll use them often in [Chapter ?](higher_order). {{id stack}} @@ -347,9 +242,7 @@ them a lot in [Chapter ?](higher_order). {{indexsee stack, "call stack"}} {{index "call stack", [function, application]}} -The way control flows through functions is somewhat involved. Let's -take a closer look at it. Here is a simple program that makes a few -function calls: +The way control flows through functions is somewhat involved. Let's take a closer look at it. Here is a simple program that makes a few function calls: ``` function greet(who) { @@ -361,48 +254,29 @@ console.log("Bye"); {{index ["control flow", functions], "execution order", "console.log"}} -A run through this program goes roughly like this: the call to `greet` -causes control to jump to the start of that function (line 2). The -function calls `console.log`, which takes control, does its job, and -then returns control to line 2. There it reaches the end of the -`greet` function, so it returns to the place that called it, which is -line 4. The line after that calls `console.log` again. After that -returns, the program reaches its end. +A run through this program goes roughly like this: the call to `greet` causes control to jump to the start of that function (line 2). The function calls `console.log`, which takes control, does its job, and then returns control to line 2. There, it reaches the end of the `greet` function, so it returns to the place that called it—line 4. The line after that calls `console.log` again. After that returns, the program reaches its end. We could show the flow of control schematically like this: ```{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 ``` {{index "return keyword", [memory, call stack]}} -Because a function has to jump back to the place that called it when -it returns, the computer must remember the context from which the call -happened. In one case, `console.log` has to return to the `greet` -function when it is done. In the other case, it returns to the end of -the program. +Because a function has to jump back to the place that called it when it returns, the computer must remember the context from which the call happened. In one case, `console.log` has to return to the `greet` function when it is done. In the other case, it returns to the end of the program. -The place where the computer stores this context is the _((call -stack))_. Every time a function is called, the current context is -stored on top of this stack. When a function returns, it removes the -top context from the stack and uses that context to continue execution. +The place where the computer stores this context is the _((call stack))_. Every time a function is called, the current context is stored on top of this stack. When a function returns, it removes the top context from the stack and uses that context to continue execution. {{index "infinite loop", "stack overflow", recursion}} -Storing this stack requires space in the computer's memory. When the -stack grows too big, the computer will fail with a message like "out -of stack space" or "too much recursion". The following code -illustrates this by asking the computer a really hard question that -causes an infinite back-and-forth between two functions. Rather, it -_would_ be infinite, if the computer had an infinite stack. As it is, -we will run out of space, or "blow the stack". +Storing this stack requires space in the computer's memory. When the stack grows too big, the computer will fail with a message like "out of stack space" or "too much recursion". The following code illustrates this by asking the computer a really hard question that causes an infinite back-and-forth between two functions. Or rather, it _would_ be infinite, if the computer had an infinite stack. As it is, we will run out of space, or "blow the stack". ```{test: no} function chicken() { @@ -427,25 +301,13 @@ console.log(square(4, true, "hedgehog")); // → 16 ``` -We defined `square` with only one ((parameter)). Yet when we call it -with three, the language doesn't complain. It ignores the extra -arguments and computes the square of the first one. +We defined `square` with only one ((parameter)). Yet when we call it with three, the language doesn't complain. It ignores the extra arguments and computes the square of the first one. {{index undefined}} -JavaScript is extremely broad-minded about the number of arguments you -pass to a function. If you pass too many, the extra ones are ignored. -If you pass too few, the missing parameters get assigned the value -`undefined`. +JavaScript is extremely broad-minded about the number of arguments you can pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters are assigned the value `undefined`. -The downside of this is that it is possible—likely, even—that you'll -accidentally pass the wrong number of arguments to functions. And no -one will tell you about it. - -The upside is that this behavior can be used to allow a function to be -called with different numbers of arguments. For example, this `minus` -function tries to imitate the `-` operator by acting on either one or -two arguments: +The downside of this is that it is possible—likely, even—that you'll accidentally pass the wrong number of arguments to functions. And no one will tell you about it. The upside is that you can use this behavior to allow a function to be called with different numbers of arguments. For example, this `minus` function tries to imitate the `-` operator by acting on either one or two arguments: ``` function minus(a, b) { @@ -459,41 +321,26 @@ console.log(minus(10, 5)); // → 5 ``` -{{id power}} -{{index "optional argument", "default value", parameter, ["= operator", "for default value"]}} - -If you write an `=` operator after -a parameter, followed by an expression, the value of that expression -will replace the argument when it is not given. +{{id roundTo}} +{{index "optional argument", "default value", parameter, ["= operator", "for default value"] "roundTo example"}} -{{index "power example"}} - -For example, this version of `power` makes its second argument -optional. If you don't provide it or pass the value `undefined`, it will default to two, and the -function will behave like `square`. +If you write an `=` operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given. For example, this version of `roundTo` makes its second argument optional. If you don't provide it or pass the value `undefined`, it will default to one: ```{test: wrap} -function power(base, exponent = 2) { - let result = 1; - for (let count = 0; count < exponent; count++) { - result *= base; - } - return result; -} +function roundTo(n, step = 1) { + let remainder = n % step; + return n - remainder + (remainder < step / 2 ? 0 : step); +}; -console.log(power(4)); -// → 16 -console.log(power(2, 6)); -// → 64 +console.log(roundTo(4.5)); +// → 5 +console.log(roundTo(4.5, 2)); +// → 4 ``` {{index "console.log"}} -In the [next chapter](data#rest_parameters), we will see a way in -which a function body can get at the whole list of arguments it was -passed. This is helpful because it makes it possible for a function to -accept any number of arguments. For example, `console.log` does -this—it outputs all of the values it is given. +The [next chapter](data#rest_parameters) will introduce a way in which a function body can get at the whole list of arguments it was passed. This is helpful because it allows a function to accept any number of arguments. For example, `console.log` does this, outputting all the values it is given: ``` console.log("C", "O", 2); @@ -504,14 +351,9 @@ console.log("C", "O", 2); {{index "call stack", "local binding", [function, "as value"], scope}} -The ability to treat functions as values, combined with the fact that -local bindings are re-created every time a function is called, brings -up an interesting question. What happens to local bindings when the -function call that created them is no longer active? +The ability to treat functions as values, combined with the fact that local bindings are 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) { @@ -527,22 +369,13 @@ console.log(wrap2()); // → 2 ``` -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 can't trample on one another's local bindings. +This is allowed and works as you'd hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls don't affect each other's local bindings. -This feature—being able to reference a specific instance of a local -binding in an enclosing scope—is called _((closure))_. A function that -references bindings from local scopes around it is called _a_ closure. This behavior -not only frees you from having to worry about lifetimes of bindings -but also makes it possible to use function values in some creative -ways. +This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called _((closure))_. A function that references bindings from local scopes around it is called _a_ closure. This behavior not only frees you from having to worry about the lifetimes of bindings but also makes it possible to use function values in some creative ways. {{index "multiplier function"}} -With a slight change, we can turn the previous example into a way to -create functions that multiply by an arbitrary amount. +With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount. ``` function multiplier(factor) { @@ -556,31 +389,19 @@ console.log(twice(5)); {{index [binding, "from parameter"]}} -The explicit `local` binding from the `wrapValue` example isn't really -needed since a parameter is itself a local binding. +The explicit `local` binding from the `wrapValue` example isn't really needed since a parameter is itself a local binding. {{index [function, "model of"]}} -Thinking about programs like this takes some practice. A good mental -model is to think of function values as containing both the code in -their body and the environment in which they are created. When called, -the function body sees the environment in which it was created, not the -environment in which it is called. +Thinking about programs like this takes some practice. A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called. -In the example, `multiplier` is called and creates an environment in -which its `factor` parameter is bound to 2. The function value it -returns, which is stored in `twice`, remembers this environment. So -when that is called, it multiplies its argument by 2. +In the previous example, `multiplier` is called and creates an environment in which its `factor` parameter is bound to 2. The function value it returns, which is stored in `twice`, remembers this environment so that when that is called, it multiplies its argument by 2. ## Recursion {{index "power example", "stack overflow", recursion, [function, application]}} -It is perfectly okay for a function to call itself, as long as it -doesn't do it so often that it overflows the stack. A function that calls -itself is called _recursive_. Recursion allows some functions to be -written in a different style. Take, for example, this alternative -implementation of `power`: +It is perfectly okay for a function to call itself, as long as it doesn't do it so often that it overflows the stack. A function that calls itself is called _recursive_. Recursion allows some functions to be written in a different style. Take, for example, this `power` function, which does the same as the `**` (exponentiation) operator: ```{test: wrap} function power(base, exponent) { @@ -597,67 +418,34 @@ console.log(power(2, 3)); {{index loop, readability, mathematics}} -This is rather close to the way mathematicians define exponentiation -and arguably describes the concept more clearly than the looping -variant. The function calls itself multiple times with ever smaller -exponents to achieve the repeated multiplication. +This is rather close to the way mathematicians define exponentiation and arguably describes the concept more clearly than the loop we used in [Chapter ?](program_structure). The function calls itself multiple times with ever smaller exponents to achieve the repeated multiplication. {{index [function, application], efficiency}} -But this implementation has one problem: in typical JavaScript -implementations, it's about three times slower than the looping version. -Running through a simple loop is generally cheaper than calling a -function multiple times. +However, this implementation has one problem: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. {{index optimization}} -The dilemma of speed versus ((elegance)) is an interesting one. You -can see it as a kind of continuum between human-friendliness and -machine-friendliness. Almost any program can be made faster by making -it bigger and more convoluted. The programmer has to decide on an -appropriate balance. +The dilemma of speed versus ((elegance)) is an interesting one. You can see it as a kind of continuum between human-friendliness and machine-friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer has to find an appropriate balance. -In the case of the `power` function, the inelegant (looping) version -is still fairly simple and easy to read. It doesn't make much sense to -replace it with the recursive version. Often, though, a program deals -with such complex concepts that giving up some efficiency in order to -make the program more straightforward is helpful. +In the case of the `power` function, an inelegant (looping) version is still fairly simple and easy to read. It doesn't make much sense to replace it with a recursive function. Often, though, a program deals with such complex concepts that giving up some efficiency in order to make the program more straightforward is helpful. {{index profiling}} -Worrying about efficiency can be a distraction. It's yet another -factor that complicates program design, and when you're doing -something that's already difficult, that extra thing to worry about -can be paralyzing. +Worrying about efficiency can be a distraction. It's yet another factor that complicates program design, and when you're doing something that's already difficult, that extra thing to worry about can be paralyzing. {{index "premature optimization"}} -Therefore, always start by writing something that's correct and easy -to understand. If you're worried that it's too slow—which it usually -isn't since most code simply isn't executed often enough to take any -significant amount of time—you can measure afterward and improve it -if necessary. +Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. {{index "branching recursion"}} -Recursion is not always just an inefficient alternative to looping. -Some problems really are easier to solve with recursion than with -loops. Most often these are problems that require exploring or -processing several "branches", each of which might branch out again -into even more branches. +Recursion is not always just an inefficient alternative to looping. Some problems really are easier to solve with recursion than with loops. Most often these are problems that require exploring or processing several "branches", each of which might branch out again into even more branches. {{id recursive_puzzle}} {{index recursion, "number puzzle example"}} -Consider this puzzle: by starting from the number 1 and repeatedly -either adding 5 or multiplying by 3, an infinite set of numbers -can be produced. How would you write a function that, given a number, -tries to find a sequence of such additions and multiplications that -produces that number? - -For example, the number 13 could be reached by first multiplying by 3 -and then adding 5 twice, whereas the number 15 cannot be reached at -all. +Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produces that number? For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all. Here is a recursive solution: @@ -669,7 +457,7 @@ function findSolution(target) { } else if (current > target) { return null; } else { - return find(current + 5, `(${history} + 5)`) || + return find(current + 5, `(${history} + 5)`) ?? find(current * 3, `(${history} * 3)`); } } @@ -680,38 +468,19 @@ console.log(findSolution(24)); // → (((1 * 3) + 5) * 3) ``` -Note that this program doesn't necessarily find the _shortest_ -sequence of operations. It is satisfied when it finds any sequence at -all. - -It is okay if you don't see how it works right away. Let's work -through it, since it makes for a great exercise in recursive thinking. - -The inner function `find` does the actual recursing. It takes two -((argument))s: the current number and a string that records how we -reached this number. If it finds a solution, it returns a string that -shows how to get to the target. If no solution can be found starting -from this number, it returns `null`. - -{{index null, "|| operator", "short-circuit evaluation"}} - -To do this, the function performs one of three actions. If the current -number is the target number, the current history is a way to reach -that target, so it is returned. If the current number is greater than -the target, there's no sense in further exploring this branch because -both adding and multiplying will only make the number bigger, so it -returns `null`. Finally, if we're still below the target number, -the function tries both possible paths that start from the current -number by calling itself twice, once for addition and once for -multiplication. If the first call returns something that is not -`null`, it is returned. Otherwise, the second call is returned, -regardless of whether it produces a string or `null`. +Note that this program doesn't necessarily find the _shortest_ sequence of operations. It is satisfied when it finds any sequence at all. + +It's okay if you don't see how this code works right away. Let's work through it since it makes for a great exercise in recursive thinking. + +The inner function `find` does the actual recursing. It takes two ((argument))s: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If it can find no solution starting from this number, it returns `null`. + +{{index null, "?? operator", "short-circuit evaluation"}} + +To do this, the function performs one of three actions. If the current number is the target number, the current history is a way to reach that target, so it is returned. If the current number is greater than the target, there's no sense in further exploring this branch because both adding and multiplying will only make the number bigger, so it returns `null`. Finally, if we're still below the target number, the function tries both possible paths that start from the current number by calling itself twice, once for addition and once for multiplication. If the first call returns something that is not `null`, it is returned. Otherwise, the second call is returned, regardless of whether it produces a string or `null`. {{index "call stack"}} -To better understand how this function produces the effect we're -looking for, let's look at all the calls to `find` that are made when -searching for a solution for the number 13. +To better understand how this function produces the effect we're looking for, let's look at all the calls to `find` that are made when searching for a solution for the number 13: ```{lang: null} find(1, "1") @@ -729,59 +498,34 @@ find(1, "1") found! ``` -The indentation indicates the depth of the call stack. The first time -`find` is called, it starts by calling itself to explore the solution -that starts with `(1 + 5)`. That call will further recurse to explore -_every_ continued solution that yields a number less than or equal to -the target number. Since it doesn't find one that hits the target, it -returns `null` back to the first call. There the `||` operator causes -the call that explores `(1 * 3)` to happen. This search has more -luck—its first recursive call, through yet _another_ recursive call, -hits upon the target number. That innermost call returns a string, and -each of the `||` operators in the intermediate calls passes that string -along, ultimately returning the solution. +The indentation indicates the depth of the call stack. The first time `find` is called, the function starts by calling itself to explore the solution that starts with `(1 + 5)`. That call will further recurse to explore _every_ continued solution that yields a number less than or equal to the target number. Since it doesn't find one that hits the target, it returns `null` back to the first call. There the `??` operator causes the call that explores `(1 * 3)` to happen. This search has more luck—its first recursive call, through yet _another_ recursive call, hits upon the target number. That innermost call returns a string, and each of the `??` operators in the intermediate calls passes that string along, ultimately returning the solution. ## Growing functions {{index [function, definition]}} -There are two more or less natural ways for functions to be introduced -into programs. +There are two more or less natural ways for functions to be introduced into programs. {{index repetition}} -The first is that you find yourself writing similar code multiple -times. You'd prefer not to do that. Having more code means more space -for mistakes to hide and more material to read for people trying to -understand the program. So you take the repeated functionality, find a -good name for it, and put it into a function. +The first occurs when you find yourself writing similar code multiple times. You'd prefer not to do that, as having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function. -The second way is that you find you need some functionality that you -haven't written yet and that sounds like it deserves its own function. -You'll start by naming the function, and then you'll write its body. -You might even start writing code that uses the function before you -actually define the function itself. +The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You start by naming the function, and then write its body. You might even start writing code that uses the function before you actually define the function itself. {{index [function, naming], [binding, naming]}} -How difficult it is to find a good name for a function is a good -indication of how clear a concept it is that you're trying to wrap. -Let's go through an example. +How difficult it is to find a good name for a function is a good indication of how clear a concept it is that you're trying to wrap. Let's go through an example. {{index "farm example"}} -We want to write a program that prints two numbers: the numbers of -cows and chickens on a farm, with the words `Cows` and `Chickens` -after them and zeros padded before both numbers so that they are -always three digits long. +We want to write a program that prints two numbers: the numbers of cows and chickens on a farm, with the words `Cows` and `Chickens` after them and zeros padded before both numbers so that they are always three digits long: ```{lang: null} 007 Cows 011 Chickens ``` -This asks for a function of two arguments—the number of cows and the -number of chickens. Let's get coding. +This asks for a function of two arguments—the number of cows and the number of chickens. Let's get coding. ``` function printFarmInventory(cows, chickens) { @@ -801,20 +545,13 @@ printFarmInventory(7, 11); {{index ["length property", "for string"], "while loop"}} -Writing `.length` after a string expression will give us the length of -that string. Thus, the `while` loops keep adding zeros in front of the -number strings until they are at least three characters long. +Writing `.length` after a string expression will give us the length of that string. Thus, the `while` loops keep adding zeros in front of the number strings until they are at least three characters long. -Mission accomplished! But just as we are about to send the farmer the -code (along with a hefty invoice), she calls and tells us she's also -started keeping pigs, and couldn't we please extend the software to -also print pigs? +Mission accomplished! But just as we are about to send the farmer the code (along with a hefty invoice), she calls and tells us she's also started keeping pigs, and couldn't we please extend the software to also print pigs? {{index "copy-paste programming"}} -We sure can. But just as we're in the process of copying and pasting -those four lines one more time, we stop and reconsider. There has to -be a better way. Here's a first attempt: +We sure can. But just as we're in the process of copying and pasting those four lines one more time, we stop and reconsider. There has to be a better way. Here's a first attempt: ``` function printZeroPaddedWithLabel(number, label) { @@ -836,14 +573,11 @@ printFarmInventory(7, 11, 3); {{index [function, naming]}} -It works! But that name, `printZeroPaddedWithLabel`, is a little -awkward. It conflates three things—printing, zero-padding, and adding -a label—into a single function. +It works! But that name, `printZeroPaddedWithLabel`, is a little awkward. It conflates three things—printing, zero-padding, and adding a label—into a single function. {{index "zeroPad function"}} -Instead of lifting out the repeated part of our program wholesale, -let's try to pick out a single _concept_. +Instead of lifting out the repeated part of our program wholesale, let's try to pick out a single _concept_: ``` function zeroPad(number, width) { @@ -865,76 +599,36 @@ printFarmInventory(7, 16, 3); {{index readability, "pure function"}} -A function with a nice, obvious name like `zeroPad` makes it easier -for someone who reads the code to figure out what it does. And such a -function is useful in more situations than just this specific program. -For example, you could use it to help print nicely aligned tables of -numbers. +A function with a nice, obvious name like `zeroPad` makes it easier for someone who reads the code to figure out what it does. Such a function is also useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers. {{index [interface, design]}} -How smart and versatile _should_ our function be? We could write -anything, from a terribly simple function that can only pad a number -to be three characters wide to a complicated generalized -number-formatting system that handles fractional numbers, negative -numbers, alignment of decimal dots, padding with different characters, -and so on. +How smart and versatile _should_ our function be? We could write anything, from a terribly simple function that can only pad a number to be three characters wide to a complicated generalized number-formatting system that handles fractional numbers, negative numbers, alignment of decimal dots, padding with different characters, and so on. -A useful principle is to not add cleverness unless you are absolutely -sure you're going to need it. It can be tempting to write general -"((framework))s" for every bit of functionality you come across. -Resist that urge. You won't get any real work done—you'll just be -writing code that you never use. +A useful principle is to refrain from adding cleverness unless you are absolutely sure you're going to need it. It can be tempting to write general "((framework))s" for every bit of functionality you come across. Resist that urge. You won't get any real work done—you'll be too busy writing code that you never use. {{id pure}} ## Functions and side effects {{index "side effect", "pure function", [function, purity]}} -Functions can be roughly divided into those that are called for their -side effects and those that are called for their return value. (Though -it is definitely also possible to both have side effects and return a -value.) +Functions can be roughly divided into those that are called for their side effects and those that are called for their return value (though it is also possible to both have side effects and return a value). {{index reuse}} -The first helper function in the ((farm example)), -`printZeroPaddedWithLabel`, is called for its side effect: it prints a -line. The second version, `zeroPad`, is called for its return value. -It is no coincidence that the second is useful in more situations than -the first. Functions that create values are easier to combine in new -ways than functions that directly perform side effects. +The first helper function in the ((farm example)), `printZeroPaddedWithLabel`, is called for its side effect: it prints a line. The second version, `zeroPad`, is called for its return value. It is no coincidence that the second is useful in more situations than the first. Functions that create values are easier to combine in new ways than functions that directly perform side effects. {{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"}} -Still, there's no need to feel bad when writing functions that are not -pure or to wage a holy war to purge them from your code. Side effects -are often useful. There'd be no way to write a pure version of -`console.log`, for example, and `console.log` is good to have. Some -operations are also easier to express in an efficient way when we use -side effects, so computing speed can be a reason to avoid purity. +Still, there's no need to feel bad when writing functions that are not pure. Side effects are often useful. There's no way to write a pure version of `console.log`, for example, and `console.log` is good to have. Some operations are also easier to express in an efficient way when we use side effects. ## Summary -This chapter taught you how to write your own functions. The -`function` keyword, when used as an expression, can create a function -value. When used as a statement, it can be used to declare a binding -and give it a function as its value. Arrow functions are yet another -way to create functions. +This chapter taught you how to write your own functions. The `function` keyword, when used as an expression, can create a function value. When used as a statement, it can be used to declare a binding and give it a function as its value. Arrow functions are yet another way to create functions. ``` // Define f to hold a function value @@ -951,16 +645,9 @@ function g(a, b) { let h = a => a % 3; ``` -A key aspect in understanding functions is understanding scopes. Each -block creates a new scope. Parameters and bindings declared in a given -scope are local and not visible from the outside. Bindings declared -with `var` behave differently—they end up in the nearest function -scope or the global scope. +A key part of understanding functions is understanding scopes. Each block creates a new scope. Parameters and bindings declared in a given scope are local and not visible from the outside. Bindings declared with `var` behave differently—they end up in the nearest function scope or the global scope. -Separating the tasks your program performs into different functions is -helpful. You won't have to repeat yourself as much, and functions can -help organize a program by grouping code into pieces that do specific -things. +Separating the tasks your program performs into different functions is helpful. You won't have to repeat yourself as much, and functions can help organize a program by grouping code into pieces that do specific things. ## Exercises @@ -968,10 +655,7 @@ things. {{index "Math object", "minimum (exercise)", "Math.min function", minimum}} -The [previous chapter](program_structure#return_values) introduced the -standard function `Math.min` that returns its smallest argument. We -can build something like that now. Write a function `min` that takes -two arguments and returns their minimum. +The [previous chapter](program_structure#return_values) introduced the standard function `Math.min` that returns its smallest argument. We can write a function like that ourselves now. Define the function `min` that takes two arguments and returns their minimum. {{if interactive @@ -989,9 +673,7 @@ if}} {{index "minimum (exercise)"}} -If you have trouble putting braces and -parentheses in the right place to get a valid function definition, -start by copying one of the examples in this chapter and modifying it. +If you have trouble putting braces and parentheses in the right place to get a valid function definition, start by copying one of the examples in this chapter and modifying it. {{index "return keyword"}} @@ -1003,10 +685,7 @@ hint}} {{index recursion, "isEven (exercise)", "even number"}} -We've seen that `%` (the remainder operator) can be used to test -whether a number is even or odd by using `% 2` to see whether it's -divisible by two. Here's another way to define whether a positive -whole number is even or odd: +We've seen that we can use `%` (the remainder operator) to test whether a number is even or odd by using `% 2` to see whether it's divisible by two. Here's another way to define whether a positive whole number is even or odd: - Zero is even. @@ -1014,14 +693,11 @@ whole number is even or odd: - For any other number _N_, its evenness is the same as _N_ - 2. -Define a recursive function `isEven` corresponding to this -description. The function should accept a single parameter (a -positive, whole number) and return a Boolean. +Define a recursive function `isEven` corresponding to this description. The function should accept a single parameter (a positive, whole number) and return a Boolean. {{index "stack overflow"}} -Test it on 50 and 75. See how it behaves on -1. Why? Can you think of -a way to fix this? +Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this? {{if interactive @@ -1042,21 +718,11 @@ if}} {{index "isEven (exercise)", ["if keyword", chaining], recursion}} -Your function will likely look somewhat similar to the inner `find` -function in the recursive `findSolution` -[example](functions#recursive_puzzle) in this chapter, with an -`if`/`else if`/`else` chain that tests which of the three cases -applies. The final `else`, corresponding to the third case, makes the -recursive call. Each of the branches should contain a `return` -statement or in some other way arrange for a specific value to be -returned. +Your function will likely look somewhat similar to the inner `find` function in the recursive `findSolution` [example](functions#recursive_puzzle) in this chapter, with an `if`/`else if`/`else` chain that tests which of the three cases applies. The final `else`, corresponding to the third case, makes the recursive call. Each of the branches should contain a `return` statement or in some other way arrange for a specific value to be returned. {{index "stack overflow"}} -When given a negative number, the function will recurse again and -again, passing itself an ever more negative number, thus getting -further and further away from returning a result. It will eventually -run out of stack space and abort. +When given a negative number, the function will recurse again and again, passing itself an ever more negative number, thus getting further and further away from returning a result. It will eventually run out of stack space and abort. hint}} @@ -1064,28 +730,18 @@ hint}} {{index "bean counting (exercise)", [string, indexing], "zero-based counting", ["length property", "for string"]}} -You can get the Nth character, or letter, from a string by writing -`"string"[N]`. The returned value will be a string containing only one -character (for example, `"b"`). The first character has position 0, -which causes the last one to be found at position `string.length - 1`. -In other words, a two-character string has length 2, and its -characters have positions 0 and 1. +You can get the *N*th character, or letter, from a string by writing `[N]` after the string (for example, `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. -Write a function `countBs` that takes a string as its only argument -and returns a number that indicates how many uppercase "B" characters -there are in the string. +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. +Next, write a function called `countChar` that behaves like `countBs`, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase B characters). Rewrite `countBs` to make use of this new function. {{if interactive ```{test: no} // Your code here. -console.log(countBs("BBC")); +console.log(countBs("BOB")); // → 2 console.log(countChar("kakkerlak", "k")); // → 4 @@ -1097,15 +753,10 @@ if}} {{index "bean counting (exercise)", ["length property", "for string"], "counter variable"}} -Your function will need a ((loop)) that looks at every character in -the string. It can run an index from zero to one below its length (`< -string.length`). If the character at the current position is the same -as the one the function is looking for, it adds 1 to a counter -variable. Once the loop has finished, the counter can be returned. +Your function will need a ((loop)) that looks at every character in the string. It can run an index from zero to one below its length (`< string.length`). If the character at the current position is the same as the one the function is looking for, it adds 1 to a counter variable. Once the loop has finished, the counter can be returned. {{index "local binding"}} -Take care to make all the bindings used in the function _local_ to the -function by properly declaring them with the `let` or `const` keyword. +Take care to make all the bindings used in the function _local_ to the function by properly declaring them with the `let` or `const` keyword. hint}} diff --git a/04_data.md b/04_data.md index 52bc92c7e..51541b8d2 100644 --- a/04_data.md +++ b/04_data.md @@ -4,42 +4,25 @@ {{quote {author: "Charles Babbage", title: "Passages from the Life of a Philosopher (1864)", chapter: true} -On two occasions I have been asked, 'Pray, Mr. Babbage, if you put -into the machine wrong figures, will the right answers come out?' -[...] I am not able rightly to apprehend the kind of confusion of -ideas that could provoke such a question. +On two occasions I have been asked, 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?' [...] I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question. quote}} {{index "Babbage, Charles"}} -{{figure {url: "img/chapter_picture_4.jpg", alt: "Picture of a weresquirrel", chapter: framed}}} +{{figure {url: "img/chapter_picture_4.jpg", alt: "Illustration of a squirrel next to a pile of books and a pair of glasses. A moon and stars are visible in the background.", chapter: framed}}} {{index object, "data structure"}} -Numbers, Booleans, and strings are the atoms that ((data)) structures -are built from. Many types of information require more than one -atom, though. _Objects_ allow us to group values—including other -objects—to build more complex structures. +Numbers, Booleans, and strings are the atoms from which ((data)) structures are built. Many types of information require more than one atom, though. _Objects_ allow us to group values—including other objects—to build more complex structures. -The programs we have built so far have been limited by the fact that -they were operating only on simple data types. This chapter will -introduce basic data structures. By the end of it, you'll know enough -to start writing useful programs. +The programs we have built so far have been limited by the fact that they were operating only on simple data types. After learning the basics of data structures in this chapter, you'll know enough to start writing useful programs. -The chapter will work through a more or less realistic programming -example, introducing concepts as they apply to the problem at hand. -The example code will often build on functions and bindings that were -introduced earlier in the text. +The chapter will work through a more or less realistic programming example, introducing concepts as they apply to the problem at hand. The example code will often build on functions and bindings introduced earlier in the book. {{if book -The online coding ((sandbox)) for the book -([_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code)) -provides a way to run code in the context of a specific chapter. If -you decide to work through the examples in another environment, be -sure to first download the full code for this chapter from the sandbox -page. +The online coding ((sandbox)) for the book ([_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code)) provides a way to run code in the context of a particular chapter. If you decide to work through the examples in another environment, be sure to first download the full code for this chapter from the sandbox page. if}} @@ -47,56 +30,31 @@ if}} {{index "weresquirrel example", lycanthropy}} -Every now and then, usually between 8 p.m. and 10 p.m., -((Jacques)) finds himself transforming into a small furry rodent with -a bushy tail. - -On one hand, Jacques is quite glad that he doesn't have classic -lycanthropy. Turning into a squirrel does cause fewer problems than -turning into a wolf. Instead of having to worry about accidentally -eating the neighbor (_that_ would be awkward), he worries about being -eaten by the neighbor's cat. After two occasions where he woke up on a -precariously thin branch in the crown of an oak, naked and -disoriented, he has taken to locking the doors and windows of his room -at night and putting a few walnuts on the floor to keep himself busy. - -That takes care of the cat and tree problems. But Jacques would prefer -to get rid of his condition entirely. The irregular occurrences of the -transformation make him suspect that they might be triggered by -something. For a while, he believed that it happened only on days when -he had been near oak trees. But avoiding oak trees did not stop the -problem. +Every now and then, usually between 8 p.m. and 10 p.m., ((Jacques)) finds himself transforming into a small furry rodent with a bushy tail. + +On one hand, Jacques is quite glad that he doesn't have classic lycanthropy. Turning into a squirrel does cause fewer problems than turning into a wolf. Instead of having to worry about accidentally eating the neighbor (_that_ would be awkward), he worries about being eaten by the neighbor's cat. After two occasions of waking up on a precariously thin branch in the crown of an oak, naked and disoriented, he has taken to locking the doors and windows of his room at night and putting a few walnuts on the floor to keep himself busy. + +But Jacques would prefer to get rid of his condition entirely. The irregular occurrences of the transformation make him suspect that they might be triggered by something. For a while, he believed that it happened only on days when he had been near oak trees. However, avoiding oak trees did not solve the problem. {{index journal}} -Switching to a more scientific approach, Jacques has started keeping a -daily log of everything he does on a given day and whether he changed -form. With this data he hopes to narrow down the conditions that -trigger the transformations. +Switching to a more scientific approach, Jacques has started keeping a daily log of everything he does on a given day and whether he changed form. With this data he hopes to narrow down the conditions that trigger the transformations. -The first thing he needs is a data structure to store this -information. +The first thing he needs is a data structure to store this information. -## Data sets +## Datasets {{index ["data structure", collection], [memory, organization]}} -To work with a chunk of digital data, we'll first have to find a way -to represent it in our machine's memory. Say, for example, that we -want to represent a ((collection)) of the numbers 2, 3, 5, 7, and 11. +To work with a chunk of digital data, we first have to find a way to represent it in our machine's memory. Say, for example, that we want to represent a ((collection)) of the numbers 2, 3, 5, 7, and 11. {{index string}} -We could get creative with strings—after all, strings can have any -length, so we can put a lot of data into them—and use `"2 3 5 7 11"` -as our representation. But this is awkward. You'd have to somehow -extract the digits and convert them back to numbers to access them. +We could get creative with strings—after all, strings can have any length, so we can put a lot of data into them—and use `"2 3 5 7 11"` as our representation. But this is awkward. We'd have to somehow extract the digits and convert them back to numbers to access them. {{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]; @@ -110,20 +68,12 @@ console.log(listOfNumbers[2 - 1]); {{index "[] (subscript)", [array, indexing]}} -The notation for getting at the elements inside an array also uses -((square brackets)). A pair of square brackets immediately after an -expression, with another expression inside of them, will look up the -element in the left-hand expression that corresponds to the -_((index))_ given by the expression in the brackets. +The notation for getting at the elements inside an array also uses ((square brackets)). A pair of square brackets immediately after an expression, with another expression inside of them, will look up the element in the left-hand expression that corresponds to the _((index))_ given by the expression in the brackets. {{id array_indexing}} {{index "zero-based counting"}} -The first index of an array is zero, not one. So the first element is -retrieved with `listOfNumbers[0]`. Zero-based counting has a long -tradition in technology and in certain ways makes a lot of sense, but -it takes some getting used to. Think of the index as the amount of -items to skip, counting from the start of the array. +The first index of an array is zero, not one, so the first element is retrieved with `listOfNumbers[0]`. Zero-based counting has a long tradition in technology and in certain ways makes a lot of sense, but it takes some getting used to. Think of the index as the number of items to skip, counting from the start of the array. {{id properties}} @@ -131,19 +81,11 @@ items to skip, counting from the start of the array. {{index "Math object", "Math.max function", ["length property", "for string"], [object, property], "period character", [property, access]}} -We've seen a few suspicious-looking expressions like `myString.length` -(to get the length of a string) and `Math.max` (the maximum function) -in past chapters. These are expressions that access a _property_ -of some value. In the first case, we access the `length` property of -the value in `myString`. In the second, we access the property named -`max` in the `Math` object (which is a collection of -mathematics-related constants and functions). +We've seen a few expressions like `myString.length` (to get the length of a string) and `Math.max` (the maximum function) in past chapters. These expressions access a _property_ of some value. In the first case, we access the `length` property of the value in `myString`. In the second, we access the property named `max` in the `Math` object (which is a collection of mathematics-related constants and functions). {{index [property, access], null, undefined}} -Almost all JavaScript values have properties. The exceptions are -`null` and `undefined`. If you try to access a property on one of -these nonvalues, you get an error. +Almost all JavaScript values have properties. The exceptions are `null` and `undefined`. If you try to access a property on one of these nonvalues, you get an error: ```{test: no} null.length; @@ -153,35 +95,15 @@ null.length; {{indexsee "dot character", "period character"}} {{index "[] (subscript)", "period character", "square brackets", "computed property", [property, access]}} -The two main ways to access properties in JavaScript are with a dot -and with square brackets. Both `value.x` and `value[x]` access a -property on `value`—but not necessarily the same property. The -difference is in how `x` is interpreted. When using a dot, the word -after the dot is the literal name of the property. When using square -brackets, the expression between the brackets is _evaluated_ to get -the property name. Whereas `value.x` fetches the property of `value` -named "x", `value[x]` tries to evaluate the expression `x` and uses -the result, converted to a string, as the property name. - -So if you know that the property you are interested in is called -_color_, you say `value.color`. If you want to extract the property -named by the value held in the binding `i`, you say `value[i]`. -Property names are strings. They can be any string, but the dot notation works only with -names that look like valid binding names. So if you want to access a -property named _2_ or _John Doe_, you must use square brackets: -`value[2]` or `value["John Doe"]`. - -The elements in an ((array)) are stored as the array's properties, using -numbers as property names. Because you can't use the dot notation with -numbers and usually want to use a binding that holds the index -anyway, you have to use the bracket notation to get at them. +The two main ways to access properties in JavaScript are with a dot and with square brackets. Both `value.x` and `value[x]` access a property on `value`—but not necessarily the same property. The difference is in how `x` is interpreted. When using a dot, the word after the dot is the literal name of the property. When using square brackets, the expression between the brackets is _evaluated_ to get the property name. Whereas `value.x` fetches the property of `value` named "x", `value[x]` takes the value of the variable named `x` and uses that, converted to a string, as the property name. + +If you know that the property in which you are interested is called _color_, you say `value.color`. If you want to extract the property named by the value held in the binding `i`, you say `value[i]`. Property names are strings. They can be any string, but the dot notation works only with names that look like valid binding names—starting with a letter or underscore, and containing only letters, numbers, and underscores. If you want to access a property named _2_ or _John Doe_, you must use square brackets: `value[2]` or `value["John Doe"]`. + +The elements in an ((array)) are stored as the array's properties, using numbers as property names. Because you can't use the dot notation with numbers and usually want to use a binding that holds the index anyway, you have to use the bracket notation to get at them. {{index ["length property", "for array"], [array, "length of"]}} -The `length` property of an array tells us how many elements it has. -This property name is a valid binding name, and we know its name in -advance, so to find the length of an array, you typically write -`array.length` because that's easier to write than `array["length"]`. +Just like strings, arrays have a `length` property that tells us how many elements the array has. {{id methods}} @@ -189,8 +111,7 @@ advance, so to find the length of an array, you typically write {{index [function, "as property"], method, string}} -Both string and array values contain, in addition to the `length` -property, a number of properties that hold function values. +Both string and array values contain, in addition to the `length` property, a number of properties that hold function values. ``` let doh = "Doh"; @@ -202,25 +123,17 @@ console.log(doh.toUpperCase()); {{index "case conversion", "toUpperCase method", "toLowerCase method"}} -Every string has a `toUpperCase` property. When called, it will return -a copy of the string in which all letters have been converted to -uppercase. There is also `toLowerCase`, going the other way. +Every string has a `toUpperCase` property. When called, it will return a copy of the string in which all letters have been converted to uppercase. There is also `toLowerCase`, going the other way. {{index "this binding"}} -Interestingly, even though the call to `toUpperCase` does not pass any -arguments, the function somehow has access to the string `"Doh"`, the -value whose property we called. How this works is described in -[Chapter ?](object#obj_methods). +Interestingly, even though the call to `toUpperCase` does not pass any arguments, the function somehow has access to the string `"Doh"`, the value whose property we called. You'll find out how this works in [Chapter ?](object#obj_methods). -Properties that contain functions are generally called _methods_ of -the value they belong to, as in "`toUpperCase` is a method of a -string". +Properties that contain functions are generally called _methods_ of the value they belong to, as in "`toUpperCase` is a method of a string". {{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]; @@ -236,36 +149,21 @@ console.log(sequence); {{index collection, array, "push method", "pop method"}} -The `push` method adds values to the end of an array, and the -`pop` method does the opposite, removing the last value in the array -and returning it. +The `push` method adds values to the end of an array. The `pop` method does the opposite, removing the last value in the array and returning it. {{index ["data structure", stack]}} -These somewhat silly names are the traditional terms for operations on -a _((stack))_. A stack, in programming, is a data structure that -allows you to push values into it and pop them out again in the -opposite order so that the thing that was added last is removed first. -These are common in programming—you might remember the function ((call -stack)) from [the previous chapter](functions#stack), which is an -instance of the same idea. +These somewhat silly names are the traditional terms for operations on a _((stack))_. A stack, in programming, is a data structure that allows you to push values into it and pop them out again in the opposite order so that the thing that was added last is removed first. Stacks are common in programming—you might remember the function ((call stack)) from [the previous chapter](functions#stack), which is an instance of the same idea. ## Objects {{index journal, "weresquirrel example", array, record}} -Back to the weresquirrel. A set of daily log entries can be -represented as an array. But the entries do not consist of just a -number or a string—each entry needs to store a list of activities and -a Boolean value that indicates whether Jacques turned into a squirrel -or not. Ideally, we would like to group these together into a single -value and then put those grouped values into an array of log entries. +Back to the weresquirrel. A set of daily log entries can be represented as an array, but the entries do not consist of just a number or a string—each entry needs to store a list of activities and a Boolean value that indicates whether Jacques turned into a squirrel or not. Ideally, we would like to group these together into a single value and then put those grouped values into an array of log entries. {{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 = { @@ -283,11 +181,7 @@ console.log(day1.wolf); {{index [quoting, "of object properties"], "colon character"}} -Inside the braces, there is a list of properties separated by commas. -Each property has a name followed by a colon and a value. When an -object is written over multiple lines, indenting it like in the -example helps with readability. Properties whose names aren't valid -binding names or valid numbers have to be quoted. +Inside the braces, you write a list of properties separated by commas. Each property has a name followed by a colon and a value. When an object is written over multiple lines, indenting it as shown in this example helps with readability. Properties whose names aren't valid binding names or valid numbers must be quoted: ``` let descriptions = { @@ -298,37 +192,23 @@ let descriptions = { {{index [braces, object]}} -This means that braces have _two_ meanings in JavaScript. At -the start of a ((statement)), they start 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. +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}} -Reading a property that doesn't exist will give you the value -`undefined`. +Reading a property that doesn't exist will give you the value `undefined`. {{index [property, assignment], mutability, "= operator"}} -It is possible to assign a value to a property expression with the `=` -operator. This will replace the property's value if it already existed -or create a new property on the object if it didn't. +It is possible to assign a value to a property expression with the `=` operator. This will replace the property's value if it already existed or create a new property on the object if it didn't. {{index "tentacle (analogy)", [property, "model of"], [binding, "model of"]}} -To briefly return to our tentacle model of ((binding))s—property -bindings are similar. They _grasp_ values, but other bindings and -properties might be holding onto those same values. You may think of -objects as octopuses with any number of tentacles, each of which has a -name tattooed on it. +To briefly return to our tentacle model of ((binding))s—property bindings are similar. They _grasp_ values, but other bindings and properties might be holding onto those same values. You can think of objects as octopuses with any number of tentacles, each of which has a name written on it. {{index "delete operator", [property, deletion]}} -The `delete` operator cuts off a tentacle from such an octopus. It is -a unary operator that, when applied to an object property, -will remove the named property from the object. This is not a common -thing to do, but it is possible. +The `delete` operator cuts off a tentacle from such an octopus. It is a unary operator that, when applied to an object property, will remove the named property from the object. This is not a common thing to do, but it is possible. ``` let anObject = {left: 1, right: 2}; @@ -345,26 +225,18 @@ console.log("right" in anObject); {{index "in operator", [property, "testing for"], object}} -The binary `in` operator, when applied to a string and an object, -tells you whether that object has a property with that name. The difference -between setting a property to `undefined` and actually deleting it is -that, in the first case, the object still _has_ the property (it just -doesn't have a very interesting value), whereas in the second case the -property is no longer present and `in` will return `false`. +The binary `in` operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to `undefined` and actually deleting it is that in the first case, the object still _has_ the property (it just doesn't have a very interesting value), whereas in the second case, the property is no longer present and `in` will return `false`. {{index "Object.keys function"}} -To find out what properties an object has, you can use the -`Object.keys` function. You give it an object, and it returns an array -of strings—the object's property names. +To find out what properties an object has, you can use the `Object.keys` function. Give the function an object and it will return an array of strings—the object's property names: ``` console.log(Object.keys({x: 0, y: 0, z: 2})); // → ["x", "y", "z"] ``` -There's an `Object.assign` function that copies all properties from -one object into another. +There's an `Object.assign` function that copies all properties from one object into another: ``` let objectA = {a: 1, b: 2}; @@ -375,14 +247,11 @@ console.log(objectA); {{index array, collection}} -Arrays, then, are just a kind of object specialized for storing -sequences of things. If you evaluate `typeof []`, it produces -`"object"`. You can see them as long, flat octopuses with all their -tentacles in a neat row, labeled with numbers. +Arrays, then, are just a kind of object specialized for storing sequences of things. If you evaluate `typeof []`, it produces `"object"`. You can visualize arrays as long, flat octopuses with all their tentacles in a neat row, labeled with numbers. {{index journal, "weresquirrel example"}} -We will represent the journal that Jacques keeps as an array of objects. +Jacques will represent the journal that Jacques keeps as an array of objects: ```{test: wrap} let journal = [ @@ -395,36 +264,23 @@ let journal = [ {events: ["weekend", "cycling", "break", "peanuts", "beer"], squirrel: true}, - /* and so on... */ + /* And so on... */ ]; ``` ## Mutability -We will get to actual programming _real_ soon now. First there's one -more piece of theory to understand. +We will get to actual programming soon, but first, there's one more piece of theory to understand. {{index mutability, "side effect", number, string, Boolean, [object, mutability]}} -We saw that object values can be modified. The types of values -discussed in earlier chapters, such as numbers, strings, and Booleans, -are all _((immutable))_—it is impossible to change values of those -types. You can combine them and derive new values from them, but when -you take a specific string value, that value will always remain the -same. The text inside it cannot be changed. If you have a string that -contains `"cat"`, it is not possible for other code to change a -character in your string to make it spell `"rat"`. +We saw that object values can be modified. The types of values discussed in earlier chapters, such as numbers, strings, and Booleans, are all _((immutable))_—it is impossible to change values of those types. You can combine them and derive new values from them, but when you take a specific string value, that value will always remain the same. The text inside it cannot be changed. If you have a string that contains `"cat"`, it is not possible for other code to change a character in your string to make it spell `"rat"`. -Objects work differently. You _can_ change their properties, -causing a single object value to have different content at different times. +Objects work differently. You _can_ change their properties, causing a single object value to have different content at different times. {{index [object, identity], identity, [memory, organization], mutability}} -When we have two numbers, 120 and 120, we can consider them precisely -the same number, whether or not they refer to the same physical bits. -With objects, there is a difference between having two references to -the same object and having two different objects that contain the same -properties. Consider the following code: +When we have two numbers, 120 and 120, we can consider them precisely the same number, whether or not they refer to the same physical bits. With objects, there is a difference between having two references to the same object and having two different objects that contain the same properties. Consider the following code: ``` let object1 = {value: 10}; @@ -445,20 +301,11 @@ console.log(object3.value); {{index "tentacle (analogy)", [binding, "model of"]}} -The `object1` and `object2` bindings grasp the _same_ object, which is -why changing `object1` also changes the value of `object2`. They are -said to have the same _identity_. The binding `object3` points to a -different object, which initially contains the same properties as -`object1` but lives a separate life. +The `object1` and `object2` bindings grasp the _same_ object, which is why changing `object1` also changes the value of `object2`. They are said to have the same _identity_. The binding `object3` points to a different object, which initially contains the same properties as `object1` but lives a separate life. {{index "const keyword", "let keyword", [binding, "as state"]}} -Bindings can also be changeable or constant, but this is separate from -the way their values behave. Even though number values don't change, -you can use a `let` binding to keep track of a changing number by -changing the value the binding points at. Similarly, though a `const` -binding to an object can itself not be changed and will continue to -point at the same object, the _contents_ of that object might change. +Bindings can also be changeable or constant, but this is separate from the way their values behave. Even though number values don't change, you can use a `let` binding to keep track of a changing number by changing the value at which the binding points. Similarly, though a `const` binding to an object can itself not be changed and will continue to point at the same object, the _contents_ of that object might change. ```{test: no} const score = {visitors: 0, home: 0}; @@ -470,20 +317,13 @@ score = {visitors: 1, home: 1}; {{index "== operator", [comparison, "of objects"], "deep comparison"}} -When you compare objects with JavaScript's `==` operator, it compares -by identity: it will produce `true` only if both objects are precisely -the same value. Comparing different objects will return `false`, even -if they have identical properties. There is no "deep" comparison -operation built into JavaScript, which compares objects by contents, -but it is possible to write it yourself (which is one of the -[exercises](data#exercise_deep_compare) at the end of this chapter). +When you compare objects with JavaScript's `==` operator, it compares by identity: it will produce `true` only if both objects are precisely the same value. Comparing different objects will return `false`, even if they have identical properties. There is no "deep" comparison operation built into JavaScript that compares objects by contents, but it is possible to write it yourself (which is one of the [exercises](data#exercise_deep_compare) at the end of this chapter). ## The lycanthrope's log {{index "weresquirrel example", lycanthropy, "addEntry function"}} -So, Jacques starts up his JavaScript interpreter and sets up the -environment he needs to keep his ((journal)). +Jacques starts up his JavaScript interpreter and sets up the environment he needs to keep his ((journal)): ```{includeCode: true} let journal = []; @@ -495,15 +335,9 @@ function addEntry(events, squirrel) { {{index [braces, object], "{} (object)", [property, definition]}} -Note that the object added to the journal looks a little odd. Instead -of declaring properties like `events: events`, it just gives a -property name. This is shorthand that means the same thing—if a -property name in brace notation isn't followed by a value, its -value is taken from the binding with the same name. +Note that the object added to the journal looks a little odd. Instead of declaring properties like `events: events`, it just gives a property name: `events`. This is shorthand that means the same thing—if a property name in brace notation isn't followed by a value, its value is taken from the binding with the same name. -So then, every evening at 10 p.m.—or sometimes the next morning, after -climbing down from the top shelf of his bookcase—Jacques records the -day. +Every evening at 10 p.m.—or sometimes the next morning, after climbing down from the top shelf of his bookcase—Jacques records the day: ``` addEntry(["work", "touched tree", "pizza", "running", @@ -514,52 +348,25 @@ addEntry(["weekend", "cycling", "break", "peanuts", "beer"], true); ``` -Once he has enough data points, he intends to use statistics to find -out which of these events may be related to the squirrelifications. +Once he has enough data points, he intends to use statistics to find out which of these events may be related to the squirrelifications. {{index correlation}} -_Correlation_ is a measure of ((dependence)) between statistical -variables. A statistical variable is not quite the same as a -programming variable. In statistics you typically have a set of -_measurements_, and each variable is measured for every measurement. -Correlation between variables is usually expressed as a value that -ranges from -1 to 1. Zero correlation means the variables are not -related. A correlation of one indicates that the two are perfectly -related—if you know one, you also know the other. Negative one also -means that the variables are perfectly related but that they are -opposites—when one is true, the other is false. +_Correlation_ is a measure of ((dependence)) between statistical variables. A statistical variable is not quite the same as a programming variable. In statistics you typically have a set of _measurements_, and each variable is measured for every measurement. Correlation between variables is usually expressed as a value that ranges from -1 to 1. Zero correlation means the variables are not related. A correlation of 1 indicates that the two are perfectly related—if you know one, you also know the other. Negative 1 also means that the variables are perfectly related but are opposites—when one is true, the other is false. {{index "phi coefficient"}} -To compute the measure of correlation between two Boolean variables, -we can use the _phi coefficient_ (_ϕ_). This is a formula whose input -is a ((frequency table)) containing the number of times the different -combinations of the variables were observed. The output of the formula -is a number between -1 and 1 that describes the correlation. +To compute the measure of correlation between two Boolean variables, we can use the _phi coefficient_ (_ϕ_). This is a formula whose input is a ((frequency table)) containing the number of times the different combinations of the variables were observed. The output of the formula is a number between -1 and 1 that describes the correlation. -We could take the event of eating ((pizza)) and put that in a -frequency table like this, where each number indicates the amount of -times that combination occurred in our measurements: +We could take the event of eating ((pizza)) and put that in a frequency table like this, where each number indicates the number of times that combination occurred in our measurements. -{{figure {url: "img/pizza-squirrel.svg", alt: "Eating pizza versus turning into a squirrel", width: "7cm"}}} +{{figure {url: "img/pizza-squirrel.svg", alt: "A two-by-two table showing the pizza variable on the horizontal, and the squirrel variable on the vertical axis. Each cell show how many time that combination occurred. In 76 cases, neither happened. In 9 cases, only pizza was true. In 4 cases only squirrel was true. And in one case both occurred.", width: "7cm"}}} If we call that table _n_, we can compute _ϕ_ using the following formula: {{if html -
- - - -
ϕ = -
n11n00 − - n10n01
-
- n1•n0•n•1n•0 -
-
-
+
ϕ =
n11n00n10n01
n1•n0•n•1n•0
if}} @@ -569,55 +376,27 @@ if}} if}} -(If at this point you're putting the book down to focus on a terrible -flashback to 10th grade math class—hold on! I do not intend to torture -you with endless pages of cryptic notation—it's just this one formula for -now. And even with this one, all we do is turn it into JavaScript.) +(If at this point you're putting the book down to focus on a terrible flashback to 10th grade math class—hold on! I do not intend to torture you with endless pages of cryptic notation—it's just this one formula for now. And even with this one, all we do is turn it into JavaScript.) -The notation [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} indicates -the number of measurements where the first variable (squirrelness) is -false (0) and the second variable (pizza) is true (1). In the pizza -table, [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} is 9. +The notation [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} indicates the number of measurements where the first variable (squirrelness) is false (0) and the second variable (pizza) is true (1). In the pizza table, [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} is 9. -The value [_n_~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} refers -to the sum of all measurements where the first variable is true, which -is 5 in the example table. Likewise, [_n_~•0~]{if -html}[[$n_{\bullet0}$]{latex}]{if tex} refers to the sum of the -measurements where the second variable is false. +The value [_n_~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} refers to the sum of all measurements where the first variable is true, which is 5 in the example table. Likewise, [_n_~•0~]{if html}[[$n_{\bullet0}$]{latex}]{if tex} refers to the sum of the measurements where the second variable is false. {{index correlation, "phi coefficient"}} -So for the pizza table, the part above the division line (the -dividend) would be 1×76−4×9 = 40, and the part below it (the -divisor) would be the square root of 5×85×10×80, or [√340000]{if -html}[[$\sqrt{340000}$]{latex}]{if tex}. This comes out to _ϕ_ ≈ -0.069, which is tiny. Eating ((pizza)) does not appear to have -influence on the transformations. +So for the pizza table, the part above the division line (the dividend) would be 1×76−4×9 = 40, and the part below it (the divisor) would be the square root of 5×85×10×80, or [√340,000]{if html}[[$\sqrt{340,000}$]{latex}]{if tex}. This comes out to _ϕ_ ≈ 0.069, which is tiny. Eating ((pizza)) does not appear to have influence on the transformations. ## Computing correlation {{index [array, "as table"], [nesting, "of arrays"]}} -We can represent a two-by-two ((table)) in JavaScript with a -four-element array (`[76, 9, 4, 1]`). We could also use other -representations, such as an array containing two two-element arrays -(`[[76, 9], [4, 1]]`) or an object with property names like `"11"` and -`"01"`, but the flat array is simple and makes the expressions that -access the table pleasantly short. We'll interpret the indices to the -array as two-((bit)) ((binary number))s, where the leftmost (most -significant) digit refers to the squirrel variable and the rightmost -(least significant) digit refers to the event variable. For example, -the binary number `10` refers to the case where Jacques did turn into -a squirrel, but the event (say, "pizza") didn't occur. This happened -four times. And since binary `10` is 2 in decimal notation, we will -store this number at index 2 of the array. +We can represent a two-by-two ((table)) in JavaScript with a four-element array (`[76, 9, 4, 1]`). We could also use other representations, such as an array containing two two-element arrays (`[[76, 9], [4, 1]]`) or an object with property names like `"11"` and `"01"`, but the flat array is simple and makes the expressions that access the table pleasantly short. We'll interpret the indices to the array as two-((bit)) ((binary number))s, where the leftmost (most significant) digit refers to the squirrel variable and the rightmost (least significant) digit refers to the event variable. For example, the binary number `10` refers to the case where Jacques did turn into a squirrel, but the event (say, "pizza") didn't occur. This happened four times. And since binary `10` is 2 in decimal notation, we will store this number at index 2 of the array. {{index "phi coefficient", "phi function"}} {{id phi_function}} -This is the function that computes the _ϕ_ coefficient from such an -array: +This is the function that computes the _ϕ_ coefficient from such an array: ```{includeCode: strip_log, test: clip} function phi(table) { @@ -634,28 +413,15 @@ console.log(phi([76, 9, 4, 1])); {{index "square root", "Math.sqrt function"}} -This is a direct translation of the _ϕ_ formula into JavaScript. -`Math.sqrt` is the square root function, as provided by the `Math` -object in a standard JavaScript environment. We have to add two fields -from the table to get fields like [n~1•~]{if -html}[[$n_{1\bullet}$]{latex}]{if tex} because the sums of rows or -columns are not stored directly in our data structure. +This is a direct translation of the _ϕ_ formula into JavaScript. `Math.sqrt` is the square root function, as provided by the `Math` object in a standard JavaScript environment. We have to add two fields from the table to get fields like [n~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} because the sums of rows or columns are not stored directly in our data structure. -{{index "JOURNAL data set"}} +{{index "JOURNAL dataset"}} -Jacques kept his journal for three months. The resulting ((data set)) -is available in the [coding -sandbox](https://eloquentjavascript.net/code#4) for this chapter[ -([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if -book}, where it is stored in the `JOURNAL` binding and in a -downloadable -[file](https://eloquentjavascript.net/code/journal.js). +Jacques keeps his journal for three months. The resulting ((dataset)) is available in the [coding sandbox](https://eloquentjavascript.net/code#4) for this chapter[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book}, where it is stored in the `JOURNAL` binding, and in a downloadable [file](https://eloquentjavascript.net/code/journal.js). {{index "tableFor function"}} -To extract a two-by-two ((table)) for a specific event from the -journal, we must loop over all the entries and tally how many times -the event occurs in relation to squirrel transformations. +To extract a two-by-two ((table)) for a specific event from the journal, we must loop over all the entries and tally how many times the event occurs in relation to squirrel transformations: ```{includeCode: strip_log} function tableFor(event, journal) { @@ -675,22 +441,13 @@ console.log(tableFor("pizza", JOURNAL)); {{index [array, searching], "includes method"}} -Arrays have an `includes` method that checks whether a given value -exists in the array. The function uses that to determine whether the -event name it is interested in is part of the event list for a given -day. +Arrays have an `includes` method that checks whether a given value exists in the array. The function uses that to determine whether the event name it is interested in is part of the event list for a given day. {{index [array, indexing]}} -The body of the loop in `tableFor` figures out which box in the table -each journal entry falls into by checking whether the entry contains -the specific event it's interested in and whether the event happens -alongside a squirrel incident. The loop then adds one to the correct -box in the table. +The body of the loop in `tableFor` figures out which box in the table each journal entry falls into by checking whether the entry contains the specific event it's interested in and whether the event happens alongside a squirrel incident. The loop then adds one to the correct box in the table. -We now have the tools we need to compute individual ((correlation))s. -The only step remaining is to find a correlation for every type of -event that was recorded and see whether anything stands out. +We now have the tools we need to compute individual ((correlation))s. The only step remaining is to find a correlation for every type of event that was recorded and see whether anything stands out. {{id for_of_loop}} @@ -707,12 +464,9 @@ for (let i = 0; i < JOURNAL.length; i++) { } ``` -This kind of loop is common in classical JavaScript—going over arrays -one element at a time is something that comes up a lot, and to do that -you'd run a counter over the length of the array and pick out each -element in turn. +This kind of loop is common in classical JavaScript—going over arrays one element at a time is something that comes up a lot, and to do that you'd run a counter over the length of the array and pick out each element in turn. -There is a simpler way to write such loops in modern JavaScript. +There is a simpler way to write such loops in modern JavaScript: ``` for (let entry of JOURNAL) { @@ -722,11 +476,7 @@ for (let entry of JOURNAL) { {{index "for/of loop"}} -When a `for` loop looks like this, with the word `of` after a variable -definition, it will loop over the elements of the value given after -`of`. This works not only for arrays but also for strings and some -other data structures. We'll discuss _how_ it works in [Chapter -?](object). +When a `for` loop uses the word `of` after its variable definition, it will loop over the elements of the value given after `of`. This works not only for arrays but also for strings and some other data structures. We'll discuss _how_ it works in [Chapter ?](object). {{id analysis}} @@ -734,9 +484,7 @@ other data structures. We'll discuss _how_ it works in [Chapter {{index journal, "weresquirrel example", "journalEvents function"}} -We need to compute a correlation for every type of event that occurs -in the data set. To do that, we first need to _find_ every type of -event. +We need to compute a correlation for every type of event that occurs in the dataset. To do that, we first need to _find_ every type of event. {{index "includes method", "push method"}} @@ -757,11 +505,9 @@ console.log(journalEvents(JOURNAL)); // → ["carrot", "exercise", "weekend", "bread", …] ``` -By going over all the events and adding those that aren't already in -there to the `events` array, the function collects every type of -event. +By adding any event names that aren't already in it to the `events` array, the function collects every type of event. -Using that, we can see all the ((correlation))s. +Using that function, we can see all the ((correlation))s: ```{test: no} for (let event of journalEvents(JOURNAL)) { @@ -772,13 +518,10 @@ 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. It _does_ -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. +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: ```{test: no, startCode: true} for (let event of journalEvents(JOURNAL)) { @@ -796,10 +539,7 @@ for (let event of journalEvents(JOURNAL)) { // → peanuts: 0.5902679812 ``` -Aha! There are two factors with a ((correlation)) that's clearly stronger -than the others. Eating ((peanuts)) has a strong positive effect on -the chance of turning into a squirrel, whereas brushing his teeth has -a significant negative effect. +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. @@ -814,40 +554,23 @@ console.log(phi(tableFor("peanut teeth", JOURNAL))); // → 1 ``` -That's a strong result. The phenomenon occurs precisely when Jacques -eats ((peanuts)) and fails to brush his teeth. If only he weren't such -a slob about dental hygiene, he'd have never even noticed his -affliction. +That's a strong result. The phenomenon occurs precisely when Jacques eats ((peanuts)) and fails to brush his teeth. If only he weren't such a slob about dental hygiene, he'd never even have noticed his affliction. -Knowing this, Jacques stops eating peanuts altogether and finds that -his transformations don't come back. +Knowing this, Jacques stops eating peanuts altogether and finds that his transformations stop. {{index "weresquirrel example"}} -For a few years, things go great for Jacques. But at some point he -loses his job. Because he lives in a nasty country where having no job -means having no medical services, he is forced to take employment with -a ((circus)) where he performs as _The Incredible Squirrelman_, -stuffing his mouth with peanut butter before every show. - -One day, fed up with this pitiful existence, Jacques fails to change -back into his human form, hops through a crack in the circus tent, and -vanishes into the forest. He is never seen again. +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"}} -We saw `push` and `pop`, which add and remove elements at the -end of an array, [earlier](data#array_methods) in this -chapter. The corresponding methods for adding and removing things at -the start of an array are called `unshift` and `shift`. +We saw `push` and `pop`, which add and remove elements at the end of an array, [earlier](data#array_methods) in this chapter. The corresponding methods for adding and removing things at the start of an array are called `unshift` and `shift`. ``` let todoList = []; @@ -864,19 +587,11 @@ function rememberUrgently(task) { {{index "task management example"}} -That program manages a queue of tasks. You add tasks to the end of the -queue by calling `remember("groceries")`, and when you're ready to do -something, you call `getTask()` to get (and remove) the front item -from the queue. The `rememberUrgently` function also adds a task but -adds it to the front instead of the back of the queue. +This program manages a queue of tasks. You add tasks to the end of the queue by calling `remember("groceries")`, and when you're ready to do something, you call `getTask()` to get (and remove) the front item from the queue. The `rememberUrgently` function also adds a task but adds it to the front instead of the back of the queue. {{index [array, searching], "indexOf method", "lastIndexOf method"}} -To search for a specific value, arrays provide an `indexOf` method. The method -searches through the array from the start to the end and returns the -index at which the requested value was found—or -1 if it wasn't found. -To search from the end instead of the start, there's a similar method -called `lastIndexOf`. +To search for a specific value, arrays provide an `indexOf` method. The method searches through the array from the start to the end and returns the index at which the requested value was found—or -1 if it wasn't found. To search from the end instead of the start, there's a similar method called `lastIndexOf`: ``` console.log([1, 2, 3, 2, 1].indexOf(2)); @@ -885,14 +600,11 @@ console.log([1, 2, 3, 2, 1].lastIndexOf(2)); // → 3 ``` -Both `indexOf` and `lastIndexOf` take an optional second argument that -indicates where to start searching. +Both `indexOf` and `lastIndexOf` take an optional second argument that indicates where to start searching. {{index "slice method", [array, indexing]}} -Another fundamental array method is `slice`, which takes start and end -indices and returns an array that has only the elements between them. -The start index is inclusive, the end index exclusive. +Another fundamental array method is `slice`, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive and the end index is exclusive. ``` console.log([0, 1, 2, 3, 4].slice(2, 4)); @@ -903,18 +615,13 @@ console.log([0, 1, 2, 3, 4].slice(2)); {{index [string, indexing]}} -When the end index is not given, `slice` will take all of the elements -after the start index. You can also omit the start index to copy the -entire array. +When the end index is not given, `slice` will take all of the elements after the start index. You can also omit the start index to copy the entire array. {{index concatenation, "concat method"}} -The `concat` method can be used to glue arrays together to create a -new array, similar to what the `+` operator does for strings. +The `concat` method can be used to append arrays together to create a new array, similar to what the `+` operator does for strings. -The following example shows both `concat` and `slice` in action. It takes -an array and an index, and it returns a new array that is a copy of -the original array with the element at the given index removed. +The following example shows both `concat` and `slice` in action. It takes an array and an index and returns a new array that is a copy of the original array with the element at the given index removed: ``` function remove(array, index) { @@ -925,15 +632,13 @@ console.log(remove(["a", "b", "c", "d", "e"], 2)); // → ["a", "b", "d", "e"] ``` -If you pass `concat` an argument that is not an array, that value will -be added to the new array as if it were a one-element array. +If you pass `concat` an argument that is not an array, that value will be added to the new array as if it were a one-element array. ## Strings and their properties {{index [string, properties]}} -We can read properties like `length` and `toUpperCase` from string -values. But if you try to add a new property, it doesn't stick. +We can read properties like `length` and `toUpperCase` from string values. But if we try to add a new property, it doesn't stick. ``` let kim = "Kim"; @@ -942,16 +647,11 @@ console.log(kim.age); // → undefined ``` -Values of type string, number, and Boolean are not objects, and though -the language doesn't complain if you try to set new properties on -them, it doesn't actually store those properties. As mentioned earlier, -such values are immutable and cannot be changed. +Values of type string, number, and Boolean are not objects, and though the language doesn't complain if you try to set new properties on them, it doesn't actually store those properties. As mentioned earlier, such values are immutable and cannot be changed. {{index [string, methods], "slice method", "indexOf method", [string, searching]}} -But these types do have built-in properties. Every string value has a -number of methods. Some very useful ones are `slice` and `indexOf`, -which resemble the array methods of the same name. +But these types do have built-in properties. Every string value has a number of methods. Some very useful ones are `slice` and `indexOf`, which resemble the array methods of the same name: ``` console.log("coconuts".slice(4, 7)); @@ -960,9 +660,7 @@ console.log("coconut".indexOf("u")); // → 5 ``` -One difference is that a string's `indexOf` can search for a string -containing more than one character, whereas the corresponding array -method looks only for a single element. +One difference is that a string's `indexOf` can search for a string containing more than one character, whereas the corresponding array method looks only for a single element: ``` console.log("one two three".indexOf("ee")); @@ -971,17 +669,16 @@ console.log("one two three".indexOf("ee")); {{index [whitespace, trimming], "trim method"}} -The `trim` method removes whitespace (spaces, newlines, tabs, and -similar characters) from the start and end of a string. +The `trim` method removes whitespace (spaces, newlines, tabs, and similar characters) from the start and end of a string: ``` console.log(" okay \n ".trim()); // → okay ``` -The `zeroPad` function from the [previous chapter](functions) also -exists as a method. It is called `padStart` and takes the desired -length and padding character as arguments. +{{id padStart}} + +The `zeroPad` function from the [previous chapter](functions) also exists as a method. It is called `padStart` and takes the desired length and padding character as arguments: ``` console.log(String(6).padStart(3, "0")); @@ -990,8 +687,9 @@ console.log(String(6).padStart(3, "0")); {{id split}} -You can split a string on every occurrence of another string with -`split` and join it again with `join`. +{{index "split method"}} + +You can split a string on every occurrence of another string with `split` and join it again with `join`: ``` let sentence = "Secretarybirds specialize in stomping"; @@ -1004,9 +702,7 @@ console.log(words.join(". ")); {{index "repeat method"}} -A string can be repeated with the `repeat` method, which creates a new -string containing multiple copies of the original string, glued -together. +A string can be repeated with the `repeat` method, which creates a new string containing multiple copies of the original string, glued together: ``` console.log("LA".repeat(3)); @@ -1015,10 +711,7 @@ console.log("LA".repeat(3)); {{index ["length property", "for string"], [string, indexing]}} -We have already seen the string type's `length` property. Accessing -the individual characters in a string looks like accessing array -elements (with a caveat that we'll discuss in [Chapter -?](higher_order#code_units)). +We have already seen the string type's `length` property. Accessing the individual characters in a string looks like accessing array elements (with a complication that we'll discuss in [Chapter ?](higher_order#code_units)). ``` let string = "abc"; @@ -1032,16 +725,9 @@ console.log(string[1]); ## Rest parameters -{{index "Math.max function"}} - -It can be useful for a function to accept any number of ((argument))s. -For example, `Math.max` computes the maximum of _all_ the arguments it -is given. - -{{index "period character", "max example", spread}} +{{index "Math.max function", "period character", "max example", spread, [array, "of rest arguments"]}} -To write such a function, you put three dots before the function's -last ((parameter)), like this: +It can be useful for a function to accept any number of ((argument))s. For example, `Math.max` computes the maximum of _all_ the arguments it is given. To write such a function, you put three dots before the function's last ((parameter)), like this: ```{includeCode: strip_log} function max(...numbers) { @@ -1055,15 +741,11 @@ console.log(max(4, 1, 9, -2)); // → 9 ``` -When such a function is called, the _((rest parameter))_ is bound to -an array containing all further arguments. If there are other -parameters before it, their values aren't part of that array. When, as -in `max`, it is the only parameter, it will hold all arguments. +When such a function is called, the _((rest parameter))_ is bound to an array containing all further arguments. If there are other parameters before it, their values aren't part of that array. When, as in `max`, it is the only parameter, it will hold all arguments. {{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]; @@ -1071,14 +753,11 @@ console.log(max(...numbers)); // → 7 ``` -This "((spread))s" out the array into the function call, passing its -elements as separate arguments. It is possible to include an array -like that along with other arguments, as in `max(9, ...numbers, 2)`. +This "((spread))s" out the array into the function call, passing its elements as separate arguments. It is possible to include an array like that along with other arguments, as in `max(9, ...numbers, 2)`. -{{index [array, "of rest arguments"], "square brackets"}} +{{index "[] (array)"}} -Square bracket array notation similarly allows the triple-dot operator -to spread another array into the new array. +Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array: ``` let words = ["never", "fully"]; @@ -1086,49 +765,39 @@ console.log(["will", ...words, "understand"]); // → ["will", "never", "fully", "understand"] ``` +{{index "{} (object)"}} + +This works even in curly brace objects, where it adds all properties from another object. If a property is added multiple times, the last value to be added wins: + +``` +let coordinates = {x: 10, y: 0}; +console.log({...coordinates, y: 5, z: 1}); +// → {x: 10, y: 5, z: 1} +``` + ## The Math object {{index "Math object", "Math.min function", "Math.max function", "Math.sqrt function", minimum, maximum, "square root"}} -As we've seen, `Math` is a grab bag of number-related utility -functions, such as `Math.max` (maximum), `Math.min` (minimum), and -`Math.sqrt` (square root). +As we've seen, `Math` is a grab bag of number-related utility functions such as `Math.max` (maximum), `Math.min` (minimum), and `Math.sqrt` (square root). {{index namespace, [object, property]}} {{id namespace_pollution}} -The `Math` object is used as a container to group a bunch of related -functionality. There is only one `Math` object, and it is almost never -useful as a value. Rather, it provides a _namespace_ so that all these -functions and values do not have to be global bindings. +The `Math` object is used as a container to group a bunch of related functionality. There is only one `Math` object, and it is almost never useful as a value. Rather, it provides a _namespace_ so that all these functions and values do not have to be global bindings. {{index [binding, naming]}} -Having too many global bindings "pollutes" the namespace. The more -names have been taken, the more likely you are to accidentally -overwrite the value of some existing binding. For example, it's not -unlikely to want to name something `max` in one of your programs. -Since JavaScript's built-in `max` function is tucked safely inside the -`Math` object, we don't have to worry about overwriting it. +Having too many global bindings "pollutes" the namespace. The more names have been taken, the more likely you are to accidentally overwrite the value of some existing binding. For example, it's not unlikely you'll want to name something `max` in one of your programs. Since JavaScript's built-in `max` function is tucked safely inside the `Math` object, you don't have to worry about overwriting it. {{index "let keyword", "const keyword"}} -Many languages will stop you, or at least warn you, when you are -defining a binding with a name that is already taken. JavaScript does -this for bindings you declared with `let` or `const` -but—perversely—not for standard bindings nor for bindings declared -with `var` or `function`. +Many languages will stop you, or at least warn you, when you are defining a binding with a name that is already taken. JavaScript does this for bindings you declared with `let` or `const` but—perversely—not for standard bindings nor for bindings declared with `var` or `function`. {{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) { @@ -1140,15 +809,11 @@ console.log(randomPointOnCircle(2)); // → {x: 0.3667, y: 1.966} ``` -If sines and cosines are not something you are familiar with, don't -worry. When they are used in this book, in [Chapter ?](dom#sin_cos), -I'll explain them. +If you're not familiar with sines and cosines, don't worry. I'll explain them when they are used in [Chapter ?](dom#sin_cos). {{index "Math.random function", "random number"}} -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()); @@ -1161,43 +826,28 @@ console.log(Math.random()); {{index "pseudorandom number", "random number"}} -Though computers are deterministic machines—they always react the same -way if given the same input—it is possible to have them produce -numbers that appear random. To do that, the machine keeps some hidden -value, and whenever you ask for a new random number, it performs -complicated computations on this hidden value to create a new value. -It stores a new value and returns some number derived from it. That -way, it can produce ever new, hard-to-predict numbers in a way that -_seems_ random. +Though computers are deterministic machines—they always react the same way if given the same input—it is possible to have them produce numbers that appear random. To do that, the machine keeps some hidden value, and whenever you ask for a new random number, it performs complicated computations on this hidden value to create a new value. It stores a new value and returns some number derived from it. That way, it can produce ever new, hard-to-predict numbers in a way that _seems_ random. {{index rounding, "Math.floor function"}} -If we want a whole random number instead of a fractional one, we can -use `Math.floor` (which rounds down to the nearest whole number) on -the result of `Math.random`. +If we want a whole random number instead of a fractional one, we can use `Math.floor` (which rounds down to the nearest whole number) on the result of `Math.random`: ```{test: no} console.log(Math.floor(Math.random() * 10)); // → 2 ``` -Multiplying the random number by 10 gives us a number greater than or -equal to 0 and below 10. Since `Math.floor` rounds down, this -expression will produce, with equal chance, any number from 0 through -9. +Multiplying the random number by 10 gives us a number greater than or equal to 0 and below 10. Since `Math.floor` rounds down, this expression will produce, with equal chance, any number from 0 through 9. {{index "Math.ceil function", "Math.round function", "Math.abs function", "absolute value"}} -There are also the functions `Math.ceil` (for "ceiling", which rounds -up to a whole number), `Math.round` (to the nearest whole number), and -`Math.abs`, which takes the absolute value of a number, meaning it -negates negative values but leaves positive ones as they are. +There are also the functions `Math.ceil` (for "ceiling", which rounds up to a whole number), `Math.round` (to the nearest whole number), and `Math.abs`, which takes the absolute value of a number, meaning it negates negative values but leaves positive ones as they are. ## Destructuring {{index "phi function"}} -Let's go back to the `phi` function for a moment. +Let's return to the `phi` function for a moment. ```{test: wrap} function phi(table) { @@ -1211,10 +861,7 @@ function phi(table) { {{index "destructuring binding", parameter}} -One of the reasons this function is awkward to read is that we have a -binding pointing at our array, but we'd much prefer to have bindings -for the _elements_ of the array, that is, `let n00 = table[0]` and so on. -Fortunately, there is a succinct way to do this in JavaScript. +One reason this function is awkward to read is that we have a binding pointing at our array, but we'd much prefer to have bindings for the _elements_ of the array—that is, `let n00 = table[0]` and so on. Fortunately, there is a succinct way to do this in JavaScript: ``` function phi([n00, n01, n10, n11]) { @@ -1226,15 +873,11 @@ function phi([n00, n01, n10, n11]) { {{index "let keyword", "var keyword", "const keyword", [binding, destructuring]}} -This also works for bindings created with `let`, `var`, or -`const`. If you know the value you are binding is an array, you can -use ((square brackets)) to "look inside" of the value, binding its -contents. +This also works for bindings created with `let`, `var`, or `const`. If you know that the value you are binding is an array, you can use ((square brackets)) to "look inside" of the value, binding its contents. {{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}; @@ -1244,50 +887,56 @@ console.log(name); {{index null, undefined}} -Note that if you try to destructure `null` or `undefined`, you get an -error, much as you would if you directly try to access a property -of those values. +Note that if you try to destructure `null` or `undefined`, you get an error, much as you would if you directly try to access a property of those values. + +## Optional property access + +{{index "optional chaining", "period character"}} + +When you aren't sure whether a given value produces an object, but still want to read a property from it when it does, you can use a variant of the dot notation: `object?.property`. + +``` +function city(object) { + return object.address?.city; +} +console.log(city({address: {city: "Toronto"}})); +// → Toronto +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. + +A similar notation can be used with square bracket access, and even with function calls, by putting `?.` in front of the parentheses or brackets: + +``` +console.log("string".notAMethod?.()); +// → undefined +console.log({}.arrayProp?.[0]); +// → undefined +``` ## JSON {{index [array, representation], [object, representation], "data format", [memory, organization]}} -Because properties only grasp their value, rather than contain it, -objects and arrays are stored in the computer's memory as -sequences of bits holding the _((address))es_—the place in memory—of -their contents. So an array with another array inside of it consists -of (at least) one memory region for the inner array, and another for -the outer array, containing (among other things) a binary number that -represents the position of the inner array. - -If you want to save data in a file for later or send it to another -computer over the network, you have to somehow convert these tangles -of memory addresses to a description that can be stored or sent. You -_could_ send over your entire computer memory along with the address -of the value you're interested in, I suppose, but that doesn't seem -like the best approach. +Because properties grasp their value rather than contain it, objects and arrays are stored in the computer's memory as sequences of bits holding the _((address))es_—the place in memory—of their contents. An array with another array inside of it consists of (at least) one memory region for the inner array and another for the outer array, containing (among other things) a number that represents the address of the inner array. + +If you want to save data in a file for later or send it to another computer over the network, you have to somehow convert these tangles of memory addresses to a description that can be stored or sent. You _could_ send over your entire computer memory along with the address of the value you're interested in, I suppose, but that doesn't seem like the best approach. {{indexsee "JavaScript Object Notation", JSON}} {{index serialization, "World Wide Web"}} -What we can do is _serialize_ the data. That means it is converted -into a flat description. A popular serialization format is called -_((JSON))_ (pronounced "Jason"), which stands for JavaScript Object -Notation. It is widely used as a data storage and communication format -on the Web, even in languages other than JavaScript. +What we can do is _serialize_ the data. That means it is converted into a flat description. A popular serialization format is called _((JSON))_ (pronounced "Jason"), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the web, even with languages other than JavaScript. {{index [array, notation], [object, creation], [quoting, "in JSON"], comment}} -JSON looks similar to JavaScript's way of writing arrays and objects, -with a few restrictions. All property names have to be surrounded by -double quotes, and only simple data expressions are allowed—no -function calls, bindings, or anything that involves actual -computation. Comments are not allowed in JSON. +JSON looks similar to JavaScript's way of writing arrays and objects, with a few restrictions. All property names have to be surrounded by double quotes, and only simple data expressions are allowed—no function calls, bindings, or anything that involves actual computation. Comments are not allowed in JSON. A journal entry might look like this when represented as JSON data: -```{lang: "application/json"} +```{lang: "json"} { "squirrel": false, "events": ["work", "touched tree", "pizza", "running"] @@ -1296,10 +945,7 @@ A journal entry might look like this when represented as JSON data: {{index "JSON.stringify function", "JSON.parse function", serialization, deserialization, parsing}} -JavaScript gives us the functions `JSON.stringify` and `JSON.parse` to -convert data to and from this format. The first takes a JavaScript -value and returns a JSON-encoded string. The second takes such a -string and converts it to the value it encodes. +JavaScript gives us the functions `JSON.stringify` and `JSON.parse` to convert data to and from this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes: ``` let string = JSON.stringify({squirrel: false, @@ -1312,25 +958,13 @@ console.log(JSON.parse(string).events); ## Summary -Objects and arrays (which are a specific kind of object) provide ways -to group several values into a single value. Conceptually, this allows -us to put a bunch of related things in a bag and run around with the -bag, instead of wrapping our arms around all of the individual things -and trying to hold on to them separately. +Objects and arrays provide ways to group several values into a single value. This allows us to put a bunch of related things in a bag and run around with the bag instead of wrapping our arms around all of the individual things and trying to hold on to them separately. -Most values in JavaScript have properties, the exceptions being `null` -and `undefined`. Properties are accessed using `value.prop` or -`value["prop"]`. Objects tend to use names for their properties -and store more or less a fixed set of them. Arrays, on the other hand, -usually contain varying amounts of conceptually identical values and -use numbers (starting from 0) as the names of their properties. +Most values in JavaScript have properties, with the exceptions being `null` and `undefined`. Properties are accessed using `value.prop` or `value["prop"]`. Objects tend to use names for their properties and store more or less a fixed set of them. Arrays, on the other hand, usually contain varying amounts of conceptually identical values and use numbers (starting from 0) as the names of their properties. -There _are_ some named properties in arrays, such as `length` and a -number of methods. Methods are functions that live in properties and -(usually) act on the value they are a property of. +There _are_ some named properties in arrays, such as `length` and a number of methods. Methods are functions that live in properties and (usually) act on the value of which they are a property. -You can iterate over arrays using a special kind of `for` loop—`for -(let element of array)`. +You can iterate over arrays using a special kind of `for` loop: `for (let element of array)`. ## Exercises @@ -1338,8 +972,7 @@ You can iterate over arrays using a special kind of `for` loop—`for {{index "summing (exercise)"}} -The [introduction](intro) of this book alluded to the following as a -nice way to compute the sum of a range of numbers: +The [introduction](intro) of this book alluded to the following as a nice way to compute the sum of a range of numbers: ```{test: no} console.log(sum(range(1, 10))); @@ -1347,23 +980,13 @@ console.log(sum(range(1, 10))); {{index "range function", "sum function"}} -Write a `range` function that takes two arguments, `start` and `end`, -and returns an array containing all the numbers from `start` up to -(and including) `end`. +Write a `range` function that takes two arguments, `start` and `end`, and returns an array containing all the numbers from `start` up to and including `end`. -Next, write a `sum` function that takes an array of numbers and -returns the sum of these numbers. Run the example program and see -whether it does indeed return 55. +Next, write a `sum` function that takes an array of numbers and returns the sum of these numbers. Run the example program and see whether it does indeed return 55. {{index "optional argument"}} -As a bonus assignment, modify your `range` function to take an -optional third argument that indicates the "step" value used when -building the array. If no step is given, the elements go up by -increments of one, corresponding to the old behavior. The function -call `range(1, 10, 2)` should return `[1, 3, 5, 7, 9]`. Make sure it -also works with negative step values so that `range(5, 2, -1)` -produces `[5, 4, 3, 2]`. +As a bonus assignment, modify your `range` function to take an optional third argument that indicates the "step" value used when building the array. If no step is given, the elements should go up by increments of one, corresponding to the old behavior. The function call `range(1, 10, 2)` should return `[1, 3, 5, 7, 9]`. Make sure this also works with negative step values so that `range(5, 2, -1)` produces `[5, 4, 3, 2]`. {{if interactive @@ -1384,33 +1007,19 @@ if}} {{index "summing (exercise)", [array, creation], "square brackets"}} -Building up an array is most easily done by first initializing a -binding to `[]` (a fresh, empty array) and repeatedly calling its -`push` method to add a value. Don't forget to return the array at the -end of the function. +Building up an array is most easily done by first initializing a binding to `[]` (a fresh, empty array) and repeatedly calling its `push` method to add a value. Don't forget to return the array at the end of the function. {{index [array, indexing], comparison}} -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"}} +Since the end boundary is inclusive, you'll need to use the `<=` operator rather than `<` to check for the end of your loop. -The step parameter can be an optional parameter that defaults (using -the `=` operator) to 1. +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. +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}} @@ -1418,28 +1027,22 @@ 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`, takes an array as argument and produces a _new_ array -that has the same elements in the inverse order. The second, -`reverseArrayInPlace`, does what the `reverse` method does: it -_modifies_ 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"}} -Thinking back to the notes about side effects and pure functions in -the [previous chapter](functions#pure), which variant do you expect to -be useful in more situations? Which one runs faster? +Thinking back to the notes about side effects and pure functions in the [previous chapter](functions#pure), which variant do you expect to be useful in more situations? Which one runs faster? {{if interactive ```{test: no} // Your code here. -console.log(reverseArray(["A", "B", "C"])); +let myArray = ["A", "B", "C"]; +console.log(reverseArray(myArray)); // → ["C", "B", "A"]; +console.log(myArray); +// → ["A", "B", "C"]; let arrayValue = [1, 2, 3, 4, 5]; reverseArrayInPlace(arrayValue); console.log(arrayValue); @@ -1452,29 +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(0)` 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. +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 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}} @@ -1484,11 +1071,7 @@ hint}} {{index ["data structure", list], "list (exercise)", "linked list", array, collection}} -Objects, as generic blobs of values, can be used to build all sorts of -data structures. A common data structure is the _list_ (not to be -confused with array). A list is a nested set of objects, with the -first object holding a reference to the second, the second to the -third, and so on. +As generic blobs of values, objects can be used to build all sorts of data structures. A common data structure is the _list_ (not to be confused with arrays). A list is a nested set of objects, with the first object holding a reference to the second, the second to the third, and so on: ```{includeCode: true} let list = { @@ -1503,27 +1086,15 @@ let list = { }; ``` -The resulting objects form a chain, like this: +The resulting objects form a chain, as shown in the following diagram: -{{figure {url: "img/linked-list.svg", alt: "A linked list",width: "8cm"}}} +{{figure {url: "img/linked-list.svg", alt: "A diagram showing the memory structure of a linked list. There are 3 cells, each with a value field holding a number, and a 'rest' field with an arrow to the rest of the list. The first cell's arrow points at the second cell, the second cell's arrow at the last cell, and the last cell's 'rest' field holds null.",width: "8cm"}}} {{index "structure sharing", [memory, structure sharing]}} -A nice thing about lists is that they can share parts of their -structure. For example, if I create two new values `{value: 0, rest: -list}` and `{value: -1, rest: list}` (with `list` referring to the -binding defined earlier), they are both independent lists, but they -share the structure that makes up their last three elements. The -original list is also still a valid three-element list. - -Write a function `arrayToList` that builds up a list structure like -the one shown when given `[1, 2, 3]` as argument. Also write a -`listToArray` function that produces an array from a list. Then add a -helper function `prepend`, which takes an element and a list and -creates a new list that adds the element to the front of the input -list, and `nth`, which takes a list and a number and returns the -element at the given position in the list (with zero referring to the -first element) or `undefined` when there is no such element. +A nice thing about lists is that they can share parts of their structure. For example, if I create two new values `{value: 0, rest: list}` and `{value: -1, rest: list}` (with `list` referring to the binding defined earlier), they are both independent lists, but they share the structure that makes up their last three elements. The original list is also still a valid three-element list. + +Write a function `arrayToList` that builds up a list structure like the one shown when given `[1, 2, 3]` as argument. Also write a `listToArray` function that produces an array from a list. Add the helper functions `prepend`, which takes an element and a list and creates a new list that adds the element to the front of the input list, and `nth`, which takes a list and a number and returns the element at the given position in the list (with zero referring to the first element) or `undefined` when there is no such element. {{index recursion}} @@ -1550,36 +1121,21 @@ 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"}} -To run over a list (in `listToArray` and `nth`), a `for` loop -specification like this can be used: +To run over a list (in `listToArray` and `nth`), a `for` loop specification like this can be used: ``` 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}} -The recursive version of `nth` will, similarly, look at an ever -smaller part of the "tail" of the list and at the same time count down -the index until it reaches zero, at which point it can return the -`value` property of the node it is looking at. To get the zeroth -element of a list, you simply take the `value` property of its head -node. To get element _N_ + 1, you take the *N*th element of the list -that's in this list's `rest` property. +The recursive version of `nth` will, similarly, look at an ever smaller part of the "tail" of the list and at the same time count down the index until it reaches zero, at which point it can return the `value` property of the node it is looking at. To get the zeroth element of a list, you simply take the `value` property of its head node. To get element _N_ + 1, you take the *N*th element of the list that's in this list's `rest` property. hint}} @@ -1589,27 +1145,17 @@ hint}} {{index "deep comparison (exercise)", [comparison, deep], "deep comparison", "== operator"}} -The `==` operator compares objects by identity. But sometimes you'd -prefer to compare the values of their actual properties. +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"}} -To find out whether values should be compared directly (use the `===` -operator for that) or have their properties compared, you can use the -`typeof` operator. If it produces `"object"` for both values, you -should do a deep comparison. But you have to take one silly exception -into account: because of a historical accident, `typeof null` also -produces `"object"`. +To find out whether values should be compared directly (using the `===` operator for that) or have their properties compared, you can use the `typeof` operator. If it produces `"object"` for both values, you should do a deep comparison. But you have to take one silly exception into account: because of a historical accident, `typeof null` also produces `"object"`. {{index "Object.keys function"}} -The `Object.keys` function will be useful when you need to go over the -properties of objects to compare them. +The `Object.keys` function will be useful when you need to go over the properties of objects to compare them. {{if interactive @@ -1631,28 +1177,14 @@ if}} {{index "deep comparison (exercise)", [comparison, deep], "typeof operator", "=== operator"}} -Your test for whether you are dealing with a real object will look -something like `typeof x == "object" && x != null`. Be careful to -compare properties only when _both_ arguments are objects. In all -other cases you can just immediately return the result of applying -`===`. +Your test for whether you are dealing with a real object will look something like `typeof x == "object" && x != null`. Be careful to compare properties only when _both_ arguments are objects. In all other cases you can just immediately return the result of applying `===`. {{index "Object.keys function"}} -Use `Object.keys` to go over the properties. You need to test whether -both objects have the same set of property names and whether those -properties have identical values. One way to do that is to ensure that -both objects have the same number of properties (the lengths of the -property lists are the same). And then, when looping over one of the -object's properties to compare them, always first make sure -the other actually has a property by that name. If they have the same -number of properties and all properties in one also exist in the -other, they have the same set of property names. +Use `Object.keys` to go over the properties. You need to test whether both objects have the same set of property names and whether those properties have identical values. One way to do that is to ensure that both objects have the same number of properties (the lengths of the property lists are the same). And then, when looping over one of the object's properties to compare them, always first make sure the other actually has a property by that name. If they have the same number of properties and all properties in one also exist in the other, they have the same set of property names. {{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 e8e9e1e49..8a37a8dd8 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -2,46 +2,23 @@ # Higher-Order Functions -{{if interactive - -{{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} - -Tzu-li and Tzu-ssu were boasting about the size of their latest -programs. 'Two-hundred thousand lines,' said Tzu-li, 'not counting -comments!' Tzu-ssu responded, 'Pssh, mine is almost a *million* lines -already.' Master Yuan-Ma said, 'My best program has five hundred -lines.' Hearing this, Tzu-li and Tzu-ssu were enlightened. - -quote}} - -if}} - {{quote {author: "C.A.R. Hoare", title: "1980 ACM Turing Award Lecture", chapter: true} {{index "Hoare, C.A.R."}} -There are two ways of constructing a software design: One way is to -make it so simple that there are obviously no deficiencies, and the -other way is to make it so complicated that there are no obvious -deficiencies. +There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. quote}} -{{figure {url: "img/chapter_picture_5.jpg", alt: "Letters from different scripts", chapter: true}}} +{{figure {url: "img/chapter_picture_5.jpg", alt: "Illustration showing letters and hieroglyphs from different scripts—Latin, Greek, Arabic, ancient Egyptian, and others", chapter: true}}} {{index "program size"}} -A large program is a costly program, and not just because of the time -it takes to build. Size almost always involves ((complexity)), and -complexity confuses programmers. Confused programmers, in turn, -introduce mistakes (_((bug))s_) into programs. A large program then -provides a lot of space for these bugs to hide, making them hard to -find. +A large program is a costly program, and not just because of the time it takes to build. Size almost always involves ((complexity)), and complexity confuses programmers. Confused programmers, in turn, introduce mistakes (_((bug))s_) into programs. A large program then provides a lot of space for these bugs to hide, making them hard to find. {{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; @@ -62,44 +39,25 @@ Which one is more likely to contain a bug? {{index "program size"}} -If we count the size of the definitions of `sum` and `range`, the -second program is also big—even bigger than the first. But still, I'd -argue that it is more likely to be correct. +If we count the size of the definitions of `sum` and `range`, the second program is also big—even bigger than the first. But still, I'd argue that it is more likely to be correct. {{index [abstraction, "with higher-order functions"], "domain-specific language"}} -It is more likely to be correct because the solution is expressed in a -((vocabulary)) that corresponds to the problem being solved. Summing a -range of numbers isn't about loops and counters. It is about ranges -and sums. +This is because the solution is expressed in a ((vocabulary)) that corresponds to the problem being solved. Summing a range of numbers isn't about loops and counters. It is about ranges and sums. -The definitions of this vocabulary (the functions `sum` and `range`) -will still involve loops, counters, and other incidental details. But -because they are expressing simpler concepts than the program as a -whole, they are easier to get right. +The definitions of this vocabulary (the functions `sum` and `range`) will still involve loops, counters, and other incidental details. But because they are expressing simpler concepts than the program as a whole, they are easier to get right. ## Abstraction -In the context of programming, these kinds of vocabularies are usually -called _((abstraction))s_. Abstractions hide details and give us the -ability to talk about problems at a higher (or more abstract) level. +In the context of programming, these kinds of vocabularies are usually called _((abstraction))s_. Abstractions give us the ability to talk about problems at a higher (or more abstract) level, without getting sidetracked by uninteresting details. {{index "recipe analogy", "pea soup"}} -As an analogy, compare these two recipes for pea soup. The first one -goes like this: +As an analogy, compare these two recipes for pea soup. The first goes like this: {{quote -Put 1 cup of dried peas per person into a container. Add water until -the peas are well covered. Leave the peas in water for at least 12 -hours. Take the peas out of the water and put them in a cooking pan. -Add 4 cups of water per person. Cover the pan and keep the peas -simmering for two hours. Take half an onion per person. Cut it into -pieces with a knife. Add it to the peas. Take a stalk of celery per -person. Cut it into pieces with a knife. Add it to the peas. Take a -carrot per person. Cut it into pieces. With a knife! Add it to the -peas. Cook for 10 more minutes. +Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas. Take a stalk of celery per person. Cut it into pieces with a knife. Add it to the peas. Take a carrot per person. Cut it into pieces. With a knife! Add it to the peas. Cook for 10 more minutes. quote}} @@ -107,41 +65,31 @@ And this is the second recipe: {{quote -Per person: 1 cup dried split peas, half a chopped onion, a stalk of -celery, and a carrot. +Per person: 1 cup dried split peas, 4 cups of water, half a chopped onion, a stalk of celery, and a carrot. -Soak peas for 12 hours. Simmer for 2 hours in 4 cups of water -(per person). Chop and add vegetables. Cook for 10 more minutes. +Soak peas for 12 hours. Simmer for 2 hours. Chop and add vegetables. Cook for 10 more minutes. quote}} {{index vocabulary}} -The second is shorter and easier to interpret. But you do need to -understand a few more cooking-related words such as _soak_, _simmer_, _chop_, -and, I guess, _vegetable_. +The second is shorter and easier to interpret. But you do need to understand a few more cooking-related words such as _soak_, _simmer_, _chop_, and, I guess, _vegetable_. -When programming, we can't rely on all the words we need to be waiting -for us in the dictionary. Thus, we might fall into the pattern of the -first recipe—work out the precise steps the computer has to perform, -one by one, blind to the higher-level concepts that they express. +When programming, we can't rely on all the words we need to be waiting for us in the dictionary. Thus, we might fall into the pattern of the first recipe—work out the precise steps the computer has to perform, one by one, blind to the higher-level concepts they express. {{index abstraction}} -It is a useful skill, in programming, to notice when you are working -at too low a level of abstraction. +It is a useful skill, in programming, to notice when you are working at too low a level of abstraction. ## Abstracting repetition {{index [array, iteration]}} -Plain functions, as we've seen them so far, are a good way to build -abstractions. But sometimes they fall short. +Plain functions, as we've seen them so far, are a good way to build abstractions. But sometimes they fall short. {{index "for loop"}} -It is common for a program to do something a given number of times. -You can write a `for` ((loop)) for that, like this: +It is common for a program to do something a given number of times. You can write a `for` ((loop)) for that, like this: ``` for (let i = 0; i < 10; i++) { @@ -149,8 +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) { @@ -164,9 +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) { @@ -181,8 +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 = []; @@ -195,30 +139,17 @@ console.log(labels); {{index "loop body", [braces, body], [parentheses, arguments]}} -This is structured a little like a `for` loop—it first describes the -kind of loop and then provides a body. However, the body is now written -as a function value, which is wrapped in the parentheses of the -call to `repeat`. This is why it has to be closed with the closing -brace _and_ closing parenthesis. In cases like this example, where the -body is a single small expression, you could also omit the -braces and write the loop on a single line. +This is structured a little like a `for` loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to `repeat`. This is why it has to be closed with the closing brace _and_ closing parenthesis. In cases like this example, where the body is a single small expression, you could also omit the braces and write the loop on a single line. ## Higher-order functions {{index [function, "higher-order"], [function, "as value"]}} -Functions that operate on other functions, either by taking them as -arguments or by returning them, are called _higher-order functions_. -Since we have already seen that functions are regular values, there is -nothing particularly remarkable about the fact that such functions -exist. The term comes from ((mathematics)), where the distinction -between functions and other values is taken more seriously. +Functions that operate on other functions, either by taking them as arguments or by returning them, are called _higher-order functions_. Since we have already seen that functions are regular values, there is nothing particularly remarkable about the fact that such functions exist. The term comes from ((mathematics)), where the distinction between functions and other values is taken more seriously. {{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) { @@ -229,7 +160,7 @@ console.log(greaterThan10(11)); // → true ``` -And we can have functions that change other functions. +We can also have functions that change other functions. ``` function noisy(f) { @@ -245,8 +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) { @@ -264,8 +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)); @@ -273,36 +202,24 @@ like a `for`/`of` loop as a higher-order function. // → B ``` -## Script data set +{{id scripts}} -One area where higher-order functions shine is data processing. To process data, we'll need some actual data. This chapter will -use a ((data set)) about scripts—((writing system))s such as Latin, -Cyrillic, or Arabic. +## Script dataset -Remember ((Unicode)) from [Chapter ?](values#unicode), the system that -assigns a number to each character in written language? Most of these -characters are associated with a specific script. The standard -contains 140 different scripts—81 are still in use today, and 59 -are historic. +One area where higher-order functions shine is data processing. To process data, we'll need some actual example data. This chapter will use a ((dataset)) about scripts—((writing system))s such as Latin, Cyrillic, or Arabic. -Though I can fluently read only Latin characters, I appreciate the -fact that people are writing texts in at least 80 other writing -systems, many of which I wouldn't even recognize. For example, here's -a sample of ((Tamil)) handwriting: +Remember ((Unicode)), the system that assigns a number to each character in written language, from [Chapter ?](values#unicode)? Most of these characters are associated with a specific script. The standard contains 140 different scripts, of which 81 are still in use today and 59 are historic. -{{figure {url: "img/tamil.png", alt: "Tamil handwriting"}}} +Though I can fluently read only Latin characters, I appreciate the fact that people are writing texts in at least 80 other writing systems, many of which I wouldn't even recognize. For example, here's a sample of ((Tamil)) handwriting: -{{index "SCRIPTS data set"}} +{{figure {url: "img/tamil.png", alt: "A line of verse in Tamil handwriting. The characters are relatively simple, and neatly separated, yet completely different from Latin."}}} -The example ((data set)) contains some pieces of information about the -140 scripts defined in Unicode. It is available in the [coding -sandbox](https://eloquentjavascript.net/code#5) for this chapter[ -([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if -book} as the `SCRIPTS` binding. The binding contains an array of -objects, each of which describes a script. +{{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. -```{lang: "application/json"} + +```{lang: "json"} { name: "Coptic", ranges: [[994, 1008], [11392, 11508], [11513, 11520]], @@ -313,28 +230,17 @@ objects, each of which describes a script. } ``` -Such an object tells us the name of the script, the Unicode ranges -assigned to it, the direction in which it is written, the -(approximate) origin time, whether it is still in use, and a link to -more information. The direction may be `"ltr"` for left to right, `"rtl"` -for right to left (the way Arabic and Hebrew text are written), or -`"ttb"` for top to bottom (as with Mongolian writing). +Such an object tells us the name of the script, the Unicode ranges assigned to it, the direction in which it is written, the (approximate) origin time, whether it is still in use, and a link to more information. The direction may be `"ltr"` for left to right, `"rtl"` for right to left (the way Arabic and Hebrew text are written), or `"ttb"` for top to bottom (as with Mongolian writing). {{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 {{index [array, methods], [array, filtering], "filter method", [function, "higher-order"], "predicate function"}} -To find the scripts in the data set that are still in use, the -following function might be helpful. It filters out the elements in an -array that don't pass a test. +If we want to find the scripts in the dataset that are still in use, the following function might be helpful. It filters out elements in an array that don't pass a test. ``` function filter(array, test) { @@ -353,20 +259,13 @@ console.log(filter(SCRIPTS, script => script.living)); {{index [function, "as value"], [function, application]}} -The function uses the argument named `test`, a function value, to fill -a "gap" in the computation—the process of deciding which elements to -collect. +The function uses the argument named `test`, a function value, to fill a "gap" in the computation—the process of deciding which elements to collect. {{index "filter method", "pure function", "side effect"}} -Note how the `filter` function, rather than deleting elements from the -existing array, builds up a new array with only the elements that pass -the test. This function is _pure_. It does not modify the array it is -given. +Note how the `filter` function, rather than deleting elements from the existing array, builds up a new array with only the elements that pass the test. This function is _pure_. It does not modify the array it is given. -Like `forEach`, `filter` is a ((standard)) array method. The example -defined the function only to show what it does internally. -From now on, we'll use it like this instead: +Like `forEach`, `filter` is a ((standard)) array method. The example defined the function only to show what it does internally. From now on, we'll use it like this instead: ``` console.log(SCRIPTS.filter(s => s.direction == "ttb")); @@ -379,16 +278,11 @@ console.log(SCRIPTS.filter(s => s.direction == "ttb")); {{index [array, methods], "map method"}} -Say we have an array of objects representing scripts, produced by -filtering the `SCRIPTS` array somehow. But we want an array of names, -which is easier to inspect. +Say we have an array of objects representing scripts, produced by filtering the `SCRIPTS` array somehow. We want an array of names instead, which is easier to inspect. {{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) { @@ -410,25 +304,15 @@ Like `forEach` and `filter`, `map` is a standard array method. {{index [array, methods], "summing example", "reduce method"}} -Another common thing to do with arrays is to compute a single value -from them. Our recurring example, summing a collection of numbers, is -an instance of this. Another example is finding the script with -the most characters. +Another common thing to do with arrays is to compute a single value from them. Our recurring example, summing a collection of numbers, is an instance of this. Another example is finding the script with the most characters. {{indexsee "fold", "reduce method"}} {{index [function, "higher-order"], "reduce method"}} -The higher-order operation that represents this pattern is called -_reduce_ (sometimes also called _fold_). It builds a value by -repeatedly taking a single element from the array and combining it -with the current value. When summing numbers, you'd start with the -number zero and, for each element, add that to the sum. +The higher-order operation that represents this pattern is called _reduce_ (sometimes also called _fold_). It builds a value by repeatedly taking a single element from the array and combining it with the current value. When summing numbers, you'd start with the number zero and, for each element, add that to the sum. -The parameters to `reduce` are, apart from the array, a combining -function and a start value. This function is a little less -straightforward than `filter` and `map`, so take a close look at -it: +The parameters to `reduce` are, apart from the array, a combining function and a start value. This function is a little less straightforward than `filter` and `map`, so take a close look at it: ``` function reduce(array, combine, start) { @@ -443,13 +327,9 @@ console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); // → 10 ``` -{{index "reduce method", "SCRIPTS data set"}} +{{index "reduce method", "SCRIPTS dataset"}} -The standard array method `reduce`, which of course corresponds to -this function, has an added convenience. If your array contains at -least one element, you are allowed to leave off the `start` argument. -The method will take the first element of the array as its start value -and start reducing at the second element. +The standard array method `reduce`, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the `start` argument. The method will take the first element of the array as its start value and start reducing at the second element. ``` console.log([1, 2, 3, 4].reduce((a, b) => a + b)); @@ -458,8 +338,7 @@ console.log([1, 2, 3, 4].reduce((a, b) => a + b)); {{index maximum, "characterCount function"}} -To use `reduce` (twice) to find the script with the most characters, -we can write something like this: +To use `reduce` (twice) to find the script with the most characters, we can write something like this: ``` function characterCount(script) { @@ -474,28 +353,15 @@ console.log(SCRIPTS.reduce((a, b) => { // → {name: "Han", …} ``` -The `characterCount` function reduces the ranges assigned to a script -by summing their sizes. Note the use of destructuring in the parameter -list of the reducer function. The second call to `reduce` then uses -this to find the largest script by repeatedly comparing two scripts -and returning the larger one. +The `characterCount` function reduces the ranges assigned to a script by summing their sizes. Note the use of destructuring in the parameter list of the reducer function. The second call to `reduce` then uses this to find the largest script by repeatedly comparing two scripts and returning the larger one. -The Han script has more than 89,000 characters assigned to it in the -Unicode standard, making it by far the biggest writing system in the -data set. Han is a script (sometimes) used for Chinese, Japanese, and -Korean text. Those languages share a lot of characters, though they -tend to write them differently. The (U.S.-based) Unicode Consortium -decided to treat them as a single writing system to save -character codes. This is called _Han unification_ and still makes some -people very angry. +The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the dataset. Han is a script sometimes used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (US-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called _Han unification_ and still makes some people very angry. ## Composability {{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; @@ -509,16 +375,13 @@ console.log(biggest); // → {name: "Han", …} ``` -There are a few more bindings, and the program is four lines -longer. But it is still very readable. +There are a few more bindings, and the program is four lines longer, but it is still very readable. {{index "average function", composability, [function, "higher-order"], "filter method", "map method", "reduce method"}} {{id average_function}} -Higher-order functions start to shine when you need to _compose_ -operations. As an example, let's write code that finds the average -year of origin for living and dead scripts in the data set. +The abstractions these functions provide really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the dataset. ``` function average(array) { @@ -533,12 +396,7 @@ console.log(Math.round(average( // → 204 ``` -So 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. +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)). @@ -554,30 +412,19 @@ console.log(Math.round(total / count)); // → 1165 ``` -But it is harder to see what was being computed and how. And because -intermediate results aren't represented as coherent values, it'd be a -lot more work to extract something like `average` into a separate -function. +However, it is harder to see what was being computed and how. And because intermediate results aren't represented as coherent values, it'd be a lot more work to extract something like `average` into a separate function. {{index efficiency, [array, creation]}} -In terms of what the computer is actually doing, these two approaches -are also quite different. The first will build up new arrays when -running `filter` and `map`, whereas the second computes only some -numbers, doing less work. You can usually afford the readable -approach, but if you're processing huge arrays, and doing so many -times, the less abstract style might be worth the extra speed. +In terms of what the computer is actually doing, these two approaches are also quite different. The first will build up new arrays when running `filter` and `map`, whereas the second computes only some numbers, doing less work. You can usually afford the readable approach, but if you're processing huge arrays and doing so many times, the less abstract style might be worth the extra speed. ## Strings and character codes -{{index "SCRIPTS data set"}} +{{index "SCRIPTS dataset"}} -One use of the data set would be figuring out what script a piece of -text is using. Let's go through a program that does this. +One interesting use of this dataset would be figuring out what script a piece of text is using. Let's go through a program that does this. -Remember that each script has an array of character code ranges -associated with it. So given a character code, we could use a function -like this to find the corresponding script (if any): +Remember that each script has an array of character code ranges associated with it. Given a character code, we could use a function like this to find the corresponding script (if any): {{index "some method", "predicate function", [array, methods]}} @@ -597,41 +444,21 @@ console.log(characterScript(121)); // → {name: "Latin", …} ``` -The `some` method is another higher-order function. It takes a test -function and tells you whether that function returns true for any of the -elements in the array. +The `some` method is another higher-order function. It takes a test function and tells you whether that function returns true for any of the elements in the array. {{id code_units}} But how do we get the character codes in a string? -In [Chapter ?](values) I mentioned that JavaScript ((string))s are -encoded as a sequence of 16-bit numbers. These are called _((code -unit))s_. A ((Unicode)) ((character)) code was initially supposed to -fit within such a unit (which gives you a little over 65,000 -characters). When it became clear that wasn't going to be enough, many -people balked at the need to use more memory per character. To address -these concerns, ((UTF-16)), the format used by JavaScript strings, was -invented. It describes most common characters using a single 16-bit -code unit but uses a pair of two such units for others. +In [Chapter ?](values) I mentioned that JavaScript ((string))s are encoded as a sequence of 16-bit numbers. These are called _((code unit))s_. A ((Unicode)) ((character)) code was initially supposed to fit within such a unit (which gives you a little over 65,000 characters). When it became clear that wasn't going to be enough, many people balked at the need to use more memory per character. To address these concerns, ((UTF-16)), the format also used by JavaScript strings, was invented. It describes most common characters using a single 16-bit code unit but uses a pair of two such units for others. {{index error}} -UTF-16 is generally considered a bad idea today. It seems almost -intentionally designed to invite mistakes. It's easy to write programs -that pretend code units and characters are the same thing. And if your -language doesn't use two-unit characters, that will appear to work -just fine. But as soon as someone tries to use such a program with -some less common ((Chinese characters)), it breaks. Fortunately, with -the advent of ((emoji)), everybody has started using two-unit -characters, and the burden of dealing with such problems is more -fairly distributed. +UTF-16 is generally considered a bad idea today. It seems almost intentionally designed to invite mistakes. It's easy to write programs that pretend code units and characters are the same thing. And if your language doesn't use two-unit characters, that will appear to work just fine. But as soon as someone tries to use such a program with some less common ((Chinese characters)), it breaks. Fortunately, with the advent of ((emoji)), everybody has started using two-unit characters, and the burden of dealing with such problems is more fairly distributed. {{index [string, length], [string, indexing], "charCodeAt method"}} -Unfortunately, obvious operations on JavaScript strings, such as -getting their length through the `length` property and accessing their -content using square brackets, deal only with code units. +Unfortunately, obvious operations on JavaScript strings, such as getting their length through the `length` property and accessing their content using square brackets, deal only with code units. ```{test: no} // Two emoji characters, horse and shoe @@ -648,21 +475,11 @@ console.log(horseShoe.codePointAt(0)); {{index "codePointAt method"}} -JavaScript's `charCodeAt` method gives you a code unit, not a full -character code. The `codePointAt` method, added later, does give a -full Unicode character. So we could use that to get characters from a -string. But the argument passed to `codePointAt` is still an index -into the sequence of code units. So to run over all characters in a -string, we'd still need to deal with the question of whether a -character takes up one or two code units. +JavaScript's `charCodeAt` method gives you a code unit, not a full character code. The `codePointAt` method, added later, does give a full Unicode character, so we could use that to get characters from a string. But the argument passed to `codePointAt` is still an index into the sequence of code units. To run over all characters in a string, we'd still need to deal with the question of whether a character takes up one or two code units. {{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 where 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 = "🌹🐉"; @@ -673,28 +490,24 @@ for (let char of roseDragon) { // → 🐉 ``` -If you have a character (which will be a string of one or two code -units), you can use `codePointAt(0)` to get its code. +If you have a character (which will be a string of one or two code units), you can use `codePointAt(0)` to get its code. ## Recognizing text -{{index "SCRIPTS data set", "countBy function", [array, counting]}} +{{index "SCRIPTS dataset", "countBy function", [array, counting]}} -We have a `characterScript` function and a way to correctly loop over -characters. The next step is to count the characters that belong -to each script. The following counting abstraction will be useful -there: +We have a `characterScript` function and a way to correctly loop over characters. The next step is to count the characters that belong to each script. The following counting abstraction will be useful there: ```{includeCode: strip_log} function countBy(items, groupName) { let counts = []; for (let item of items) { let name = groupName(item); - let known = counts.findIndex(c => c.name == name); - if (known == -1) { + let known = counts.find(c => c.name == name); + if (!known) { counts.push({name, count: 1}); } else { - counts[known].count++; + known.count++; } } return counts; @@ -704,23 +517,15 @@ console.log(countBy([1, 2, 3, 4, 5], n => n > 2)); // → [{name: false, count: 2}, {name: true, count: 3}] ``` -The `countBy` function expects a collection (anything that we can loop -over with `for`/`of`) and a function that computes a group name for a -given element. It returns an array of -objects, each of which names a group and tells you the number of -elements that were found in that group. +The `countBy` function expects a collection (anything that we can loop over with `for`/`of`) and a function that computes a group name for a given element. It returns an array of objects, each of which names a group and tells you the number of elements that were found in that group. -{{index "findIndex method", "indexOf method"}} +{{index "find method"}} -It uses another array method—`findIndex`. This method is somewhat like -`indexOf`, but instead of looking for a specific value, it finds the -first value for which the given function returns true. Like `indexOf`, -it returns -1 when no such element is found. +It uses another array method, `find`, which goes over the elements in the array and returns the first one for which a function returns true. It returns `undefined` when it finds no such element. {{index "textScripts function", "Chinese characters"}} -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) { @@ -743,36 +548,17 @@ 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 no such characters are found, the function returns a -specific string. Otherwise, it transforms the counting entries into -readable strings with `map` and then combines them with `join`. +To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If we find no such characters, the function returns a specific string. Otherwise, it transforms the counting entries into readable strings with `map` and then combines them with `join`. ## Summary -Being able to pass function values to other functions is a deeply -useful aspect of JavaScript. It allows us to write functions that -model computations with "gaps" in them. The code that calls these -functions can fill in the gaps by providing function values. - -Arrays provide a number of useful higher-order methods. You can use -`forEach` to loop over the elements in an array. The `filter` method -returns a new array containing only the elements that pass the -((predicate function)). Transforming an array by putting each element -through a function is done with `map`. You can use `reduce` to combine -all the elements in an array into a single value. The `some` method -tests whether any element matches a given predicate function. And -`findIndex` finds the position of the first element that matches a -predicate. +Being able to pass function values to other functions is a deeply useful aspect of JavaScript. It allows us to write functions that model computations with "gaps" in them. The code that calls these functions can fill in the gaps by providing function values. + +Arrays provide a number of useful higher-order methods. You can use `forEach` to loop over the elements in an array. The `filter` method returns a new array containing only the elements that pass the ((predicate function)). You can transform an array by putting each element through a function using `map`. You can use `reduce` to combine all the elements in an array into a single value. The `some` method tests whether any element matches a given predicate function, while `find` finds the first element that matches a predicate. ## Exercises @@ -780,9 +566,7 @@ predicate. {{index "flattening (exercise)", "reduce method", "concat method", [array, flattening]}} -Use the `reduce` method in combination with the `concat` method to -"flatten" an array of arrays into a single array that has all the -elements of the original arrays. +Use the `reduce` method in combination with the `concat` method to "flatten" an array of arrays into a single array that has all the elements of the original arrays. {{if interactive @@ -797,16 +581,9 @@ if}} {{index "your own loop (example)", "for loop"}} -Write a higher-order function `loop` that provides something like a -`for` loop statement. It takes a value, a test function, an update -function, and a body function. Each iteration, it first runs the test -function on the current loop value and stops if that returns false. -Then it calls the body function, giving it the current value. -Finally, it calls the update function to create a new value and -starts 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. +When defining the function, you can use a regular loop to do the actual looping. {{if interactive @@ -825,14 +602,9 @@ if}} {{index "predicate function", "everything (exercise)", "every method", "some method", [array, methods], "&& operator", "|| operator"}} -Analogous to the `some` method, arrays also have an `every` method. -This one 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. +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. {{if interactive @@ -855,36 +627,21 @@ 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. +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. hint}} ### Dominant writing direction -{{index "SCRIPTS data set", "direction (writing)", "groupBy function", "dominant direction (exercise)"}} +{{index "SCRIPTS dataset", "direction (writing)", "groupBy function", "dominant direction (exercise)"}} -Write a function that computes the dominant writing direction in a -string of text. Remember that each script object has a `direction` -property that can be `"ltr"` (left to right), `"rtl"` (right to left), -or `"ttb"` (top to bottom). +Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a `direction` property that can be `"ltr"` (left to right), `"rtl"` (right to left), or `"ttb"` (top to bottom). {{index "characterScript function", "countBy function"}} -The dominant direction is the direction of a majority of the -characters that have a script associated with them. The -`characterScript` and `countBy` functions defined earlier in the -chapter are probably useful here. +The dominant direction is the direction of a majority of the characters that have a script associated with them. The `characterScript` and `countBy` functions defined earlier in the chapter are probably useful here. {{if interactive @@ -904,16 +661,10 @@ if}} {{index "dominant direction (exercise)", "textScripts function", "filter method", "characterScript function"}} -Your solution might look a lot like the first half of the -`textScripts` example. You again have to count characters by a -criterion based on `characterScript` and then filter out the part of -the result that refers to uninteresting (script-less) characters. +Your solution might look a lot like the first half of the `textScripts` example. You again have to count characters by a criterion based on `characterScript` and then filter out the part of the result that refers to uninteresting (script-less) characters. {{index "reduce method"}} -Finding the direction with the highest character count can be done -with `reduce`. If it's not clear how, refer to the example -earlier in the chapter, where `reduce` was used to find the script -with the most characters. +Finding the direction with the highest character count can be done with `reduce`. If it's not clear how, refer to the example earlier in the chapter, where `reduce` was used to find the script with the most characters. hint}} diff --git a/06_object.md b/06_object.md index eea6f8c4a..86b92bd50 100644 --- a/06_object.md +++ b/06_object.md @@ -4,71 +4,38 @@ {{quote {author: "Barbara Liskov", title: "Programming with Abstract Data Types", chapter: true} -An abstract data type is realized by writing a special kind of program -[…] which defines the type in terms of the operations which can be -performed on it. +An abstract data type is realized by writing a special kind of program […] which defines the type in terms of the operations which can be performed on it. quote}} {{index "Liskov, Barbara", "abstract data type"}} -{{figure {url: "img/chapter_picture_6.jpg", alt: "Picture of a rabbit with its proto-rabbit", chapter: framed}}} +{{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. In programming -culture, we have a thing called _((object-oriented programming))_, a -set of techniques that use objects (and related concepts) as the -central principle of program organization. +[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. -Though no one really agrees on its precise definition, object-oriented -programming has shaped the design of many programming languages, -including JavaScript. This chapter will describe the way these ideas -can be applied in JavaScript. +## Abstract Data Types -## Encapsulation +{{index "abstract data type", type, "mixer example"}} -{{index encapsulation, isolation, modularity}} - -The core idea in object-oriented programming is to divide programs -into smaller pieces and make each piece responsible for managing its -own state. +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. -This way, some knowledge about the way a piece of the program works -can be kept _local_ to that piece. Someone working on the rest of the -program does not have to remember or even be aware of that knowledge. -Whenever these local details change, only the code directly around it -needs to be updated. - -{{id interface}} -{{index [interface, object]}} +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. -Different pieces of such a program interact with each other through -_interfaces_, limited sets of functions or bindings that provide -useful functionality at a more abstract level, hiding their precise -implementation. +{{index "class"}} -{{index "public properties", "private properties", "access control", [method, interface]}} +Similarly, an _abstract data type_, or _object class_, is a subprogram that may contain arbitrarily complicated code but exposes a limited set of methods and properties that people working with it are supposed to use. This allows large programs to be built up out of a number of appliance types, limiting the degree to which these different parts are entangled by requiring them to only interact with each other in specific ways. -Such program pieces are modeled using ((object))s. Their interface -consists of a specific set of methods and properties. Properties -that are part of the interface are called _public_. The others, which -outside code should not be touching, are called _private_. +{{index encapsulation, isolation, modularity}} -{{index "underscore character"}} +If a problem is found in one such object class, it can often be repaired or even completely rewritten without impacting the rest of the program. Even better, it may be possible to use object classes in multiple different programs, avoiding the need to recreate their functionality from scratch. You can think of JavaScript's built-in data structures, such as arrays and strings, as such reusable abstract data types. -Many languages provide a way to distinguish public and private -properties and prevent outside code from accessing the private -ones altogether. JavaScript, once again taking the minimalist -approach, does not—not yet at least. There is work underway to add -this to the language. +{{id interface}} +{{index [interface, object]}} -Even though the language doesn't have this distinction built in, -JavaScript programmers _are_ successfully using this idea. Typically, -the available interface is described in documentation or comments. It -is also common to put an underscore (`_`) character at the start of -property names to indicate that those properties are private. +Each abstract data type has an _interface_, the collection of operations that external code can perform on it. Any details beyond that interface are _encapsulated_, treated as internal to the type and of no concern to the rest of the program. -Separating interface from implementation is a great idea. It is -usually called _((encapsulation))_. +Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate since useful pieces of functionality often involve a group of different object classes working closely together. {{id obj_methods}} @@ -76,86 +43,70 @@ usually called _((encapsulation))_. {{index "rabbit example", method, [property, access]}} -Methods are nothing more than properties that hold function values. -This is a simple method: - -``` -let rabbit = {}; -rabbit.speak = function(line) { - console.log(`The rabbit says '${line}'`); -}; - -rabbit.speak("I'm alive."); -// → The rabbit says 'I'm alive.' -``` - -{{index "this binding", "method call"}} +In JavaScript, methods are nothing more than properties that hold function values. This is a simple method: -Usually a method needs to do something with the object it was called -on. When a function is called as a method—looked up as a property and -immediately called, as in `object.method()`—the binding called `this` -in its body automatically points at the object that it was called on. - -```{includeCode: "top_lines:6", test: join} +```{includeCode: "top_lines:6"} function speak(line) { console.log(`The ${this.type} rabbit says '${line}'`); } let whiteRabbit = {type: "white", speak}; let hungryRabbit = {type: "hungry", speak}; -whiteRabbit.speak("Oh my ears and whiskers, " + - "how late it's getting!"); -// → The white rabbit says 'Oh my ears and whiskers, how -// late it's getting!' -hungryRabbit.speak("I could use a carrot right now."); -// → The hungry rabbit says 'I could use a carrot right now.' +whiteRabbit.speak("Oh my fur and whiskers"); +// → The white rabbit says 'Oh my fur and whiskers' +hungryRabbit.speak("Got any carrots?"); +// → The hungry rabbit says 'Got any carrots?' ``` +{{index "this binding", "method call"}} + +Typically a method needs to do something with the object on which it was called. When a function is called as a method—looked up as a property and immediately called, as in `object.method()`—the binding called `this` in its body automatically points at the object on which it was called. + {{id call_method}} {{index "call method"}} -You can think of `this` as an extra ((parameter)) that is passed in a -different way. If you want to pass it explicitly, you can use a -function's `call` method, which takes the `this` value as its first -argument and treats further arguments as normal parameters. +You can think of `this` as an extra ((parameter)) that is passed to the function in a different way than regular parameters. If you want to provide it explicitly, you can use a function's `call` method, which takes the `this` value as its first argument and treats further arguments as normal parameters. ``` -speak.call(hungryRabbit, "Burp!"); -// → The hungry rabbit says 'Burp!' +speak.call(whiteRabbit, "Hurry"); +// → The white rabbit says 'Hurry' ``` -Since each function has its own `this` binding, whose value depends on -the way it is called, you cannot refer to the `this` of the wrapping -scope in a regular function defined with the `function` keyword. +Since each function has its own `this` binding whose value depends on the way it is called, you cannot refer to the `this` of the wrapping scope in a regular function defined with the `function` keyword. {{index "this binding", "arrow function"}} -Arrow functions are different—they do not bind their own `this` but -can see the `this` binding of the scope around them. Thus, you can do -something like the following code, which references `this` from inside -a local function: +Arrow functions are different—they do not bind their own `this` but can see the `this` binding of the scope around them. Thus, you can do something like the following code, which references `this` from inside a local function: ``` -function normalize() { - console.log(this.coords.map(n => n / this.length)); -} -normalize.call({coords: [0, 2, 3], length: 5}); -// → [0, 0.4, 0.6] +let finder = { + find(array) { + return array.some(v => v == this.value); + }, + value: 5 +}; +console.log(finder.find([4, 5])); +// → true ``` -{{index "map method"}} +A property like `find(array)` in an object expression is a shorthand way of defining a method. It creates a property called `find` and gives it a function as its value. -If I had written the argument to `map` using the `function` keyword, -the code wouldn't work. +If I had written the argument to `some` using the `function` keyword, this code wouldn't work. {{id prototypes}} ## Prototypes -{{index "toString method"}} +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 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"}} -Watch closely. +In JavaScript, _((prototype))s_ are the way to do that. Objects can be linked to other objects, to magically get all the properties that other object has. Plain old objects created with `{}` notation are linked to an object called `Object.prototype`. + +{{index "toString method"}} ``` let empty = {}; @@ -165,29 +116,12 @@ console.log(empty.toString()); // → [object Object] ``` -{{index magic}} - -I pulled a property out of an empty object. Magic! +It looks like we just pulled a property out of an empty object. But in fact, `toString` is a method stored in `Object.prototype`, meaning it is available in most objects. -{{index [property, inheritance], [object, property]}} - -Well, not really. I have simply been withholding information about the -way JavaScript objects work. In addition to their set of properties, -most objects also have a _((prototype))_. A prototype is another -object that is used as a fallback source of properties. When an object -gets a request for a property that it does not have, its prototype -will be searched for the property, then the prototype's prototype, and -so on. - -{{index "Object prototype"}} - -So who is the ((prototype)) of that empty object? It is the great -ancestral prototype, the entity behind almost all objects, -`Object.prototype`. +When an object gets a request for a property that it doesn't have, its prototype will be searched for the property. If that doesn't have it, the _prototype's_ prototype is searched, and so on until an object without prototype is reached (`Object.prototype` is such an object). ``` -console.log(Object.getPrototypeOf({}) == - Object.prototype); +console.log(Object.getPrototypeOf({}) == Object.prototype); // → true console.log(Object.getPrototypeOf(Object.prototype)); // → null @@ -195,92 +129,59 @@ console.log(Object.getPrototypeOf(Object.prototype)); {{index "getPrototypeOf function"}} -As you guess, `Object.getPrototypeOf` returns the prototype of an -object. - -{{index "toString method"}} - -The prototype relations of JavaScript objects form a ((tree))-shaped -structure, and at the root of this structure sits `Object.prototype`. -It provides a few methods that show up in all objects, such as -`toString`, which converts an object to a string representation. +As you'd guess, `Object.getPrototypeOf` returns the prototype of an object. {{index inheritance, "Function prototype", "Array prototype", "Object prototype"}} -Many objects don't directly have `Object.prototype` as their -((prototype)) but instead have another object that provides a different set of -default properties. Functions derive from `Function.prototype`, and -arrays derive from `Array.prototype`. +Many objects don't directly have `Object.prototype` as their ((prototype)) but instead have another object that provides a different set of default properties. Functions derive from `Function.prototype` and arrays derive from `Array.prototype`. ``` console.log(Object.getPrototypeOf(Math.max) == Function.prototype); // → true -console.log(Object.getPrototypeOf([]) == - Array.prototype); +console.log(Object.getPrototypeOf([]) == Array.prototype); // → true ``` {{index "Object prototype"}} -Such a prototype object will itself have a prototype, often -`Object.prototype`, so that it still indirectly provides methods like -`toString`. +Such a prototype object will itself have a prototype, often `Object.prototype`, so that it still indirectly provides methods like `toString`. {{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 = { speak(line) { console.log(`The ${this.type} rabbit says '${line}'`); } }; -let killerRabbit = Object.create(protoRabbit); -killerRabbit.type = "killer"; -killerRabbit.speak("SKREEEE!"); -// → The killer rabbit says 'SKREEEE!' +let blackRabbit = Object.create(protoRabbit); +blackRabbit.type = "black"; +blackRabbit.speak("I am fear and darkness"); +// → The black rabbit says 'I am fear and darkness' ``` {{index "shared property"}} -A property like `speak(line)` in an object expression is a shorthand way -of defining a method. It creates a property called `speak` and gives -it a function as its value. - -The "proto" rabbit acts as a container for the properties that are -shared by all rabbits. An individual rabbit object, like the killer -rabbit, contains properties that apply only to itself—in this case its -type—and derives shared properties from its prototype. +The "proto" rabbit acts as a container for the properties shared by all rabbits. An individual rabbit object, like the black rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype. {{id classes}} ## Classes -{{index "object-oriented programming"}} +{{index "object-oriented programming", "abstract data type"}} -JavaScript's ((prototype)) system can be interpreted as a somewhat -informal take on an object-oriented concept called _((class))es_. A -class defines the shape of a type of object—what methods and -properties it has. Such an object is called an _((instance))_ of the -class. +JavaScript's ((prototype)) system can be interpreted as a somewhat free-form take on abstract data types or ((class))es. A _class_ defines the shape of a type of object—what methods and properties it has. Such an object is called an _((instance))_ of the class. {{index [property, inheritance]}} -Prototypes are useful for defining properties for which all instances -of a class share the same value, such as ((method))s. Properties that -differ per instance, such as our rabbits' `type` property, need to -be stored directly in the objects themselves. +Prototypes are useful for defining properties for which all instances of a class share the same value. Properties that differ per instance, such as our rabbits' `type` property, need to be stored directly in the objects themselves. {{id constructors}} -So to create an instance of a given class, you have to make -an object that derives from the proper prototype, but you _also_ have -to make sure it, itself, has the properties that instances of this -class are supposed to have. This is what a _((constructor))_ function -does. +To create an instance of a given class, you have to make an object that derives from the proper prototype, but you _also_ have to make sure it itself has the properties that instances of this class are supposed to have. This is what a _((constructor))_ function does. ``` function makeRabbit(type) { @@ -290,125 +191,133 @@ function makeRabbit(type) { } ``` -{{index "new operator", "this binding", "return keyword", [object, creation]}} +JavaScript's ((class)) notation makes it easier to define this type of function, along with a ((prototype)) object. -JavaScript provides a way to make defining this type of function -easier. If you put the keyword `new` in front of a function call, the -function is treated as a constructor. This means that an object with -the right prototype is automatically created, bound to `this` in the -function, and returned at the end of the function. +{{index "rabbit example", constructor}} -{{index "prototype property"}} +```{includeCode: true} +class Rabbit { + constructor(type) { + this.type = type; + } + speak(line) { + console.log(`The ${this.type} rabbit says '${line}'`); + } +} +``` + +{{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 that holds the `speak` method. + +{{index "new operator", "this binding", [object, creation]}} -The prototype object used when constructing objects is found by taking -the `prototype` property of the constructor function. +This function cannot be called like a normal function. Constructors, in JavaScript, are called by putting the keyword `new` in front of them. Doing so creates a fresh instance object whose prototype is the object from the function's `prototype` property, then runs the function with `this` bound to the new object, and finally returns the object. -{{index "rabbit example"}} +```{includeCode: true} +let killerRabbit = new Rabbit("killer"); +``` + +In fact, `class` was only introduced in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015, the way to define a class was to write a regular function and then manipulate its `prototype` property. ``` -function Rabbit(type) { +function ArchaicRabbit(type) { this.type = type; } -Rabbit.prototype.speak = function(line) { +ArchaicRabbit.prototype.speak = function(line) { console.log(`The ${this.type} rabbit says '${line}'`); }; - -let weirdRabbit = new Rabbit("weird"); +let oldSchoolRabbit = new ArchaicRabbit("old school"); ``` -{{index constructor}} - -Constructors (all functions, in fact) automatically get a property -named `prototype`, which by default holds a plain, empty object that -derives from `Object.prototype`. You can overwrite it with a new -object if you want. Or you can add properties to the existing object, -as the example does. +For this reason, all non-arrow functions start with a `prototype` property holding an empty object. {{index capitalization}} -By convention, the names of constructors are capitalized so that they -can easily be distinguished from other functions. +By convention, the names of constructors are capitalized so that they can easily be distinguished from other functions. {{index "prototype property", "getPrototypeOf function"}} -It is important to understand the distinction between the way a -prototype is associated with a constructor (through its `prototype` -property) and the way objects _have_ a prototype (which can be found -with `Object.getPrototypeOf`). The actual prototype of a constructor -is `Function.prototype` since constructors are functions. Its -`prototype` _property_ holds the prototype used for instances created -through it. +It is important to understand the distinction between the way a prototype is associated with a constructor (through its `prototype` property) and the way objects _have_ a prototype (which can be found with `Object.getPrototypeOf`). The actual prototype of a constructor is `Function.prototype` since constructors are functions. The constructor function's `prototype` _property_ holds the prototype used for instances created through it. ``` console.log(Object.getPrototypeOf(Rabbit) == Function.prototype); // → true -console.log(Object.getPrototypeOf(weirdRabbit) == +console.log(Object.getPrototypeOf(killerRabbit) == Rabbit.prototype); // → true ``` -## Class notation +{{index constructor}} -So JavaScript ((class))es are ((constructor)) functions with a -((prototype)) property. That is how they work, and until 2015, that -was how you had to write them. These days, we have a less awkward -notation. +Constructors will typically add some per-instance properties to `this`. It is also possible to declare properties directly in the ((class declaration)). Unlike methods, such properties are added to ((instance)) objects and not the prototype. -```{includeCode: true} -class Rabbit { - constructor(type) { - this.type = type; - } - speak(line) { - console.log(`The ${this.type} rabbit says '${line}'`); +``` +class Particle { + speed = 0; + constructor(position) { + this.position = position; } } +``` + +Like `function`, `class` can be used both in statements and in expressions. When used as an expression, it doesn't define a binding but just produces the constructor as a value. You are allowed to omit the class name in a class expression. -let killerRabbit = new Rabbit("killer"); -let blackRabbit = new Rabbit("black"); ``` +let object = new class { getWord() { return "hello"; } }; +console.log(object.getWord()); +// → hello +``` + -{{index "rabbit example", [braces, class]}} +## Private Properties -The `class` keyword starts a ((class declaration)), which allows us to -define a constructor and a set of methods all in a single place. Any -number of methods may be written inside the declaration's braces. -The one named `constructor` is treated specially. It -provides the actual constructor function, which will be bound to the -name `Rabbit`. The others are packaged into that constructor's -prototype. Thus, the earlier class declaration is equivalent to the -constructor definition from the previous section. It just looks nicer. +{{index [property, private], [property, public], "class declaration"}} -{{index ["class declaration", properties]}} +It is common for classes to define some properties and ((method))s for internal use that are not part of their ((interface)). These are called _private_ properties, as opposed to _public_ ones, which are part of the object's external interface. -Class declarations currently allow only _methods_—properties that hold -functions—to be added to the ((prototype)). This can be somewhat -inconvenient when you want to save a non-function value in there. -The next version of the language will probably improve this. For now, you -can create such properties by directly manipulating the -prototype after you've defined the class. +{{index [method, private]}} -Like `function`, `class` can be used both in statements and in -expressions. When used as an expression, it doesn't define a -binding but just produces the constructor as a value. You are allowed -to omit the class name in a class expression. +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. ``` -let object = new class { getWord() { return "hello"; } }; -console.log(object.getWord()); -// → hello +class SecretiveObject { + #getSecret() { + return "I ate all the plums"; + } + interrogate() { + let shallISayIt = this.#getSecret(); + return "never"; + } +} +``` + +When a class does not declare a constructor, it will automatically get an empty one. + +If you try to call `#getSecret` from outside the class, you get an error. Its existence is entirely hidden inside the class declaration. + +To use private instance properties, you must declare them. Regular properties can be created by just assigning to them, but private properties _must_ be declared in the class declaration to be available at all. + +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 { + #max; + constructor(max) { + this.#max = max; + } + getNumber() { + return Math.floor(Math.random() * this.#max); + } +} ``` ## Overriding derived properties {{index "shared property", overriding, [property, inheritance]}} -When you add a property to an object, whether it is present in the -prototype or not, the property is added to the object _itself_. -If there was already a property with -the same name in the prototype, this property will no longer affect -the object, as it is now hidden behind the object's own property. +When you add a property to an object, whether it is present in the prototype or not, the property is added to the object _itself_. If there was already a property with the same name in the prototype, this property will no longer affect the object, as it is now hidden behind the object's own property. ``` Rabbit.prototype.teeth = "small"; @@ -417,7 +326,7 @@ console.log(killerRabbit.teeth); killerRabbit.teeth = "long, sharp, and bloody"; console.log(killerRabbit.teeth); // → long, sharp, and bloody -console.log(blackRabbit.teeth); +console.log((new Rabbit("basic")).teeth); // → small console.log(Rabbit.prototype.teeth); // → small @@ -425,25 +334,17 @@ console.log(Rabbit.prototype.teeth); {{index [prototype, diagram]}} -The following diagram sketches the situation after this code has run. -The `Rabbit` and `Object` ((prototype))s lie behind `killerRabbit` as -a kind of backdrop, where properties that are not found in the object -itself can be looked up. +The following diagram sketches the situation after this code has run. The `Rabbit` and `Object` ((prototype))s lie behind `killerRabbit` as a kind of backdrop, where properties that are not found in the object itself can be looked up. -{{figure {url: "img/rabbits.svg", alt: "Rabbit object prototype schema",width: "8cm"}}} +{{figure {url: "img/rabbits.svg", alt: "A diagram showing the object structure of rabbits and their prototypes. There is a box for the 'killerRabbit' instance (holding instance properties like 'type'), with its two prototypes, 'Rabbit.prototype' (holding the 'speak' method) and 'Object.prototype' (holding methods like 'toString') stacked behind it.",width: "8cm"}}} {{index "shared property"}} -Overriding properties that exist in a prototype can be a useful thing -to do. As the rabbit teeth example shows, overriding can be used to express -exceptional properties in instances of a more generic class of -objects, while letting the nonexceptional objects take a -standard value from their prototype. +Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects while letting the nonexceptional objects take a standard value from their prototype. {{index "toString method", "Array prototype", "Function prototype"}} -Overriding is also used to give the standard function and array prototypes a -different `toString` method than the basic object prototype. +Overriding is also used to give the standard function and array prototypes a different `toString` method than the basic object prototype. ``` console.log(Array.prototype.toString == @@ -455,12 +356,7 @@ console.log([1, 2].toString()); {{index "toString method", "join method", "call method"}} -Calling `toString` on an array gives a result similar to calling -`.join(",")` on it—it puts commas between the values in the array. -Directly calling `Object.prototype.toString` with an array produces a -different string. That function doesn't know about arrays, so it -simply puts the word _object_ and the name of the type between square -brackets. +Calling `toString` on an array gives a result similar to calling `.join(",")` on it—it puts commas between the values in the array. Directly calling `Object.prototype.toString` with an array produces a different string. That function doesn't know about arrays, so it simply puts the word _object_ and the name of the type between square brackets. ``` console.log(Object.prototype.toString.call([1, 2])); @@ -471,16 +367,11 @@ console.log(Object.prototype.toString.call([1, 2])); {{index "map method"}} -We saw the word _map_ used in the [previous chapter](higher_order#map) -for an operation that transforms a data structure by applying a -function to its elements. Confusing as it is, in programming the same -word is also used for a related but rather different thing. +We saw the word _map_ used in the [previous chapter](higher_order#map) for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is used for a related but rather different thing. {{index "map (data structure)", "ages example", ["data structure", map]}} -A _map_ (noun) is a data structure that associates values (the keys) -with other values. For example, you might want to map names to ages. -It is possible to use objects for this. +A _map_ (noun) is a data structure that associates values (the keys) with other values. For example, you might want to map names to ages. It is possible to use objects for this. ``` let ages = { @@ -499,18 +390,11 @@ console.log("Is toString's age known?", "toString" in ages); {{index "Object.prototype", "toString method"}} -Here, the object's property names are the people's names, and the -property values are their ages. But we certainly didn't list anybody named -toString in our map. Yet, because plain objects derive from -`Object.prototype`, it looks like the property is there. +Here, the object's property names are the people's names and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet because plain objects derive from `Object.prototype`, it looks like the property is there. {{index "Object.create function", prototype}} -As such, using plain objects as maps is dangerous. There are several -possible ways to avoid this problem. First, it is possible to create -objects with _no_ prototype. If you pass `null` to `Object.create`, -the resulting object will not derive from `Object.prototype` and can -safely be used as a map. +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)); @@ -519,15 +403,11 @@ console.log("toString" in Object.create(null)); {{index [property, naming]}} -Object property names must be strings. If you need a map whose -keys can't easily be converted to strings—such as objects—you cannot -use an object as your map. +Object property names must be strings. If you need a map whose keys can't easily be converted to strings—such as objects—you cannot use an object as your map. {{index "Map class"}} -Fortunately, JavaScript comes with a class called `Map` that is -written for this exact purpose. It stores a mapping and allows any -type of keys. +Fortunately, JavaScript comes with a class called `Map` that is written for this exact purpose. It stores a mapping and allows any type of keys. ``` let ages = new Map(); @@ -545,24 +425,16 @@ console.log(ages.has("toString")); {{index [interface, object], "set method", "get method", "has method", encapsulation}} -The methods `set`, `get`, and `has` are part of the interface of the -`Map` object. Writing a data structure that can quickly update and -search a large set of values isn't easy, but we don't have to worry -about that. Someone else did it for us, and we can go through this -simple interface to use their work. +The methods `set`, `get`, and `has` are part of the interface of the `Map` object. Writing a data structure that can quickly update and search a large set of values isn't easy, but we don't have to worry about that. Someone else did it for us, and we can go through this simple interface to use their work. -{{index "hasOwnProperty method", "in operator"}} +{{index "hasOwn function", "in operator"}} -If you do have a plain object that you need to treat as a map for some -reason, it is useful to know that `Object.keys` returns only an -object's _own_ keys, not those in the prototype. As an alternative to -the `in` operator, you can use the `hasOwnProperty` method, which -ignores the object's prototype. +If you do have a plain object that you need to treat as a map for some reason, it is useful to know that `Object.keys` returns only an object's _own_ keys, not those in the prototype. As an alternative to the `in` operator, you can use the `Object.hasOwn` function, which ignores the object's prototype. ``` -console.log({x: 1}.hasOwnProperty("x")); +console.log(Object.hasOwn({x: 1}, "x")); // → true -console.log({x: 1}.hasOwnProperty("toString")); +console.log(Object.hasOwn({x: 1}, "toString")); // → false ``` @@ -570,130 +442,164 @@ console.log({x: 1}.hasOwnProperty("toString")); {{index "toString method", "String function", polymorphism, overriding, "object-oriented programming"}} -When you call the `String` function (which converts a value to a -string) on an object, it will call the `toString` method on that -object to try to create a meaningful string from it. I mentioned that -some of the standard prototypes define their own version of `toString` -so they can create a string that contains more useful information than -`"[object Object]"`. You can also do that yourself. +When you call the `String` function (which converts a value to a string) on an object, it will call the `toString` method on that object to try to create a meaningful string from it. I mentioned that some of the standard prototypes define their own version of `toString` so they can create a string that contains more useful information than `"[object Object]"`. You can also do that yourself. ```{includeCode: "top_lines: 3"} Rabbit.prototype.toString = function() { return `a ${this.type} rabbit`; }; -console.log(String(blackRabbit)); -// → a black rabbit +console.log(String(killerRabbit)); +// → a killer rabbit ``` {{index "object-oriented programming", [interface, object]}} -This is a simple instance of a powerful idea. When a piece of code is -written to work with objects that have a certain interface—in this -case, a `toString` method—any kind of object that happens to support -this interface can be plugged into the code, and it will just work. +This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a `toString` method—any kind of object that happens to support this interface can be plugged into the code and will be able to work with it. -This technique is called _polymorphism_. Polymorphic code can work -with values of different shapes, as long as they support the interface -it expects. +This technique is called _polymorphism_. Polymorphic code can work with values of different shapes, as long as they support the interface it expects. -{{index "for/of loop", "iterator interface"}} +{{index "forEach method"}} + +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({ + length: 2, + 0: "A", + 1: "B" +}, elt => console.log(elt)); +// → A +// → B +``` + +## Getters, setters, and statics + +{{index [interface, object], [property, definition], "Map class"}} + +Interfaces often contain plain properties, not just methods. For example, `Map` objects have a `size` property that tells you how many keys are stored in them. -I mentioned in [Chapter ?](data#for_of_loop) that a `for`/`of` loop -can loop over several kinds of data structures. This is another case -of polymorphism—such loops expect the data structure to expose a -specific interface, which arrays and strings do. And we can also add -this interface to our own objects! But before we can do that, we need -to know what symbols are. +It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_ and are defined by writing `get` in front of the method name in an object expression or class declaration. + +```{test: no} +let varyingSize = { + get size() { + return Math.floor(Math.random() * 100); + } +}; + +console.log(varyingSize.size); +// → 73 +console.log(varyingSize.size); +// → 49 +``` + +{{index "temperature example"}} + +Whenever someone reads from this object's `size` property, the associated method is called. You can do a similar thing when a property is written to, using a _((setter))_. + +```{startCode: true, includeCode: "top_lines: 16"} +class Temperature { + constructor(celsius) { + this.celsius = celsius; + } + get fahrenheit() { + return this.celsius * 1.8 + 32; + } + set fahrenheit(value) { + this.celsius = (value - 32) / 1.8; + } + + static fromFahrenheit(value) { + return new Temperature((value - 32) / 1.8); + } +} + +let temp = new Temperature(22); +console.log(temp.fahrenheit); +// → 71.6 +temp.fahrenheit = 86; +console.log(temp.celsius); +// → 30 +``` + +The `Temperature` class allows you to read and write the temperature in either degrees ((Celsius)) or degrees ((Fahrenheit)), but internally it stores only Celsius and automatically converts to and from Celsius in the `fahrenheit` getter and setter. + +{{index "static method", "static property"}} + +Sometimes you want to attach some properties directly to your constructor function rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. + +Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. For example, the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit. + +``` +let boil = Temperature.fromFahrenheit(212); +console.log(boil.celsius); +// → 100 +``` ## Symbols -It is possible for multiple interfaces to use the same property name -for different things. For example, I could define an interface in which -the `toString` method is supposed to convert the object into a piece -of yarn. It would not be possible for an object to conform to both -that interface and the standard use of `toString`. +{{index "for/of loop", "iterator interface"}} + +I mentioned in [Chapter ?](data#for_of_loop) that a `for`/`of` loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to our own objects! But before we can do that, we need to briefly take a look at the symbol type. + +It is possible for multiple interfaces to use the same property name for different things. For example, on array-like objects, `length` refers to the number of elements in the collection. But an object interface describing a hiking route could use `length` to provide the length of the route in meters. It would not be possible for an object to conform to both these interfaces. -That would be a bad idea, and this problem isn't that common. Most -JavaScript programmers simply don't think about it. But the language -designers, whose _job_ it is to think about this stuff, have provided -us with a solution anyway. +An object trying to be a route and array-like (maybe to enumerate its waypoints) is somewhat far-fetched, and this kind of problem isn't that common in practice. For things like the iteration protocol, though, the language designers needed a type of property that _really_ doesn't conflict with any others. So in 2015, _((symbol))s_ were added to the language. {{index "Symbol function", [property, naming]}} -When I claimed that property names are strings, that wasn't entirely -accurate. They usually are, but they can also be _((symbol))s_. -Symbols are values created with the `Symbol` function. Unlike strings, -newly created symbols are unique—you cannot create the same symbol -twice. +Most properties, including all those we have seen so far, are named with strings. But it is also possible to use symbols as property names. Symbols are values created with the `Symbol` function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice. ``` let sym = Symbol("name"); console.log(sym == Symbol("name")); // → false Rabbit.prototype[sym] = 55; -console.log(blackRabbit[sym]); +console.log(killerRabbit[sym]); // → 55 ``` -The string you pass to `Symbol` is included when you convert it to a -string and can make it easier to recognize a symbol when, for -example, showing it in the console. But it has no meaning beyond -that—multiple symbols may have the same name. +The string you pass to `Symbol` is included when you convert it to a string and can make it easier to recognize a symbol when, for example, showing it in the console. But it has no meaning beyond that—multiple symbols may have the same name. -Being both unique and usable as property names makes symbols suitable -for defining interfaces that can peacefully live alongside other -properties, no matter what their names are. +Being both unique and usable as property names makes symbols suitable for defining interfaces that can peacefully live alongside other properties, no matter what their names are. ```{includeCode: "top_lines: 1"} -const toStringSymbol = Symbol("toString"); -Array.prototype[toStringSymbol] = function() { - return `${this.length} cm of blue yarn`; -}; +const length = Symbol("length"); +Array.prototype[length] = 0; -console.log([1, 2].toString()); -// → 1,2 -console.log([1, 2][toStringSymbol]()); -// → 2 cm of blue yarn +console.log([1, 2].length); +// → 2 +console.log([1, 2][length]); +// → 0 ``` {{index [property, naming]}} -It is possible to include symbol properties in object expressions and -classes by using ((square bracket))s around the property name. -That causes the property name to be evaluated, much like the square -bracket property access notation, which allows us to refer to a -binding that holds the symbol. +It is possible to include symbol properties in object expressions and classes by using ((square bracket))s around the property name. That causes the expression between the brackets to be evaluated to produce the property name, analogous to the square bracket property access notation. ``` -let stringObject = { - [toStringSymbol]() { return "a jute rope"; } +let myTrip = { + length: 2, + 0: "Lankwitz", + 1: "Babelsberg", + [length]: 21500 }; -console.log(stringObject[toStringSymbol]()); -// → a jute rope +console.log(myTrip[length], myTrip.length); +// → 21500 2 ``` ## The iterator interface {{index "iterable interface", "Symbol.iterator symbol", "for/of loop"}} -The object given to a `for`/`of` loop is expected to be _iterable_. -This means it has a method named with the `Symbol.iterator` -symbol (a symbol value defined by the language, stored as a property -of the `Symbol` function). +The object given to a `for`/`of` loop is expected to be _iterable_. This means it has a method named with the `Symbol.iterator` symbol (a symbol value defined by the language, stored as a property of the `Symbol` function). {{index "iterator interface", "next method"}} -When called, that method should return an object that provides a -second interface, _iterator_. This is the actual thing that iterates. -It has a `next` method that returns the next result. That result -should be an object with a `value` property that provides the next value, -if there is one, and a `done` property, which should be true when there -are no more results and false otherwise. +When called, that method should return an object that provides a second interface, _iterator_. This is the actual thing that iterates. It has a `next` method that returns the next result. That result should be an object with a `value` property that provides the next value, if there is one, and a `done` property, which should be true when there are no more results and false otherwise. -Note that the `next`, `value`, and `done` property names are plain -strings, not symbols. Only `Symbol.iterator`, which is likely to be -added to a _lot_ of different objects, is an actual symbol. +Note that the `next`, `value`, and `done` property names are plain strings, not symbols. Only `Symbol.iterator`, which is likely to be added to a _lot_ of different objects, is an actual symbol. We can directly use this interface ourselves. @@ -707,286 +613,142 @@ console.log(okIterator.next()); // → {value: undefined, done: true} ``` -{{index "matrix example", "Matrix class", [array, "as matrix"]}} - -{{id matrix}} +{{index ["data structure", list], "linked list", collection}} -Let's implement an iterable data structure. We'll build a _matrix_ -class, acting as a two-dimensional array. +Let's implement an iterable data structure similar to the linked list from the exercise in [Chapter ?](data). We'll write the list as a class this time. ```{includeCode: true} -class Matrix { - constructor(width, height, element = (x, y) => undefined) { - this.width = width; - this.height = height; - this.content = []; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - this.content[y * width + x] = element(x, y); - } - } +class List { + constructor(value, rest) { + this.value = value; + this.rest = rest; } - get(x, y) { - return this.content[y * this.width + x]; + get length() { + return 1 + (this.rest ? this.rest.length : 0); } - set(x, y, value) { - this.content[y * this.width + x] = value; + + static fromArray(array) { + let result = null; + for (let i = array.length - 1; i >= 0; i--) { + result = new this(array[i], result); + } + return result; } } ``` -The class stores its content in a single array of _width_ × _height_ -elements. The elements are stored row by row, so, for example, the third -element in the fifth row is (using zero-based indexing) stored at -position 4 × _width_ + 2. +Note that `this`, in a static method, points at the constructor of the class, not an instance—there is no instance around when a static method is called. -The constructor function takes a width, a height, and an optional -`element` function that will be used to fill in the initial values. -There are `get` and `set` methods to retrieve and update elements in -the matrix. +Iterating over a list should return all the list's elements from start to end. We'll write a separate class for the iterator. -When looping over a matrix, you are usually interested in the position -of the elements as well as the elements themselves, so we'll have our -iterator produce objects with `x`, `y`, and `value` properties. - -{{index "MatrixIterator class"}} +{{index "ListIterator class"}} ```{includeCode: true} -class MatrixIterator { - constructor(matrix) { - this.x = 0; - this.y = 0; - this.matrix = matrix; +class ListIterator { + constructor(list) { + this.list = list; } next() { - if (this.y == this.matrix.height) return {done: true}; - - let value = {x: this.x, - y: this.y, - value: this.matrix.get(this.x, this.y)}; - this.x++; - if (this.x == this.matrix.width) { - this.x = 0; - this.y++; + if (this.list == null) { + return {done: true}; } + let value = this.list.value; + this.list = this.list.rest; return {value, done: false}; } } ``` -The class tracks the progress of iterating over a matrix in its `x` -and `y` properties. The `next` method starts by checking whether the -bottom of the matrix has been reached. If it hasn't, it _first_ -creates the object holding the current value and _then_ updates its -position, moving to the next row if necessary. +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 `Matrix` 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} -Matrix.prototype[Symbol.iterator] = function() { - return new MatrixIterator(this); +List.prototype[Symbol.iterator] = function() { + return new ListIterator(this); }; ``` {{index "for/of loop"}} -We can now loop over a matrix with `for`/`of`. +We can now loop over a list with `for`/`of`. ``` -let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`); -for (let {x, y, value} of matrix) { - console.log(x, y, value); +let list = List.fromArray([1, 2, 3]); +for (let element of list) { + console.log(element); } -// → 0 0 value 0,0 -// → 1 0 value 1,0 -// → 0 1 value 0,1 -// → 1 1 value 1,1 +// → 1 +// → 2 +// → 3 ``` -## Getters, setters, and statics +{{index spread}} -{{index [interface, object], [property, definition], "Map class"}} +The `...` syntax in array notation and function calls similarly works with any iterable object. For example, you can use `[...value]` to create an array containing the elements in an arbitrary iterable object. -Interfaces often consist mostly of methods, but it is also okay to -include properties that hold non-function values. For example, `Map` -objects have a `size` property that tells you how many keys are stored -in them. +``` +console.log([..."PCI"]); +// → ["P", "C", "I"] +``` -It is not even necessary for such an object to compute and store such -a property directly in the instance. Even properties that are accessed -directly may hide a method call. Such methods are called -_((getter))s_, and they are defined by writing `get` in front of the -method name in an object expression or class declaration. +## Inheritance -```{test: no} -let varyingSize = { - get size() { - return Math.floor(Math.random() * 100); - } -}; +{{index inheritance, "linked list", "object-oriented programming", "LengthList class"}} -console.log(varyingSize.size); -// → 73 -console.log(varyingSize.size); -// → 49 -``` +Imagine we need a list type much like the `List` class we saw before, but because we will be asking for its length all the time, we don't want it to have to scan through its `rest` every time. Instead, we want to store the length in every instance for efficient access. -{{index "temperature example"}} +{{index overriding, prototype}} -Whenever someone reads from this object's `size` property, the -associated method is called. You can do a similar thing when a -property is written to, using a _((setter))_. +JavaScript's prototype system makes it possible to create a _new_ class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `length` getter. -```{test: no, startCode: true} -class Temperature { - constructor(celsius) { - this.celsius = celsius; - } - get fahrenheit() { - return this.celsius * 1.8 + 32; - } - set fahrenheit(value) { - this.celsius = (value - 32) / 1.8; +In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class. + +```{includeCode: "top_lines: 12"} +class LengthList extends List { + #length; + + constructor(value, rest) { + super(value, rest); + this.#length = super.length; } - static fromFahrenheit(value) { - return new Temperature((value - 32) / 1.8); + get length() { + return this.#length; } } -let temp = new Temperature(22); -console.log(temp.fahrenheit); -// → 71.6 -temp.fahrenheit = 86; -console.log(temp.celsius); -// → 30 +console.log(LengthList.fromArray([1, 2, 3]).length); +// → 3 ``` -The `Temperature` class allows you to read and write the temperature -in either degrees ((Celsius)) or degrees ((Fahrenheit)), but -internally it stores only Celsius and automatically converts to -and from Celsius in the `fahrenheit` getter and setter. - -{{index "static method"}} +The use of the word `extends` indicates that this class shouldn't be directly based on the default `Object` prototype but on some other class. This is called the _((superclass))_. The derived class is the _((subclass))_. -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 that have `static` written before -their name are stored on the constructor. So the `Temperature` class -allows you to write `Temperature.fromFahrenheit(100)` to create a -temperature using degrees Fahrenheit. - -## Inheritance +To initialize a `LengthList` instance, the constructor calls the constructor of its superclass through the `super` keyword. This is necessary because if this new object is to behave (roughly) like a `List`, it is going to need the instance properties that lists have. -{{index inheritance, "matrix example", "object-oriented programming", "SymmetricMatrix class"}} +The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet since `#length` hasn't been filled in yet. We can use `super.something` to call methods and getters on the superclass's prototype, which is often useful. -Some matrices are known to be _symmetric_. If you mirror a symmetric -matrix around its top-left-to-bottom-right diagonal, it stays the -same. In other words, the value stored at _x_,_y_ is always the same -as that at _y_,_x_. - -Imagine we need a data structure like `Matrix` but one that enforces -the fact that the matrix is and remains symmetrical. We could write it -from scratch, but that would involve repeating some code very similar -to what we already wrote. - -{{index overriding, prototype}} - -JavaScript's prototype system makes it possible to create a _new_ -class, much like the old class, but with new definitions for some of -its properties. The prototype for the new class derives from the old -prototype but adds a new definition for, say, the `set` method. - -In object-oriented programming terms, this is called -_((inheritance))_. The new class inherits properties and behavior from -the old class. - -```{includeCode: "top_lines: 17"} -class SymmetricMatrix extends Matrix { - constructor(size, element = (x, y) => undefined) { - super(size, size, (x, y) => { - if (x < y) return element(y, x); - else return element(x, y); - }); - } - - set(x, y, value) { - super.set(x, y, value); - if (x != y) { - super.set(y, x, value); - } - } -} - -let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`); -console.log(matrix.get(2, 3)); -// → 3,2 -``` - -The use of the word `extends` indicates that this class shouldn't be -directly based on the default `Object` prototype but on some other class. This -is called the _((superclass))_. The derived class is the -_((subclass))_. - -To initialize a `SymmetricMatrix` instance, the constructor calls its -superclass's constructor through the `super` keyword. This is necessary -because if this new object is to behave (roughly) like a `Matrix`, it -is going to need the instance properties that matrices have. -To ensure the matrix is symmetrical, the constructor wraps the -`element` function to swap the coordinates for values below the -diagonal. - -The `set` method again uses `super` but this time not to call the -constructor but to call a specific method from the superclass's set of -methods. We are redefining `set` but do want to use the original -behavior. Because `this.set` refers to the _new_ `set` method, calling -that wouldn't work. Inside class methods, `super` provides a way to -call methods as they were defined in the superclass. - -Inheritance allows us to build slightly different data types from -existing data types with relatively little work. It is a fundamental -part of the object-oriented tradition, alongside encapsulation and -polymorphism. But while the latter two are now generally regarded as -wonderful ideas, inheritance is more controversial. +Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial. {{index complexity, reuse, "class hierarchy"}} -Whereas ((encapsulation)) and polymorphism can be used to _separate_ -pieces of code from each other, reducing the tangledness of the -overall program, ((inheritance)) fundamentally ties classes together, -creating _more_ tangle. When inheriting from a class, you usually have -to know more about how it works than when simply using it. Inheritance -can be a useful tool, and I use it now and then in my own programs, -but it shouldn't be the first tool you reach for, and you probably -shouldn't actively go looking for opportunities to construct class -hierarchies (family trees of classes). +Whereas ((encapsulation)) and polymorphism can be used to _separate_ pieces of code from one another, reducing the tangledness of the overall program, ((inheritance)) fundamentally ties classes together, creating _more_ tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool to make some types of programs more succinct, but it shouldn't be the first tool you reach for, and you probably shouldn't actively go looking for opportunities to construct class hierarchies (family trees of classes). ## The instanceof operator {{index type, "instanceof operator", constructor, object}} -It is occasionally useful to know whether an object was derived from a -specific class. For this, JavaScript provides a binary operator called -`instanceof`. +It is occasionally useful to know whether an object was derived from a specific class. For this, JavaScript provides a binary operator called `instanceof`. ``` console.log( - new SymmetricMatrix(2) instanceof SymmetricMatrix); + new LengthList(1, null) instanceof LengthList); // → true -console.log(new SymmetricMatrix(2) instanceof Matrix); +console.log(new LengthList(2, null) instanceof List); // → true -console.log(new Matrix(2, 2) instanceof SymmetricMatrix); +console.log(new List(3, null) instanceof LengthList); // → false console.log([1] instanceof Array); // → true @@ -994,46 +756,23 @@ console.log([1] instanceof Array); {{index inheritance}} -The operator will see through inherited types, so a `SymmetricMatrix` -is an instance of `Matrix`. The operator can also be applied to -standard constructors like `Array`. Almost every object is an instance -of `Object`. +The operator will see through inherited types, so a `LengthList` is an instance of `List`. The operator can also be applied to standard constructors like `Array`. Almost every object is an instance of `Object`. ## Summary -So objects do more than just hold their own properties. They have -prototypes, which are other objects. They'll act as if they have -properties they don't have as long as their prototype has that -property. Simple objects have `Object.prototype` as their prototype. +Objects do more than just hold their own properties. They have prototypes, which are other objects. They'll act as if they have properties they don't have as long as their prototype has that property. Simple objects have `Object.prototype` as their prototype. -Constructors, which are functions whose names usually start with a -capital letter, can be used with the `new` operator to create new -objects. The new object's prototype will be the object found in the -`prototype` property of the constructor. You can make good use of this -by putting the properties that all values of a given type share into -their prototype. There's a `class` notation that provides a clear way -to define a constructor and its prototype. +Constructors, which are functions whose names usually start with a capital letter, can be used with the `new` operator to create new objects. The new object's prototype will be the object found in the `prototype` property of the constructor. You can make good use of this by putting the properties that all values of a given type share into their prototype. There's a `class` notation that provides a clear way to define a constructor and its prototype. -You can define getters and setters to secretly call methods every time -an object's property is accessed. Static methods are methods stored in -a class's constructor, rather than its prototype. +You can define getters and setters to secretly call methods every time an object's property is accessed. Static methods are methods stored in a class's constructor rather than its prototype. -The `instanceof` operator can, given an object and a constructor, tell -you whether that object is an instance of that constructor. +The `instanceof` operator can, given an object and a constructor, tell you whether that object is an instance of that constructor. -One useful thing to do with objects is to specify an interface for -them and tell everybody that they are supposed to talk to your object -only through that interface. The rest of the details that make up your -object are now _encapsulated_, hidden behind the interface. +One useful thing to do with objects is to specify an interface for them and tell everybody that they are supposed to talk to your object only through that interface. The rest of the details that make up your object are now _encapsulated_, hidden behind the interface. You can use private properties to hide a part of your object from the outside world. -More than one type may implement the same interface. Code written to -use an interface automatically knows how to work with any number of -different objects that provide the interface. This is called -_polymorphism_. +More than one type may implement the same interface. Code written to use an interface automatically knows how to work with any number of different objects that provide the interface. This is called _polymorphism_. -When implementing multiple classes that differ in only some details, -it can be helpful to write the new classes as _subclasses_ of an -existing class, _inheriting_ part of its behavior. +When implementing multiple classes that differ in only some details, it can be helpful to write the new classes as _subclasses_ of an existing class, _inheriting_ part of its behavior. ## Exercises @@ -1043,20 +782,13 @@ existing class, _inheriting_ part of its behavior. {{index dimensions, "Vec class", coordinates, "vector (exercise)"}} -Write a ((class)) `Vec` that represents a vector in two-dimensional -space. It takes `x` and `y` parameters (numbers), which it should save -to properties of the same name. +Write a ((class)) `Vec` that represents a vector in two-dimensional space. It takes `x` and `y` parameters (numbers), that it saves to properties of the same name. {{index addition, subtraction}} -Give the `Vec` prototype two methods, `plus` and `minus`, that take -another vector as a parameter and return a new vector that has the sum -or difference of the two vectors' (`this` and the parameter) _x_ and -_y_ values. +Give the `Vec` prototype two methods, `plus` and `minus`, that take another vector as a parameter and return a new vector that has the sum or difference of the two vectors' (`this` and the parameter) _x_ and _y_ values. -Add a ((getter)) property `length` to the prototype that computes the -length of the vector—that is, the distance of the point (_x_, _y_) from -the origin (0, 0). +Add a ((getter)) property `length` to the prototype that computes the length of the vector—that is, the distance of the point (_x_, _y_) from the origin (0, 0). {{if interactive @@ -1076,19 +808,11 @@ if}} {{index "vector (exercise)"}} -Look back to the `Rabbit` class example if you're unsure how `class` -declarations look. +Look back to the `Rabbit` class example if you're unsure how `class` declarations look. {{index Pythagoras, "defineProperty function", "square root", "Math.sqrt function"}} -Adding a getter property to the constructor can be done by putting the -word `get` before the method name. To compute the distance from (0, 0) -to (x, y), you can use the Pythagorean theorem, which says that the -square of the distance we are looking for is equal to the square of -the x-coordinate plus the square of the y-coordinate. Thus, [√(x^2^ + -y^2^)]{if html}[[$\sqrt{x^2 + y^2}$]{latex}]{if tex} is the number you -want, and `Math.sqrt` is the way you compute a square root in -JavaScript. +Adding a getter property to the constructor can be done by putting the word `get` before the method name. To compute the distance from (0, 0) to (x, y), you can use the Pythagorean theorem, which says that the square of the distance we are looking for is equal to the square of the x-coordinate plus the square of the y-coordinate. Thus, [√(x^2^ + y^2^)]{if html}[[$\sqrt{x^2 + y^2}$]{latex}]{if tex} is the number you want. `Math.sqrt` is the way you compute a square root in JavaScript and `x ** 2` can be used to square a number. hint}} @@ -1098,31 +822,19 @@ hint}} {{id groups}} -The standard JavaScript environment provides another data structure -called `Set`. Like an instance of `Map`, a set holds a collection of -values. Unlike `Map`, it does not associate other values with those—it -just tracks which values are part of the set. A value can be part -of a set only once—adding it again doesn't have any effect. +The standard JavaScript environment provides another data structure called `Set`. Like an instance of `Map`, a set holds a collection of values. Unlike `Map`, it does not associate other values with those—it just tracks which values are part of the set. A value can be part of a set only once—adding it again doesn't have any effect. {{index "add method", "delete method", "has method"}} -Write a class called `Group` (since `Set` is already taken). Like -`Set`, it has `add`, `delete`, and `has` methods. Its constructor -creates an empty group, `add` adds a value to the group (but only if -it isn't already a member), `delete` removes its argument from the -group (if it was a member), and `has` returns a Boolean value -indicating whether its argument is a member of the group. +Write a class called `Group` (since `Set` is already taken). Like `Set`, it has `add`, `delete`, and `has` methods. Its constructor creates an empty group, `add` adds a value to the group (but only if it isn't already a member), `delete` removes its argument from the group (if it was a member), and `has` returns a Boolean value indicating whether its argument is a member of the group. {{index "=== operator", "indexOf method"}} -Use the `===` operator, or something equivalent such as `indexOf`, to -determine whether two values are the same. +Use the `===` operator, or something equivalent such as `indexOf`, to determine whether two values are the same. {{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 @@ -1148,28 +860,19 @@ if}} {{index "groups (exercise)", "Group class", "indexOf method", "includes method"}} -The easiest way to do this is to store an array of group members -in an instance property. The `includes` or `indexOf` methods can be -used to check whether a given value is in the array. +The easiest way to do this is to store an array of group members in an instance property. The `includes` or `indexOf` methods can be used to check whether a given value is in the array. {{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"}} -Deleting an element from an array, in `delete`, is less -straightforward, but you can use `filter` to create a new array -without the value. Don't forget to overwrite the property holding the -members with the newly filtered version of the array. +Deleting an element from an array, in `delete`, is less straightforward, but you can use `filter` to create a new array without the value. Don't forget to overwrite the property holding the members with the newly filtered version of the array. {{index "for/of loop", "iterable interface"}} -The `from` method can use a `for`/`of` loop to get the values out of -the iterable object and call `add` to put them into a newly created -group. +The `from` method can use a `for`/`of` loop to get the values out of the iterable object and call `add` to put them into a newly created group. hint}} @@ -1179,16 +882,11 @@ hint}} {{id group_iterator}} -Make the `Group` class from the previous exercise iterable. Refer -to the section about the iterator interface earlier in the chapter if -you aren't clear on the exact form of the interface anymore. +Make the `Group` class from the previous exercise iterable. Refer to the section about the iterator interface earlier in the chapter if you aren't clear on the exact form of the interface anymore. -If you used an array to represent the group's members, don't just -return the iterator created by calling the `Symbol.iterator` method on -the array. That would work, but it defeats the purpose of this exercise. +If you used an array to represent the group's members, don't just return the iterator created by calling the `Symbol.iterator` method on the array. That would work, but it defeats the purpose of this exercise. -It is okay if your iterator behaves strangely when the group is -modified during iteration. +It is okay if your iterator behaves strangely when the group is modified during iteration. {{if interactive @@ -1209,47 +907,8 @@ if}} {{index "groups (exercise)", "Group class", "next method"}} -It is probably worthwhile to define a new class `GroupIterator`. -Iterator instances should have a property that tracks the current -position in the group. Every time `next` is called, it checks whether -it is done and, if not, moves past the current value and returns it. - -The `Group` class itself gets a method named by `Symbol.iterator` -that, when called, returns a new instance of the iterator class for -that group. - -hint}} - -### Borrowing a method - -Earlier in the chapter I mentioned that an object's `hasOwnProperty` -can be used as a more robust alternative to the `in` operator when you -want to ignore the prototype's properties. But what if your map needs -to include the word `"hasOwnProperty"`? You won't be able to call that -method anymore because the object's own property hides the method -value. - -Can you think of a way to call `hasOwnProperty` on an object that has -its own property by that name? - -{{if interactive - -```{test: no} -let map = {one: true, two: true, hasOwnProperty: true}; - -// Fix this call -console.log(map.hasOwnProperty("one")); -// → true -``` - -if}} - -{{hint - -Remember that methods that exist on plain objects come from -`Object.prototype`. +It is probably worthwhile to define a new class `GroupIterator`. Iterator instances should have a property that tracks the current position in the group. Every time `next` is called, it checks whether it is done and, if not, moves past the current value and returns it. -Also remember that you can call a function with a specific `this` -binding by using its `call` method. +The `Group` class itself gets a method named by `Symbol.iterator` that, when called, returns a new instance of the iterator class for that group. hint}} diff --git a/07_robot.md b/07_robot.md index 61f29d295..3c942cfb3 100644 --- a/07_robot.md +++ b/07_robot.md @@ -4,33 +4,25 @@ {{quote {author: "Edsger Dijkstra", title: "The Threats to Computing Science", chapter: true} -[...] the question of whether Machines Can Think [...] is about as -relevant as the question of whether Submarines Can Swim. +The question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim. quote}} {{index "artificial intelligence", "Dijkstra, Edsger"}} -{{figure {url: "img/chapter_picture_7.jpg", alt: "Picture of a package-delivery robot", chapter: framed}}} +{{figure {url: "img/chapter_picture_7.jpg", alt: "Illustration of a robot holding a stack of packages", chapter: framed}}} {{index "project chapter", "reading code", "writing code"}} -In "project" chapters, I'll stop pummeling you with new theory for a -brief moment, and instead we'll work through a program together. Theory -is necessary to learn to program, but reading and understanding actual -programs is just as important. +In "project" chapters, I'll stop pummeling you with new theory for a brief moment, and instead we'll work through a program together. Theory is necessary to learn to program, but reading and understanding actual programs is just as important. -Our project in this chapter is to build an ((automaton)), a little -program that performs a task in a ((virtual world)). Our automaton -will be a mail-delivery ((robot)) picking up and dropping off parcels. +Our project in this chapter is to build an ((automaton)), a little program that performs a task in a ((virtual world)). Our automaton will be a mail-delivery ((robot)) picking up and dropping off parcels. ## Meadowfield {{index "roads array"}} -The village of ((Meadowfield)) isn't very big. It consists of 11 -places with 14 roads between them. It can be described with this -array of roads: +The village of ((Meadowfield)) isn't very big. It consists of 11 places with 14 roads between them. It can be described with this array of roads: ```{includeCode: true} const roads = [ @@ -44,27 +36,22 @@ const roads = [ ]; ``` -{{figure {url: "img/village2x.png", alt: "The village of Meadowfield"}}} +{{figure {url: "img/village2x.png", alt: "Pixel art illustration of a small village with 11 locations, labeled with letters, and roads going being them"}}} -The network of roads in the village forms a _((graph))_. A graph is a -collection of points (places in the village) with lines between them -(roads). This graph will be the world that our robot moves through. +The network of roads in the village forms a _((graph))_. A graph is a collection of points (places in the village) with lines between them (roads). This graph will be the world that our robot moves through. {{index "roadGraph object"}} -The array of strings isn't very easy to work with. What we're -interested in is the destinations that we can reach from a given -place. Let's convert the list of roads to a data structure that, for -each place, tells us what can be reached from there. +The array of strings isn't very easy to work with. What we're interested in is the destinations that we can reach from a given place. Let's convert the list of roads to a data structure that, for each place, tells us what can be reached from there. ```{includeCode: true} function buildGraph(edges) { let graph = Object.create(null); function addEdge(from, to) { - if (graph[from] == null) { - graph[from] = [to]; - } else { + if (from in graph) { graph[from].push(to); + } else { + graph[from] = [to]; } } for (let [from, to] of edges.map(r => r.split("-"))) { @@ -77,62 +64,33 @@ function buildGraph(edges) { const roadGraph = buildGraph(roads); ``` -Given an array of edges, `buildGraph` creates a map object that, for -each node, stores an array of connected nodes. - {{index "split method"}} -It uses the `split` method to go from the road strings, which have the -form `"Start-End"`, to two-element arrays containing the start and end -as separate strings. +Given an array of edges, `buildGraph` creates a map object that, for each node, stores an array of connected nodes. It uses the `split` method to go from the road strings—which have the form `"Start-End"`)—to two-element arrays containing the start and end as separate strings. ## The task -Our ((robot)) will be moving around the village. There are parcels -in various places, each addressed to some other place. The robot picks -up parcels when it comes to them and delivers them when it arrives at -their destinations. +Our ((robot)) will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes across them and delivers them when it arrives at their destinations. -The automaton must decide, at each point, where to go next. It has -finished its task when all parcels have been delivered. +The automaton must decide, at each point, where to go next. It has finished its task when all parcels have been delivered. {{index simulation, "virtual world"}} -To be able to simulate this process, we must define a virtual world -that can describe it. This model tells us where the robot is and where -the parcels are. When the robot has decided to move somewhere, we need -to update the model to reflect the new situation. +To be able to simulate this process, we must define a virtual world that can describe it. This model tells us where the robot is and where the parcels are. When the robot has decided to move somewhere, we need to update the model to reflect the new situation. {{index [state, in objects]}} -If you're thinking in terms of ((object-oriented programming)), your -first impulse might be to start defining objects for the various -elements in the world: a ((class)) for the robot, one for a parcel, -maybe one for places. These could then hold properties that describe -their current ((state)), such as the pile of parcels at a location, -which we could change when updating the world. - -This is wrong. +If you're thinking in terms of ((object-oriented programming)), your first impulse might be to start defining objects for the various elements in the world: a ((class)) for the robot, one for a parcel, maybe one for places. These could then hold properties that describe their current ((state)), such as the pile of parcels at a location, which we could change when updating the world. -At least, it usually is. The fact that something sounds like an object -does not automatically mean that it should be an object in your -program. Reflexively writing classes for every concept in your -application tends to leave you with a collection of interconnected -objects that each have their own internal, changing state. Such -programs are often hard to understand and thus easy to break. +This is wrong. At least, it usually is. The fact that something sounds like an object does not automatically mean that it should be an object in your program. Reflexively writing classes for every concept in your application tends to leave you with a collection of interconnected objects that each have their own internal, changing state. Such programs are often hard to understand and thus easy to break. {{index [state, in objects]}} -Instead, let's condense the village's state down to the minimal -set of values that define it. There's the robot's current location and -the collection of undelivered parcels, each of which has a current -location and a destination address. That's it. +Instead, let's condense the village's state down to the minimal set of values that define it. There's the robot's current location and the collection of undelivered parcels, each of which has a current location and a destination address. That's it. {{index "VillageState class", "persistent data structure"}} -And while we're at it, let's make it so that we don't _change_ this -state when the robot moves but rather compute a _new_ state for the -situation after the move. +While we're at it, let's make it so that we don't _change_ this state when the robot moves but rather compute a _new_ state for the situation after the move. ```{includeCode: true} class VillageState { @@ -155,23 +113,13 @@ 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"}} -Then it creates a new state with the destination as the robot's new -place. But it also needs to create a new set of parcels—parcels that -the robot is carrying (that are at the robot's current place) need to -be moved along to the new place. And parcels that are addressed to the -new place need to be delivered—that is, they need to be removed from -the set of undelivered parcels. The call to `map` takes care of the -moving, and the call to `filter` does the delivering. +Next, the method creates a new state with the destination as the robot's new place. It also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot's current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to `map` takes care of the moving, and the call to `filter` does the delivering. -Parcel objects aren't changed when they are moved but re-created. The -`move` method gives us a new village state but leaves the old one -entirely intact. +Parcel objects aren't changed when they are moved but re-created. The `move` method gives us a new village state but leaves the old one entirely intact. ``` let first = new VillageState( @@ -188,28 +136,15 @@ console.log(first.place); // → Post Office ``` -The move causes the parcel to be delivered, and this is reflected in -the next state. But the initial state still describes the situation -where the robot is at the post office and the parcel is undelivered. +The move causes the parcel to be delivered, which is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered. ## Persistent data {{index "persistent data structure", mutability, ["data structure", immutable]}} -Data structures that don't change are called _((immutable))_ or -_persistent_. They behave a lot like strings and numbers in that they -are who they are and stay that way, rather than containing different -things at different times. - -In JavaScript, just about everything _can_ be changed, so working with -values that are supposed to be persistent requires some restraint. -There is a function called `Object.freeze` that changes an object so -that writing to its properties is ignored. You could use that to make -sure your objects aren't changed, if you want to be careful. Freezing -does require the computer to do some extra work, and having updates -ignored is just about as likely to confuse someone as having them do -the wrong thing. So I usually prefer to just tell people that a given -object shouldn't be messed with and hope they remember it. +Data structures that don't change are called _((immutable))_ or _persistent_. They behave a lot like strings and numbers in that they are who they are and stay that way, rather than containing different things at different times. + +In JavaScript, just about everything _can_ be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called `Object.freeze` that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren't changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. I usually prefer to just tell people that a given object shouldn't be messed with and hope they remember it. ``` let object = Object.freeze({value: 5}); @@ -218,44 +153,21 @@ console.log(object.value); // → 5 ``` -Why am I going out of my way to not change objects when the language -is obviously expecting me to? - -Because it helps me understand my programs. This is about complexity -management again. When the objects in my system are fixed, stable -things, I can consider operations on them in isolation—moving to -Alice's house from a given start state always produces the same new -state. When objects change over time, that adds a whole new dimension -of complexity to this kind of reasoning. +Why am I going out of my way to not change objects when the language is obviously expecting me to? Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice's house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning. -For a small system like the one we are building in this chapter, we -could handle that bit of extra complexity. But the most important -limit on what kind of systems we can build is how much we can -understand. Anything that makes your code easier to understand makes -it possible to build a more ambitious system. +For a small system like the one we are building in this chapter, we could handle that bit of extra complexity. But the most important limit on what kind of systems we can build is how much we can understand. Anything that makes your code easier to understand makes it possible to build a more ambitious system. -Unfortunately, although understanding a system built on persistent data -structures is easier, _designing_ one, especially when your -programming language isn't helping, can be a little harder. We'll -look for opportunities to use persistent data structures in this -book, but we'll also be using changeable ones. +Unfortunately, although understanding a system built on persistent data structures is easier, _designing_ one, especially when your programming language isn't helping, can be a little harder. We'll look for opportunities to use persistent data structures in this book, but we'll also be using changeable ones. ## Simulation {{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"}} -Because we want robots to be able to remember things, so that they can -make and execute plans, we also pass them their memory and allow them -to return a new memory. Thus, the thing a robot returns is an object -containing both the direction it wants to move in and a memory value -that will be given back to it the next time it is called. +Because we want robots to be able to remember things so they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called. ```{includeCode: true} function runRobot(state, robot, memory) { @@ -272,15 +184,9 @@ function runRobot(state, robot, memory) { } ``` -Consider what a robot has to do to "solve" a given state. It must pick -up all parcels by visiting every location that has a parcel and -deliver them by visiting every location that a parcel is addressed to, -but only after picking up the parcel. +Consider what a robot has to do to "solve" a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location to which a parcel is addressed, but only after picking up the parcel. -What is the dumbest strategy that could possibly work? The robot could -just walk in a random direction every turn. That means, with -great likelihood, it will eventually run into all parcels and then -also at some point reach the place where they should be delivered. +What is the dumbest strategy that could possibly work? The robot could just walk in a random direction every turn. That means, with great likelihood, it will eventually run into all parcels and then also at some point reach the place where they should be delivered. {{index "randomPick function", "randomRobot function"}} @@ -299,20 +205,11 @@ 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. +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. -To put this sophisticated robot to work, we'll first need a way to -create a new state with some parcels. A static method (written here by -directly adding a property to the constructor) is a good place to put -that functionality. +To put this sophisticated robot to work, we'll first need a way to create a new state with some parcels. A static method (written here by directly adding a property to the constructor) is a good place to put that functionality. ```{includeCode: true} VillageState.random = function(parcelCount = 5) { @@ -331,9 +228,7 @@ VillageState.random = function(parcelCount = 5) { {{index "do loop"}} -We don't want any parcels that are sent from the same place that they -are addressed to. For this reason, the `do` loop keeps picking new -places when it gets one that's equal to the address. +We don't want any parcels to be sent from the same place that they are addressed to. For this reason, the `do` loop keeps picking new places when it gets one that's equal to the address. Let's start up a virtual world. @@ -345,25 +240,17 @@ runRobot(VillageState.random(), randomRobot); // → Done in 63 turns ``` -It takes the robot a lot of turns to deliver the parcels because it -isn't planning ahead very well. We'll address that soon. +It takes the robot a lot of turns to deliver the parcels because it isn't planning ahead very well. We'll address that soon. {{if interactive -For a more pleasant perspective on the simulation, you can use the -`runRobotAnimation` function that's available in [this chapter's -programming environment](https://eloquentjavascript.net/code/#7). -This runs the simulation, but instead of outputting text, it shows -you the robot moving around the village map. +For a more pleasant perspective on the simulation, you can use the `runRobotAnimation` function that's available in [this chapter's programming environment](https://eloquentjavascript.net/code/#7). This runs the simulation, but instead of outputting text, it shows you the robot moving around the village map. ```{test: no} runRobotAnimation(VillageState.random(), randomRobot); ``` -The way `runRobotAnimation` is implemented will remain a mystery for -now, but after you've read the [later chapters](dom) of this book, -which discuss JavaScript integration in web browsers, you'll be able -to guess how it works. +The way `runRobotAnimation` is implemented will remain a mystery for now, but after you've read the [later chapters](dom) of this book, which discuss JavaScript integration in web browsers, you'll be able to guess how it works. if}} @@ -371,12 +258,7 @@ if}} {{index "mailRoute array"}} -We should be able to do a lot better than the random ((robot)). An -easy improvement would be to take a hint from the way real-world mail -delivery works. If we find a route that passes all places in the -village, the robot could run that route twice, at which point it is -guaranteed to be done. Here is one such route (starting from the post -office): +We should be able to do a lot better than the random ((robot)). An easy improvement would be to take a hint from the way real-world mail delivery works. If we find a route that passes all places in the village, the robot could run that route twice, at which point it is guaranteed to be done. Here is one such route (starting from the post office): ```{includeCode: true} const mailRoute = [ @@ -389,9 +271,7 @@ const mailRoute = [ {{index "routeRobot function"}} -To implement the route-following robot, we'll need to make use of -robot memory. The robot keeps the rest of its route in its memory and -drops the first element every turn. +To implement the route-following robot, we'll need to make use of robot memory. The robot keeps the rest of its route in its memory and drops the first element every turn. ```{includeCode: true} function routeRobot(state, memory) { @@ -402,8 +282,7 @@ function routeRobot(state, memory) { } ``` -This robot is a lot faster already. It'll take a maximum of 26 turns -(twice the 13-step route) but usually less. +This robot is a lot faster already. It'll take a maximum of 26 turns (twice the 13-step route) but usually less. {{if interactive @@ -415,37 +294,17 @@ if}} ## Pathfinding -Still, I wouldn't really call blindly following a fixed route -intelligent behavior. The ((robot)) could work more efficiently if it -adjusted its behavior to the actual work that needs to be done. +Still, I wouldn't really call blindly following a fixed route intelligent behavior. The ((robot)) could work more efficiently if it adjusted its behavior to the actual work that needs to be done. {{index pathfinding}} -To do that, it has to be able to deliberately move toward a given -parcel or toward the location where a parcel has to be delivered. -Doing that, even when the goal is more than one move away, will -require some kind of route-finding function. - -The problem of finding a route through a ((graph)) is a typical -_((search problem))_. We can tell whether a given solution (a route) -is a valid solution, but we can't directly compute the solution the -way we could for 2 + 2. Instead, we have to keep creating potential -solutions until we find one that works. - -The number of possible routes through a graph is infinite. But -when searching for a route from _A_ to _B_, we are interested only in -the ones that start at _A_. We also don't care about routes that visit -the same place twice—those are definitely not the most efficient route -anywhere. So that cuts down on the number of routes that the route -finder has to consider. - -In fact, we are mostly interested in the _shortest_ route. So we want -to make sure we look at short routes before we look at longer ones. A -good approach would be to "grow" routes from the starting point, -exploring every reachable place that hasn't been visited yet, until a -route reaches the goal. That way, we'll only explore routes that are -potentially interesting, and we'll find the shortest route (or one of the -shortest routes, if there are more than one) to the goal. +To do that, it has to be able to deliberately move toward a given parcel or toward the location where a parcel has to be delivered. Doing that, even when the goal is more than one move away, will require some kind of route-finding function. + +The problem of finding a route through a ((graph)) is a typical _((search problem))_. We can tell whether a given solution (a route) is valid, but we can't directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works. + +The number of possible routes through a graph is infinite. But when searching for a route from _A_ to _B_, we are interested only in the ones that start at _A_. We also don't care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider. + +In fact, 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"}} @@ -468,39 +327,17 @@ function findRoute(graph, from, to) { } ``` -The exploring has to be done in the right order—the places that were -reached first have to be explored first. We can't immediately explore -a place as soon as we reach it because that would mean places reached -_from there_ would also be explored immediately, and so on, even -though there may be other, shorter paths that haven't yet been -explored. - -Therefore, the function keeps a _((work list))_. This is an array of -places that should be explored next, along with the route that got us -there. It starts with just the start position and an empty route. - -The search then operates by taking the next item in the list and -exploring that, which means all roads going from that place are -looked at. If one of them is the goal, a finished route can be -returned. Otherwise, if we haven't looked at this place before, a new -item is added to the list. If we have looked at it before, since we -are looking at short routes first, we've found either a longer route -to that place or one precisely as long as the existing one, and we -don't need to explore it. - -You can visually imagine this as a web of known routes crawling out -from the start location, growing evenly on all sides (but never -tangling back into itself). As soon as the first thread reaches the -goal location, that thread is traced back to the start, giving us our -route. +The exploring has to be done in the right order—the places that were reached first have to be explored first. We can't immediately explore a place as soon as we reach it because that would mean places reached _from there_ would also be explored immediately, and so on, even though there may be other, shorter paths that haven't yet been explored. + +Therefore, the function keeps a _((work list))_. This is an array of places that should be explored next, along with the route that got us there. It starts with just the start position and an empty route. + +The search then operates by taking the next item in the list and exploring that, which means it looks at all roads going from that place. If one of them is the goal, a finished route can be returned. Otherwise, if we haven't looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we've found either a longer route to that place or one precisely as long as the existing one, and we don't need to explore it. + +You can visualize this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route. {{index "connected graph"}} -Our code doesn't handle the situation where there are no more work -items on the work list because we know that our graph is _connected_, -meaning that every location can be reached from all other locations. -We'll always be able to find a route between two points, and the -search can't fail. +Our code doesn't handle the situation where there are no more work items on the work list because we know that our graph is _connected_, meaning that every location can be reached from all other locations. We'll always be able to find a route between two points, and the search can't fail. ```{includeCode: true} function goalOrientedRobot({place, parcels}, route) { @@ -518,12 +355,7 @@ function goalOrientedRobot({place, parcels}, route) { {{index "goalOrientedRobot function"}} -This robot uses its memory value as a list of directions to move in, -just like the route-following robot. Whenever that list is empty, it -has to figure out what to do next. It takes the first undelivered -parcel in the set and, if that parcel hasn't been picked up yet, plots a -route toward it. If the parcel _has_ been picked up, it still needs to be -delivered, so the robot creates a route toward the delivery address instead. +This robot uses its memory value as a list of directions to move in, just like the route-following robot. Whenever that list is empty, it has to figure out what to do next. It takes the first undelivered parcel in the set and, if that parcel hasn't been picked up yet, plots a route toward it. If the parcel _has_ been picked up, it still needs to be delivered, so the robot creates a route toward the delivery address instead. {{if interactive @@ -536,8 +368,7 @@ runRobotAnimation(VillageState.random(), if}} -This robot usually finishes the task of delivering 5 parcels in about -16 turns. That's slightly better than `routeRobot` but still definitely not optimal. +This robot usually finishes the task of delivering 5 parcels in about 16 turns. That's slightly better than `routeRobot` but still definitely not optimal. We'll continue refining it in the exercises. ## Exercises @@ -545,17 +376,11 @@ This robot usually finishes the task of delivering 5 parcels in about {{index "measuring a robot (exercise)", testing, automation, "compareRobots function"}} -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. +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. +For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot. {{if interactive @@ -572,15 +397,9 @@ if}} {{index "measuring a robot (exercise)", "runRobot function"}} -You'll have to write a variant of the `runRobot` function that, -instead of logging the events to the console, returns the number of -steps the robot took to complete the task. +You'll have to write a variant of the `runRobot` function that, instead of logging the events to the console, returns the number of steps the robot took to complete the task. -Your measurement function can then, in a loop, generate new states and -count the steps each of the robots takes. When it has generated enough -measurements, it can use `console.log` to output the average for each -robot, which is the total number of steps taken divided by the number -of measurements. +Your measurement function can then, in a loop, generate new states and count the steps each of the robots takes. When it has generated enough measurements, it can use `console.log` to output the average for each robot, which is the total number of steps taken divided by the number of measurements. hint}} @@ -588,12 +407,9 @@ hint}} {{index "robot efficiency (exercise)"}} -Can you write a robot that finishes the delivery task faster than -`goalOrientedRobot`? If you observe that robot's behavior, what -obviously stupid things does it do? How could those be improved? +Can you write a robot that finishes the delivery task faster than `goalOrientedRobot`? If you observe that robot's behavior, what obviously stupid things does it do? How could those be improved? -If you solved the previous exercise, you might want to use your -`compareRobots` function to verify whether you improved the robot. +If you solved the previous exercise, you might want to use your `compareRobots` function to verify whether you improved the robot. {{if interactive @@ -609,15 +425,9 @@ if}} {{index "robot efficiency (exercise)"}} -The main limitation of `goalOrientedRobot` is that it considers only -one parcel at a time. It will often walk back and forth across the -village because the parcel it happens to be looking at happens to be -at the other side of the map, even if there are others much closer. +The main limitation of `goalOrientedRobot` is that it considers only one parcel at a time. It will often walk back and forth across the village because the parcel it happens to be looking at happens to be at the other side of the map, even if there are others much closer. -One possible solution would be to compute routes for all packages and -then take the shortest one. Even better results can be obtained, if -there are multiple shortest routes, by preferring the ones that go to -pick up a package instead of delivering a package. +One possible solution would be to compute routes for all packages and then take the shortest one. Even better results can be obtained, if there are multiple shortest routes, by preferring the ones that go to pick up a package instead of delivering a package. hint}} @@ -625,35 +435,19 @@ hint}} {{index "persistent group (exercise)", "persistent data structure", "Set class", "set (data structure)", "Group class", "PGroup class"}} -Most data structures provided in a standard JavaScript environment -aren't very well suited for persistent use. Arrays have `slice` and -`concat` methods, which allow us to easily create new arrays without -damaging the old one. But `Set`, for example, has no methods for -creating a new set with an item added or removed. - -Write a new class `PGroup`, similar to the `Group` class from [Chapter -?](object#groups), which stores a set of values. Like `Group`, it has -`add`, `delete`, and `has` methods. +Most data structures provided in a standard JavaScript environment aren't very well suited for persistent use. Arrays have `slice` and `concat` methods, which allow us to easily create new arrays without damaging the old one. But `Set`, for example, has no methods for creating a new set with an item added or removed. -Its `add` method, however, should return a _new_ `PGroup` instance -with the given member added and leave the old one unchanged. -Similarly, `delete` creates a new instance without a given member. +Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. Its `add` method, however, should return a _new_ `PGroup` instance with the given member added and leave the old one unchanged. Similarly, `delete` should create a new instance without a given member. -The class should work for values of any type, not just strings. It -does _not_ have to be efficient when used with large amounts of -values. +The class should work for values of any type, not just strings. It does _not_ have to be efficient when used with large numbers of values. {{index [interface, object]}} -The ((constructor)) shouldn't be part of the class's interface -(though you'll definitely want to use it internally). Instead, there -is an empty instance, `PGroup.empty`, that can be used as a starting -value. +The ((constructor)) shouldn't be part of the class's interface (though you'll definitely want to use it internally). Instead, there is an empty instance, `PGroup.empty`, that can be used as a starting value. {{index singleton}} -Why do you need only one `PGroup.empty` value, rather than having a -function that creates a new, empty map every time? +Why do you need only one `PGroup.empty` value rather than having a function that creates a new, empty map every time? {{if interactive @@ -680,27 +474,18 @@ 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. +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 method"}} +{{index "static property"}} -To add a property (`empty`) to a constructor that is not a method, you -have to add it to the constructor after the class definition, as a -regular property. +To add the `empty` property to the constructor, you can declare it as a static property. -You need only one `empty` instance because all empty groups are the -same and instances of the class don't change. You can create many -different groups from that single empty group without affecting it. +You need only one `empty` instance because all empty groups are the same and instances of the class don't change. You can create many different groups from that single empty group without affecting it. hint}} diff --git a/08_error.md b/08_error.md index 6a591ca9f..f191c9ae6 100644 --- a/08_error.md +++ b/08_error.md @@ -4,59 +4,33 @@ {{quote {author: "Brian Kernighan and P.J. Plauger", title: "The Elements of Programming Style", chapter: true} -Debugging is twice as hard as writing the code in the first place. -Therefore, if you write the code as cleverly as possible, you are, by -definition, not smart enough to debug it. +Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. quote}} -{{figure {url: "img/chapter_picture_8.jpg", alt: "Picture of a collection of bugs", chapter: framed}}} +{{figure {url: "img/chapter_picture_8.jpg", alt: "Illustration showing various insects and a centipede", chapter: framed}}} {{index "Kernighan, Brian", "Plauger, P.J.", debugging, "error handling"}} -Flaws in computer programs are usually called _((bug))s_. It makes -programmers feel good to imagine them as little things that just -happen to crawl into our work. In reality, of course, we put them -there ourselves. +Flaws in computer programs are usually called _((bug))s_. It makes programmers feel good to imagine them as little things that just happen to crawl into our work. In reality, of course, we put them there ourselves. -If a program is crystallized thought, you can roughly categorize bugs -into those caused by the thoughts being confused and those caused by -mistakes introduced while converting a thought to code. The former -type is generally harder to diagnose and fix than the latter. +If a program is crystallized thought, we can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter. ## Language {{index parsing, analysis}} -Many mistakes could be pointed out to us automatically by the -computer, if it knew enough about what we're trying to do. But here -JavaScript's looseness is a hindrance. Its concept of bindings and -properties is vague enough that it will rarely catch ((typo))s before -actually running the program. And even then, it allows you to do some -clearly nonsensical things without complaint, such as computing -`true * "monkey"`. +Many mistakes could be pointed out to us automatically by the computer if it knew enough about what we're trying to do. But here, JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. Even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. {{index [syntax, error], [property, access]}} -There are some things that JavaScript does complain about. Writing a -program that does not follow the language's ((grammar)) will -immediately make the computer complain. Other things, such as calling -something that's not a function or looking up a property on an -((undefined)) value, will cause an error to be reported when the -program tries to perform the action. +There are some things that JavaScript does complain about. Writing a program that does not follow the language's ((grammar)) will immediately make the computer complain. Other things, such as calling something that's not a function or looking up a property on an ((undefined)) value, will cause an error to be reported when the program tries to perform the action. {{index NaN, error}} -But often, your nonsense computation will merely produce `NaN` (not a -number) or an undefined value, while the program happily continues, -convinced that it's doing something meaningful. The mistake will -manifest itself only later, after the bogus value has traveled through -several functions. It might not trigger an error at all but silently -cause the program's output to be wrong. Finding the source of such -problems can be difficult. +Often, however, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all, but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. -The process of finding mistakes—bugs—in programs is called -_((debugging))_. +The process of finding mistakes—bugs—in programs is called _((debugging))_. ## Strict mode @@ -64,9 +38,7 @@ _((debugging))_. {{indexsee "use strict", "strict mode"}} -JavaScript can be made a _little_ stricter by enabling _strict -mode_. This is done by putting the string `"use strict"` at the top of -a file or a function body. Here's an example: +JavaScript can be made a _little_ stricter by enabling _strict mode_. This can done by putting the string `"use strict"` at the top of a file or a function body. Here's an example: ```{test: "error \"ReferenceError: counter is not defined\""} function canYouSpotTheProblem() { @@ -80,30 +52,19 @@ canYouSpotTheProblem(); // → ReferenceError: counter is not defined ``` +{{index ECMAScript, compatibility}} + +Code inside classes and modules (which we will discuss in [Chapter ?](modules)) is automatically strict. The old 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]}} -Normally, when you forget to put `let` in front of your binding, as -with `counter` in the example, JavaScript quietly creates a global -binding and uses that. In strict mode, an ((error)) is reported -instead. This is very helpful. It should be noted, though, that this -doesn't work when the binding in question already exists as a global -binding. In that case, the loop will still quietly overwrite the value -of the binding. +Normally, when you forget to put `let` in front of your binding, as with `counter` in the example, JavaScript quietly creates a global binding and uses that. In strict mode, an ((error)) is reported instead. This is very helpful. It should be noted, though, that this doesn't work when the binding in question already exists somewhere in scope. In that case, the loop will still quietly overwrite the value of the binding. {{index "this binding", "global object", undefined, "strict mode"}} -Another change in strict mode is that the `this` binding holds the -value `undefined` in functions that are not called as ((method))s. -When making such a call outside of strict mode, `this` refers to the -global scope object, which is an object whose properties are the -global bindings. So if you accidentally call a method or constructor -incorrectly in strict mode, JavaScript will produce an error as soon -as it tries to read something from `this`, rather than happily writing -to the global scope. +Another change in strict mode is that the `this` binding holds the value `undefined` in functions that are not called as ((method))s. When making such a call outside of strict mode, `this` refers to the global scope object, which is an object whose properties are the global bindings. So if you accidentally call a method or constructor incorrectly in strict mode, JavaScript will produce an error as soon as it tries to read something from `this`, rather than happily writing to the global scope. -For example, consider the following code, which calls a -((constructor)) function without the `new` keyword so that its `this` -will _not_ refer to a newly constructed object: +For example, consider the following code, which calls a ((constructor)) function without the `new` keyword so that its `this` will _not_ refer to a newly constructed object: ``` function Person(name) { this.name = name; } @@ -114,11 +75,9 @@ console.log(name); {{index error}} -So the bogus call to `Person` succeeded but returned an undefined -value and created the global binding `name`. In strict mode, the -result is different. +The bogus call to `Person` succeeded, but returned an undefined value and created the global binding `name`. In strict mode, the result is different. -```{test: "error \"TypeError: Cannot set property 'name' of undefined\""} +```{test: "error \"TypeError: Cannot set properties of undefined (setting 'name')\""} "use strict"; function Person(name) { this.name = name; } let ferdinand = Person("Ferdinand"); // forgot new @@ -127,98 +86,56 @@ 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"}} -Strict mode does a few more things. It disallows giving a function -multiple parameters with the same name and removes certain problematic -language features entirely (such as the `with` statement, which is so -wrong it is not further discussed in this book). +Strict mode does a few more things. It disallows giving a function multiple parameters with the same name and removes certain problematic language features entirely (such as the `with` statement, which is so wrong it is not further discussed in this book). {{index debugging}} -In short, putting `"use strict"` at the top of your program rarely -hurts and might help you spot a problem. +In short, putting `"use strict"` at the top of your program rarely hurts and might help you spot a problem. ## Types -Some languages want to know the types of all your bindings and -expressions before even running a program. They will tell you right -away when a type is used in an inconsistent way. JavaScript considers -types only when actually running the program, and even there often -tries to implicitly convert values to the type it expects, so it's not -much help. +Some languages want to know the types of all your bindings and expressions before even running a program. They will tell you right away when a type is used in an inconsistent way. JavaScript considers types only when actually running the program, and even there often tries to implicitly convert values to the type it expects, so it's not much help. -Still, types provide a useful framework for talking about programs. A -lot of mistakes come from being confused about the kind of value that -goes into or comes out of a function. If you have that information -written down, you're less likely to get confused. +Still, types provide a useful framework for talking about programs. A lot of mistakes come from being confused about the kind of value that goes into or comes out of a function. If you have that information written down, you're less likely to get confused. -You could add a comment like the following before the `goalOrientedRobot` -function from the previous chapter to describe its type: +You could add a comment like the following before the `findRoute` function from the previous chapter to describe its type: ``` -// (VillageState, Array) → {direction: string, memory: Array} -function goalOrientedRobot(state, memory) { +// (graph: Object, from: string, to: string) => string[] +function findRoute(graph, from, to) { // ... } ``` -There are a number of different conventions for annotating JavaScript -programs with types. +There are a number of different conventions for annotating JavaScript programs with types. -One thing about types is that they need to introduce their own -complexity to be able to describe enough code to be useful. What do -you think would be the type of the `randomPick` function that returns -a random element from an array? You'd need to introduce a _((type -variable))_, _T_, which can stand in for any type, so that you can -give `randomPick` a type like `([T]) → T` (function from an array of -*T*s to a *T*). +One thing about types is that they need to introduce their own complexity to be able to describe enough code to be useful. What do you think would be the type of the `randomPick` function that returns a random element from an array? You'd need to introduce a _((type variable))_, _T_, which can stand in for any type, so that you can give `randomPick` a type like `(T[]) → T` (function from an array of *T*s to a *T*). {{index "type checking", TypeScript}} {{id typing}} -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. +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 {{index "test suite", "run-time error", automation, testing}} -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. +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. +Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice rather than randomly running into it at some later time. {{index "toUpperCase method"}} -Tests usually take the form of little labeled programs that verify -some aspect of your code. For example, a set of tests for the -(standard, probably already tested by someone else) `toUpperCase` -method might look like this: +Tests usually take the form of little labeled programs that verify some aspect of your code. For example, a set of tests for the (standard, probably already tested by someone else) `toUpperCase` method might look like this: ``` function test(label, body) { @@ -238,48 +155,27 @@ test("don't convert case-less characters", () => { {{index "domain-specific language"}} -Writing tests like this tends to produce rather repetitive, awkward -code. Fortunately, there exist pieces of software that help you build -and run collections of tests (_((test suites))_) by providing a -language (in the form of functions and methods) suited to expressing -tests and by outputting informative information when a test fails. -These are usually called _((test runners))_. +Writing tests like this tends to produce rather repetitive, awkward code. Fortunately, there exist pieces of software that help you build and run collections of tests (_((test suites))_) by providing a language (in the form of functions and methods) suited to expressing tests and by outputting informative information when a test fails. These are usually called _((test runners))_. {{index "persistent data structure"}} -Some code is easier to test than other code. Generally, the more -external objects that the code interacts with, the harder it is to set -up the context in which to test it. The style of programming shown in -the [previous chapter](robot), which uses self-contained persistent -values rather than changing objects, tends to be easy to test. +Some code is easier to test than other code. Generally, the more external objects that the code interacts with, the harder it is to set up the context in which to test it. The style of programming shown in the [previous chapter](robot), which uses self-contained persistent values rather than changing objects, tends to be easy to test. ## Debugging {{index debugging}} -Once you notice there is something wrong with your program -because it misbehaves or produces errors, the next step is to figure -out _what_ the problem is. +Once you notice there is something wrong with your program because it misbehaves or produces errors, the next step is to figure out _what_ the problem is. -Sometimes it is obvious. The ((error)) message will point at a -specific line of your program, and if you look at the error -description and that line of code, you can often see the problem. +Sometimes it is obvious. The ((error)) message will point at a specific line of your program, and if you look at the error description and that line of code, you can often see the problem. {{index "run-time error"}} -But not always. Sometimes the line that triggered the problem is -simply the first place where a flaky value produced elsewhere gets -used in an invalid way. If you have been solving the ((exercises)) in -earlier chapters, you will probably have already experienced such -situations. +But not always. Sometimes the line that triggered the problem is simply the first place where a flaky value produced elsewhere gets used in an invalid way. If you have been solving the ((exercises)) in earlier chapters, you will probably have already experienced such situations. {{index "decimal number", "binary number"}} -The following example program tries to convert a whole number to a -string in a given base (decimal, binary, and so on) by repeatedly -picking out the last ((digit)) and then dividing the number to get rid -of this digit. But the strange output that it currently produces -suggests that it has a ((bug)). +The following example program tries to convert a whole number to a string in a given base (decimal, binary, and so on) by repeatedly picking out the last ((digit)) and then dividing the number to get rid of this digit. But the strange output that it currently produces suggests that it has a ((bug)). ``` function numberToString(n, base = 10) { @@ -300,25 +196,15 @@ console.log(numberToString(13, 10)); {{index analysis}} -Even if you see the problem already, pretend for a moment that you -don't. We know that our program is malfunctioning, and we want to find -out why. +Even if you see the problem already, pretend for a moment that you don't. We know that our program is malfunctioning, and we want to find out why. {{index "trial and error"}} -This is where you must resist the urge to start making random changes -to the code to see whether that makes it better. Instead, _think_. Analyze -what is happening and come up with a ((theory)) of why it might be -happening. Then, make additional observations to test this theory—or, -if you don't yet have a theory, make additional observations to help -you come up with one. +This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, _think_. Analyze what is happening and come up with a ((theory)) of why it might be happening. Then make additional observations to test this theory—or, if you don't yet have a theory, make additional observations to help you come up with one. {{index "console.log", output, debugging, logging}} -Putting a few strategic `console.log` calls into the program is a good -way to get additional information about what the program is doing. In -this case, we want `n` to take the values `13`, `1`, and then `0`. -Let's write out its value at the start of the loop. +Putting a few strategic `console.log` calls into the program is a good way to get additional information about what the program is doing. In this case, we want `n` to take the values `13`, `1`, and then `0`. Let's write out its value at the start of the loop. ```{lang: null} 13 @@ -331,55 +217,31 @@ Let's write out its value at the start of the loop. {{index rounding}} -_Right_. Dividing 13 by 10 does not produce a whole number. Instead of -`n /= base`, what we actually want is `n = Math.floor(n / base)` so -that the number is properly "shifted" to the right. +_Right_. Dividing 13 by 10 does not produce a whole number. Instead of `n /= base`, what we actually want is `n = Math.floor(n / base)` so that the number is properly "shifted" to the right. {{index "JavaScript console", "debugger statement"}} -An alternative to using `console.log` to peek into the program's -behavior is to use the _debugger_ capabilities of your browser. -Browsers come with the ability to set a _((breakpoint))_ on a specific -line of your code. When the execution of the program reaches a line -with a breakpoint, it is paused, and you can inspect the values of -bindings at that point. I won't go into details, as debuggers differ -from browser to browser, but look in your browser's ((developer -tools)) or search the Web for more information. +An alternative to using `console.log` to peek into the program's behavior is to use the _debugger_ capabilities of your browser. Browsers come with the ability to set a _((breakpoint))_ on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won't go into details, as debuggers differ from browser to browser, but look in your browser's ((developer tools)) or search the web for instructions. -Another way to set a breakpoint is to include a `debugger` statement -(consisting of simply that keyword) in your program. If the -((developer tools)) of your browser are active, the program will pause -whenever it reaches such a statement. +Another way to set a breakpoint is to include a `debugger` statement (consisting simply of that keyword) in your program. If the ((developer tools)) of your browser are active, the program will pause whenever it reaches such a statement. ## Error propagation {{index input, output, "run-time error", error, validation}} -Not all problems can be prevented by the programmer, unfortunately. If -your program communicates with the outside world in any way, it is -possible to get malformed input, to become overloaded with work, or to -have the network fail. +Not all problems can be prevented by the programmer, unfortunately. If your program communicates with the outside world in any way, it is possible to get malformed input, to become overloaded with work, or to have the network fail. {{index "error recovery"}} -If you're programming only for yourself, you can afford to just ignore -such problems until they occur. But if you build something that is -going to be used by anybody else, you usually want the program to do -better than just crash. Sometimes the right thing to do is take the -bad input in stride and continue running. In other cases, it is better -to report to the user what went wrong and then give up. But in either -situation, the program has to actively do something in response to the -problem. +If you're programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. In either situation the program has to actively do something in response to the problem. {{index "promptNumber function", validation}} -Say you have a function `promptNumber` that asks the user for a number -and returns it. What should it return if the user inputs "orange"? +Say you have a function `promptNumber` that asks the user for a number and returns it. What should it return if the user inputs "orange"? {{index null, undefined, "return value", "special return value"}} -One option is to make it return a special value. Common choices for -such values are `null`, `undefined`, or -1. +One option is to make it return a special value. Common choices for such values are `null`, `undefined`, or `-1`. ```{test: no} function promptNumber(question) { @@ -391,68 +253,39 @@ function promptNumber(question) { console.log(promptNumber("How many trees do you see?")); ``` -Now any code that calls `promptNumber` must check whether an actual -number was read and, failing that, must somehow recover—maybe by -asking again or by filling in a default value. Or it could again -return a special value to _its_ caller to indicate that it failed to -do what it was asked. +Now any code that calls `promptNumber` must check whether an actual number was read and, failing that, must somehow recover—maybe by asking again or by filling in a default value. Or it could again return a special value to _its_ caller to indicate that it failed to do what it was asked. {{index "error handling"}} -In many situations, mostly when ((error))s are common and the caller -should be explicitly taking them into account, returning a special -value is a good way to indicate an error. It does, however, have its -downsides. First, what if the function can already return every -possible kind of value? In such a function, you'll have to do -something like wrap the result in an object to be able to distinguish -success from failure. +In many situations, mostly when ((error))s are common and the caller should be explicitly taking them into account, returning a special value is a good way to indicate an error. It does, however, have its downsides. First, what if the function can already return every possible kind of value? In such a function, you'll have to do something like wrap the result in an object to be able to distinguish success from failure, the way the `next` method on the iterator interface does. ``` function lastElement(array) { if (array.length == 0) { return {failed: true}; } else { - return {element: array[array.length - 1]}; + return {value: array[array.length - 1]}; } } ``` {{index "special return value", readability}} -The second issue with returning special values is that it can lead to -awkward code. If a piece of code calls `promptNumber` 10 times, -it has to check 10 times whether `null` was returned. And if its -response to finding `null` is to simply return `null` itself, callers -of the function will in turn have to check for it, and so on. +The second issue with returning special values is that it can lead to awkward code. If a piece of code calls `promptNumber` 10 times, it has to check 10 times whether `null` was returned. If its response to finding `null` is to simply return `null` itself, callers of the function will in turn have to check for it, and so on. ## Exceptions {{index "error handling"}} -When a function cannot proceed normally, what we would _like_ to do is -just stop what we are doing and immediately jump to a place that knows -how to handle the problem. This is what _((exception handling))_ does. +When a function cannot proceed normally, what we would often _like_ to do is just stop what we are doing and immediately jump to a place that knows how to handle the problem. This is what _((exception handling))_ does. {{index ["control flow", exceptions], "raising (exception)", "throw keyword", "call stack"}} -Exceptions are a mechanism that makes it possible for code that runs -into a problem to _raise_ (or _throw_) an exception. An exception can -be any value. Raising one somewhat resembles a super-charged return -from a function: it jumps out of not just the current function but -also its callers, all the way down to the first call that -started the current execution. This is called _((unwinding the -stack))_. You may remember the stack of function calls that was -mentioned in [Chapter ?](functions#stack). An exception zooms down -this stack, throwing away all the call contexts it encounters. +Exceptions are a mechanism that makes it possible for code that runs into a problem to _raise_ (or _throw_) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called _((unwinding the stack))_. You may remember the stack of function calls mentioned in [Chapter ?](functions#stack). An exception zooms down this stack, throwing away all the call contexts it encounters. {{index "error handling", [syntax, statement], "catch keyword"}} -If exceptions always zoomed right down to the bottom of the stack, -they would not be of much use. They'd just provide a novel way to blow -up your program. Their power lies in the fact that you can set -"obstacles" along the stack to _catch_ the exception as it is zooming -down. Once you've caught an exception, you can do something with it to -address the problem and then continue to run the program. +If exceptions always zoomed right down to the bottom of the stack, they would not be of much use. They'd just provide a novel way to blow up your program. Their power lies in the fact that you can set "obstacles" along the stack to _catch_ the exception as it is zooming down. Once you've caught an exception, you can do something with it to address the problem and then continue to run the program. Here's an example: @@ -482,33 +315,15 @@ try { {{index "exception handling", block, "throw keyword", "try keyword", "catch keyword"}} -The `throw` keyword is used to raise an exception. Catching one is -done by wrapping a piece of code in a `try` block, followed by the -keyword `catch`. When the code in the `try` block causes an exception -to be raised, the `catch` block is evaluated, with the name in -parentheses bound to the exception value. After the `catch` block -finishes—or if the `try` block finishes without problems—the program -proceeds beneath the entire `try/catch` statement. +The `throw` keyword is used to raise an exception. Catching one is done by wrapping a piece of code in a `try` block, followed by the keyword `catch`. When the code in the `try` block causes an exception to be raised, the `catch` block is evaluated, with the name in parentheses bound to the exception value. After the `catch` block finishes—or if the `try` block finishes without problems—the program proceeds beneath the entire `try/catch` statement. {{index debugging, "call stack", "Error type"}} -In this case, we used the `Error` ((constructor)) to create our -exception value. This is a ((standard)) JavaScript constructor that -creates an object with a `message` property. In most JavaScript -environments, instances of this constructor also gather information -about the call stack that existed when the exception was created, a -so-called _((stack trace))_. This information is stored in the `stack` -property and can be helpful when trying to debug a problem: it tells -us the function where the problem occurred and which functions made -the failing call. +In this case, we used the `Error` ((constructor)) to create our exception value. This is a ((standard)) JavaScript constructor that creates an object with a `message` property. Instances of `Error` also gather information about the call stack that existed when the exception was created, a so-called _((stack trace))_. This information is stored in the `stack` property and can be helpful when trying to debug a problem: it tells us the function where the problem occurred and which functions made the failing call. {{index "exception handling"}} -Note that the `look` function completely ignores the possibility that -`promptDirection` might go wrong. This is the big advantage of -exceptions: error-handling code is necessary only at the point where -the error occurs and at the point where it is handled. The functions -in between can forget all about it. +Note that the `look` function completely ignores the possibility that `promptDirection` might go wrong. This is the big advantage of exceptions: error-handling code is necessary only at the point where the error occurs and at the point where it is handled. The functions in between can forget all about it. Well, almost... @@ -516,18 +331,13 @@ Well, almost... {{index "exception handling", "cleaning up", ["control flow", exceptions]}} -The effect of an exception is another kind of control flow. Every -action that might cause an exception, which is pretty much every -function call and property access, might cause control to suddenly -leave your code. +The effect of an exception is another kind of control flow. Every action that might cause an exception, which is pretty much every function call and property access, might cause control to suddenly leave your code. -This means when code has several side effects, even if its -"regular" control flow looks like they'll always all happen, an -exception might prevent some of them from taking place. +This means when code has several side effects, even if its "regular" control flow looks like they'll always all happen, an exception might prevent some of them from taking place. {{index "banking example"}} -Here is some really bad banking code. +Here is some really bad banking code: ```{includeCode: true} const accounts = { @@ -538,7 +348,7 @@ const accounts = { function getAccount() { let accountName = prompt("Enter an account name"); - if (!accounts.hasOwnProperty(accountName)) { + if (!Object.hasOwn(accounts, accountName)) { throw new Error(`No such account: ${accountName}`); } return accountName; @@ -551,34 +361,17 @@ function transfer(from, amount) { } ``` -The `transfer` function transfers a sum of money from a given account -to another, asking for the name of the other account in the process. -If given an invalid account name, `getAccount` throws an exception. +The `transfer` function transfers a sum of money from a given account to another, asking for the name of the other account in the process. If given an invalid account name, `getAccount` throws an exception. -But `transfer` _first_ removes the money from the account and _then_ -calls `getAccount` before it adds it to another account. If it is -broken off by an exception at that point, it'll just make the money -disappear. +But `transfer` _first_ removes the money from the account and _then_ calls `getAccount` before it adds it to another account. If it is broken off by an exception at that point, it'll just make the money disappear. -That code could have been written a little more intelligently, for -example by calling `getAccount` before it starts moving money around. -But often problems like this occur in more subtle ways. Even functions -that don't look like they will throw an exception might do so in -exceptional circumstances or when they contain a programmer mistake. +That code could have been written a little more intelligently, for example by calling `getAccount` before it starts moving money around. But often problems like this occur in more subtle ways. Even functions that don't look like they will throw an exception might do so in exceptional circumstances or when they contain a programmer mistake. -One way to address this is to use fewer side effects. Again, a -programming style that computes new values instead of changing -existing data helps. If a piece of code stops running in the middle of -creating a new value, no one ever sees the half-finished value, and -there is no problem. +One way to address this is to use fewer side effects. Again, a programming style that computes new values instead of changing existing data helps. If a piece of code stops running in the middle of creating a new value, no existing data structures were damaged, making it easier to recover. {{index block, "try keyword", "finally keyword"}} -But that isn't always practical. So there is another feature that -`try` statements have. They may be followed by a `finally` block -either instead of or in addition to a `catch` block. A `finally` block -says "no matter _what_ happens, run this code after trying to run the -code in the `try` block." +Since that isn't always practical, `try` statements have another feature: they may be followed by a `finally` block either instead of or in addition to a `catch` block. A `finally` block says "no matter _what_ happens, run this code after trying to run the code in the `try` block." ```{includeCode: true} function transfer(from, amount) { @@ -597,76 +390,43 @@ function transfer(from, amount) { } ``` -This version of the function tracks its progress, and if, when -leaving, it notices that it was aborted at a point where it had -created an inconsistent program state, it repairs the damage it did. +This version of the function tracks its progress, and if, when leaving, it notices that it was aborted at a point where it had created an inconsistent program state, it repairs the damage it did. -Note that even though the `finally` code is run when an exception -is thrown in the `try` block, it does not interfere with the exception. -After the `finally` block runs, the stack continues unwinding. +Note that even though the `finally` code is run when an exception is thrown in the `try` block, it does not interfere with the exception. After the `finally` block runs, the stack continues unwinding. {{index "exception safety"}} -Writing programs that operate reliably even when exceptions pop up in -unexpected places is hard. Many people simply don't bother, and -because exceptions are typically reserved for exceptional -circumstances, the problem may occur so rarely that it is never even -noticed. Whether that is a good thing or a really bad thing depends on -how much damage the software will do when it fails. +Writing programs that operate reliably even when exceptions pop up in unexpected places is hard. Many people simply don't bother, and because exceptions are typically reserved for exceptional circumstances, the problem may occur so rarely that it is never even noticed. Whether that is a good thing or a really bad thing depends on how much damage the software will do when it fails. ## Selective catching {{index "uncaught exception", "exception handling", "JavaScript console", "developer tools", "call stack", error}} -When an exception makes it all the way to the bottom of the stack -without being caught, it gets handled by the environment. What this -means differs between environments. In browsers, a description of the -error typically gets written to the JavaScript console (reachable -through the browser's Tools or Developer menu). Node.js, the -browserless JavaScript environment we will discuss in [Chapter -?](node), is more careful about data corruption. It aborts the whole -process when an unhandled exception occurs. +When an exception makes it all the way to the bottom of the stack without being caught, it gets handled by the environment. What this means differs between environments. In browsers, a description of the error typically gets written to the JavaScript console (reachable through the browser's Tools or Developer menu). Node.js, the browserless JavaScript environment we will discuss in [Chapter ?](node), is more careful about data corruption. It aborts the whole process when an unhandled exception occurs. {{index crash, "error handling"}} -For programmer mistakes, just letting the error go through is often -the best you can do. An unhandled exception is a reasonable way to -signal a broken program, and the JavaScript console will, on modern -browsers, provide you with some information about which function calls -were on the stack when the problem occurred. +For programmer mistakes, just letting the error go through is often the best you can do. An unhandled exception is a reasonable way to signal a broken program, and the JavaScript console will, on modern browsers, provide you with some information about which function calls were on the stack when the problem occurred. {{index "user interface"}} -For problems that are _expected_ to happen during routine use, -crashing with an unhandled exception is a terrible strategy. +For problems that are _expected_ to happen during routine use, crashing with an unhandled exception is a terrible strategy. {{index [function, application], "exception handling", "Error type", [binding, undefined]}} -Invalid uses of the language, such as referencing a nonexistent -binding, looking up a property on `null`, or calling something -that's not a function, will also result in exceptions being raised. -Such exceptions can also be caught. +Invalid uses of the language, such as referencing a nonexistent binding, looking up a property on `null`, or calling something that's not a function, will also result in exceptions being raised. Such exceptions can also be caught. {{index "catch keyword"}} -When a `catch` body is entered, all we know is that _something_ in our -`try` body caused an exception. But we don't know _what_ did or _which_ -exception it caused. +When a `catch` body is entered, all we know is that _something_ in our `try` body caused an exception. But we don't know _what_ did or _which_ exception it caused. {{index "exception handling"}} -JavaScript (in a rather glaring omission) doesn't provide direct -support for selectively catching exceptions: either you catch them all -or you don't catch any. This makes it tempting to _assume_ that the -exception you get is the one you were thinking about when you wrote -the `catch` block. +JavaScript (in a rather glaring omission) doesn't provide direct support for selectively catching exceptions: either you catch them all or you don't catch any. This makes it tempting to _assume_ that the exception you get is the one you were thinking about when you wrote the `catch` block. {{index "promptDirection function"}} -But it might not be. Some other ((assumption)) might be violated, or -you might have introduced a bug that is causing an exception. Here is -an example that _attempts_ to keep on calling `promptDirection` -until it gets a valid answer: +But it might not be. Some other ((assumption)) might be violated, or you might have introduced a bug that is causing an exception. Here is an example that _attempts_ to keep on calling `promptDirection` until it gets a valid answer: ```{test: no} for (;;) { @@ -682,37 +442,19 @@ for (;;) { {{index "infinite loop", "for loop", "catch keyword", debugging}} -The `for (;;)` construct is a way to intentionally create a loop that -doesn't terminate on its own. We break out of the loop only when a -valid direction is given. _But_ we misspelled `promptDirection`, which -will result in an "undefined variable" error. Because the `catch` -block completely ignores its exception value (`e`), assuming it knows -what the problem is, it wrongly treats the binding error as indicating -bad input. Not only does this cause an infinite loop, it -"buries" the useful error message about the misspelled binding. +The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. Unfortunately, we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop 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. +As a general rule, don't blanket-catch exceptions unless it is for the purpose of "routing" them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information. {{index "exception handling"}} -So we want to catch a _specific_ kind of exception. We can do this by -checking in the `catch` block whether the exception we got is the one -we are interested in and rethrowing it otherwise. But how do we -recognize an exception? +We want to catch a _specific_ kind of exception. We can do this by checking in the `catch` block whether the exception we got is the one we are interested in, and if not, rethrow it. But how do we recognize an exception? -We could compare its `message` property against the ((error)) message -we happen to expect. But that's a shaky way to write code—we'd be -using information that's intended for human consumption (the message) -to make a programmatic decision. As soon as someone changes (or -translates) the message, the code will stop working. +We could compare its `message` property against the ((error)) message we happen to expect. But that's a shaky way to write code—we'd be using information that's intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working. {{index "Error type", "instanceof operator", "promptDirection function"}} -Rather, let's define a new type of error and use `instanceof` to -identify it. +Rather, let's define a new type of error and use `instanceof` to identify it. ```{includeCode: true} class InputError extends Error {} @@ -727,12 +469,7 @@ function promptDirection(question) { {{index "throw keyword", inheritance}} -The new error class extends `Error`. It doesn't define its own -constructor, which means that it inherits the `Error` constructor, -which expects a string message as argument. In fact, it doesn't define -anything at all—the class is empty. `InputError` objects behave like -`Error` objects, except that they have a different class by which we -can recognize them. +The new error class extends `Error`. It doesn't define its own constructor, which means that it inherits the `Error` constructor, which expects a string message as argument. In fact, it doesn't define anything at all—the class is empty. `InputError` objects behave like `Error` objects, except that they have a different class by which we can recognize them. {{index "exception handling"}} @@ -756,20 +493,15 @@ for (;;) { {{index debugging}} -This will catch only instances of `InputError` and let unrelated -exceptions through. If you reintroduce the typo, the undefined binding -error will be properly reported. +This will catch only instances of `InputError` and let unrelated exceptions through. If you reintroduce the typo, the undefined binding error will be properly reported. ## Assertions {{index "assert function", assertion, debugging}} -_Assertions_ are checks inside a program that verify that something is -the way it is supposed to be. They are used not to handle situations -that can come up in normal operation but to find programmer mistakes. +_Assertions_ are checks inside a program that verify that something is the way it is supposed to be. They are used not to handle situations that can come up in normal operation but to find programmer mistakes. -If, for example, `firstElement` is described as a function that should -never be called on empty arrays, we might write it like this: +If, for example, `firstElement` is described as a function that should never be called on empty arrays, we might write it like this: ``` function firstElement(array) { @@ -782,36 +514,17 @@ function firstElement(array) { {{index validation, "run-time error", crash, assumption}} -Now, instead of silently returning undefined (which you get when -reading an array property that does not exist), this will loudly blow -up your program as soon as you misuse it. This makes it less likely -for such mistakes to go unnoticed and easier to find their cause when -they occur. +Now, instead of silently returning undefined (which you get when reading an array property that does not exist), this will loudly blow up your program as soon as you misuse it. This makes it less likely for such mistakes to go unnoticed and easier to find their cause when they occur. -I do not recommend trying to write assertions for every possible kind -of bad input. That'd be a lot of work and would lead to very noisy -code. You'll want to reserve them for mistakes that are easy to make -(or that you find yourself making). +I do not recommend trying to write assertions for every possible kind of bad input. That'd be a lot of work and would lead to very noisy code. You'll want to reserve them for mistakes that are easy to make (or that you find yourself making). ## Summary -Mistakes and bad input are facts of life. An important part of -programming is finding, diagnosing, and fixing bugs. Problems can -become easier to notice if you have an automated test suite or add -assertions to your programs. +An important part of programming is finding, diagnosing, and fixing bugs. Problems can become easier to notice if you have an automated test suite or add assertions to your programs. -Problems caused by factors outside the program's control should -usually be handled gracefully. Sometimes, when the problem can be -handled locally, special return values are a good way to track them. -Otherwise, exceptions may be preferable. +Problems caused by factors outside the program's control should usually be actively planned for. Sometimes, when the problem can be handled locally, special return values are a good way to track them. Otherwise, exceptions may be preferable. -Throwing an exception causes the call stack to be unwound until the -next enclosing `try/catch` block or until the bottom of the stack. The -exception value will be given to the `catch` block that catches it, -which should verify that it is actually the expected kind of exception -and then do something with it. To help address the unpredictable -control flow caused by exceptions, `finally` blocks can be used to -ensure that a piece of code _always_ runs when a block finishes. +Throwing an exception causes the call stack to be unwound until the next enclosing `try/catch` block or until the bottom of the stack. The exception value will be given to the `catch` block that catches it, which should verify that it is actually the expected kind of exception and then do something with it. To help address the unpredictable control flow caused by exceptions, `finally` blocks can be used to ensure that a piece of code _always_ runs when a block finishes. ## Exercises @@ -819,11 +532,7 @@ ensure that a piece of code _always_ runs when a block finishes. {{index "primitiveMultiply (exercise)", "exception handling", "throw keyword"}} -Say you have a function `primitiveMultiply` that in 20 percent of -cases multiplies two numbers and in the other 80 percent of cases raises an -exception of type `MultiplicatorUnitFailure`. Write a function that -wraps this clunky function and just keeps trying until a call -succeeds, after which it returns the result. +Say you have a function `primitiveMultiply` that in 20 percent of cases multiplies two numbers and in the other 80 percent of cases raises an exception of type `MultiplicatorUnitFailure`. Write a function that wraps this clunky function and just keeps trying until a call succeeds, after which it returns the result. {{index "catch keyword"}} @@ -855,16 +564,9 @@ if}} {{index "primitiveMultiply (exercise)", "try keyword", "catch keyword", "throw keyword"}} -The call to `primitiveMultiply` should definitely happen in a `try` -block. The corresponding `catch` block should rethrow the exception -when it is not an instance of `MultiplicatorUnitFailure` and ensure -the call is retried when it is. +The call to `primitiveMultiply` should definitely happen in a `try` block. The corresponding `catch` block should rethrow the exception when it is not an instance of `MultiplicatorUnitFailure` and ensure the call is retried when it is. -To do the retrying, you can either use a loop that stops only when a -call succeeds—as in the [`look` example](error#look) earlier in this -chapter—or use ((recursion)) and hope you don't get a string of -failures so long that it overflows the stack (which is a pretty safe -bet). +To do the retrying, you can either use a loop that stops only when a call succeeds—as in the [`look` example](error#look) earlier in this chapter—or use ((recursion)) and hope you don't get a string of failures so long that it overflows the stack (which is a pretty safe bet). hint}} @@ -875,42 +577,39 @@ hint}} Consider the following (rather contrived) object: ``` -const box = { - locked: true, - unlock() { this.locked = false; }, - lock() { this.locked = true; }, - _content: [], +const box = new class { + locked = true; + #content = []; + + unlock() { this.locked = false; } + lock() { this.locked = true; } get content() { if (this.locked) throw new Error("Locked!"); - return this._content; + return this.#content; } }; ``` {{index "private property", "access control"}} -It is a ((box)) with a lock. There is an array in the box, but you can -get at it only when the box is unlocked. Directly accessing the -private `_content` property is forbidden. +It is a ((box)) with a lock. There is an array in the box, but you can get at it only when the box is unlocked. {{index "finally keyword", "exception handling"}} -Write a function called `withBoxUnlocked` that takes a function value -as argument, unlocks the box, runs the function, and then ensures that -the box is locked again before returning, regardless of whether the -argument function returned normally or threw an exception. +Write a function called `withBoxUnlocked` that takes a function value as argument, unlocks the box, runs the function, and then ensures that the box is locked again before returning, regardless of whether the argument function returned normally or threw an exception. {{if interactive ``` -const box = { - locked: true, - unlock() { this.locked = false; }, - lock() { this.locked = true; }, - _content: [], +const box = new class { + locked = true; + #content = []; + + unlock() { this.locked = false; } + lock() { this.locked = true; } get content() { if (this.locked) throw new Error("Locked!"); - return this._content; + return this.#content; } }; @@ -918,12 +617,12 @@ function withBoxUnlocked(body) { // Your code here. } -withBoxUnlocked(function() { +withBoxUnlocked(() => { box.content.push("gold piece"); }); try { - withBoxUnlocked(function() { + withBoxUnlocked(() => { throw new Error("Pirates on the horizon! Abort!"); }); } catch (e) { @@ -935,19 +634,14 @@ console.log(box.locked); if}} -For extra points, make sure that if you call `withBoxUnlocked` when -the box is already unlocked, the box stays unlocked. +For extra points, make sure that if you call `withBoxUnlocked` when the box is already unlocked, the box stays unlocked. {{hint {{index "locked box (exercise)", "finally keyword", "try keyword"}} -This exercise calls for a `finally` block. Your function should first -unlock the box and then call the argument function from inside a `try` -body. The `finally` block after it should lock the box again. +This exercise calls for a `finally` block. Your function should first unlock the box and then call the argument function from inside a `try` body. The `finally` block after it should lock the box again. -To make sure we don't lock the box when it wasn't already locked, -check its lock at the start of the function and unlock and lock -it only when it started out locked. +To make sure we don't lock the box when it wasn't already locked, check its lock at the start of the function and unlock and lock it only when it started out locked. hint}} diff --git a/09_regexp.md b/09_regexp.md index 75307dc9e..02eae5bbb 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -2,8 +2,7 @@ {{quote {author: "Jamie Zawinski", chapter: true} -Some people, when confronted with a problem, think 'I know, I'll use -regular expressions.' Now they have two problems. +Some people, when confronted with a problem, think 'I know, I'll use regular expressions.' Now they have two problems. quote}} @@ -13,85 +12,56 @@ quote}} {{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} -Yuan-Ma said, 'When you cut against the grain of the wood, much -strength is needed. When you program against the grain of the problem, -much code is needed.' +When you cut against the grain of the wood, much strength is needed. When you program against the grain of the problem, much code is needed. quote}} if}} -{{figure {url: "img/chapter_picture_9.jpg", alt: "A railroad diagram", chapter: "square-framed"}}} +{{figure {url: "img/chapter_picture_9.jpg", alt: "Illustration of a railroad system representing the syntactic structure of regular expressions", chapter: "square-framed"}}} {{index evolution, adoption, integration}} -Programming ((tool))s and techniques survive and spread in a chaotic, -evolutionary way. It's not always the pretty or brilliant ones that -win but rather the ones that function well enough within the right -niche or that happen to be integrated with another successful piece of -technology. +Programming ((tool))s and techniques survive and spread in a chaotic, evolutionary way. It's not always the best or most brilliant ones that win, but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology. {{index "domain-specific language"}} -In this chapter, I will discuss one such tool, _((regular -expression))s_. Regular expressions are a way to describe ((pattern))s -in string data. They form a small, separate language that is part of -JavaScript and many other languages and systems. +In this chapter, I will discuss one such tool, _((regular expression))s_. Regular expressions are a way to describe ((pattern))s in string data. They form a small, separate language that is part of JavaScript and many other languages and systems. {{index [interface, design]}} -Regular expressions are both terribly awkward and extremely useful. -Their syntax is cryptic, and the programming interface JavaScript -provides for them is clumsy. But they are a powerful ((tool)) for -inspecting and processing strings. Properly understanding regular -expressions will make you a more effective programmer. +Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic and the programming interface JavaScript provides for them is clumsy. But they are a powerful ((tool)) for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer. ## Creating a regular expression {{index ["regular expression", creation], "RegExp class", "literal expression", "slash character"}} -A regular expression is a type of object. It can be either constructed -with the `RegExp` constructor or written as a literal value by -enclosing a pattern in forward slash (`/`) characters. +A regular expression is a type of object. It can be either constructed with the `RegExp` constructor or written as a literal value by enclosing a pattern in forward slash (`/`) characters. ``` let re1 = new RegExp("abc"); let re2 = /abc/; ``` -Both of those regular expression objects represent the same -((pattern)): an _a_ character followed by a _b_ followed by a _c_. +Both of those regular expression objects represent the same ((pattern)): an _a_ character followed by a _b_ followed by a _c_. {{index ["backslash character", "in regular expressions"], "RegExp class"}} -When using the `RegExp` constructor, the pattern is written as a -normal string, so the usual rules apply for backslashes. +When using the `RegExp` constructor, the pattern is written as a normal string, so the usual rules apply for backslashes. {{index ["regular expression", escaping], [escaping, "in regexps"], "slash character"}} -The second notation, where the pattern appears between slash -characters, treats backslashes somewhat differently. First, since a -forward slash ends the pattern, we need to put a backslash before any -forward slash that we want to be _part_ of the pattern. In addition, -backslashes that aren't part of special character codes (like `\n`) -will be _preserved_, rather than ignored as they are in strings, and -change the meaning of the pattern. Some characters, such as question -marks and plus signs, have special meanings in regular expressions and -must be preceded by a backslash if they are meant to represent the -character itself. +The second notation, where the pattern appears between slash characters, treats backslashes somewhat differently. First, since a forward slash ends the pattern, we need to put a backslash before any forward slash that we want to be _part_ of the pattern. In addition, backslashes that aren't part of special character codes (like `\n`) will be _preserved_, rather than ignored as they are in strings, and change the meaning of the pattern. Some characters, such as question marks and plus signs, have special meanings in regular expressions and must be preceded by a backslash if they are meant to represent the character itself. ``` -let eighteenPlus = /eighteen\+/; +let aPlus = /A\+/; ``` ## Testing for matches {{index matching, "test method", ["regular expression", methods]}} -Regular expression objects have a number of methods. The simplest one -is `test`. If you pass it a string, it will return a ((Boolean)) -telling you whether the string contains a match of the pattern in the -expression. +Regular expression objects have a number of methods. The simplest one is `test`. If you pass it a string, it will return a ((Boolean)) telling you whether the string contains a match of the pattern in the expression. ``` console.log(/abc/.test("abcde")); @@ -102,22 +72,15 @@ console.log(/abc/.test("abxde")); {{index pattern}} -A ((regular expression)) consisting of only nonspecial characters -simply represents that sequence of characters. If _abc_ occurs -anywhere in the string we are testing against (not just at the start), -`test` will return `true`. +A ((regular expression)) consisting of only nonspecial characters simply represents that sequence of characters. If _abc_ occurs anywhere in the string we are testing against (not just at the start), `test` will return `true`. ## Sets of characters {{index "regular expression", "indexOf method"}} -Finding out whether a string contains _abc_ could just as well be done -with a call to `indexOf`. Regular expressions allow us to express more -complicated ((pattern))s. +Finding out whether a string contains _abc_ could just as well be done with a call to `indexOf`. Regular expressions are useful because they allow us to describe more complicated ((pattern))s. -Say we want to match any ((number)). In a regular expression, putting -a ((set)) of characters between square brackets makes that part of the -expression match any of the characters between the brackets. +Say we want to match any ((number)). In a regular expression, putting a ((set)) of characters between square brackets makes that part of the expression match any of the characters between the brackets. Both of the following expressions match all strings that contain a ((digit)): @@ -130,17 +93,11 @@ console.log(/[0-9]/.test("in 1992")); {{index "hyphen character"}} -Within square brackets, a hyphen (`-`) between two characters can be -used to indicate a ((range)) of characters, where the ordering is -determined by the character's ((Unicode)) number. Characters 0 to 9 -sit right next to each other in this ordering (codes 48 to 57), so -`[0-9]` covers all of them and matches any ((digit)). +Within square brackets, a hyphen (`-`) between two characters can be used to indicate a ((range)) of characters, where the ordering is determined by the character's ((Unicode)) number. Characters 0 to 9 sit right next to each other in this ordering (codes 48 to 57), so `[0-9]` covers all of them and matches any ((digit)). {{index [whitespace, matching], "alphanumeric character", "period character"}} -A number of common character groups have their own -built-in shortcuts. Digits are one of them: `\d` means the same thing -as `[0-9]`. +A number of common character groups have their own built-in shortcuts. Digits are one of them: `\d` means the same thing as `[0-9]`. {{index "newline character", [whitespace, matching]}} @@ -154,8 +111,7 @@ as `[0-9]`. | `\S` | A nonwhitespace character | `.` | Any character except for newline -So you could match a ((date)) and ((time)) format like 01-30-2003 -15:20 with the following expression: +You could match a ((date)) and ((time)) format like 01-30-2003 15:20 with the following expression: ``` let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/; @@ -167,44 +123,72 @@ console.log(dateTime.test("30-jan-2003 15:20")); {{index ["backslash character", "in regular expressions"]}} -That looks completely awful, doesn't it? Half of it is backslashes, -producing a background noise that makes it hard to spot the actual -((pattern)) expressed. We'll see a slightly improved version of this -expression [later](regexp#date_regexp_counted). +That regular expression looks completely awful, doesn't it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual ((pattern)) expressed. We'll see a slightly improved version of this expression [later](regexp#date_regexp_counted). {{index [escaping, "in regexps"], "regular expression", set}} -These backslash codes can also be used inside ((square brackets)). For -example, `[\d.]` means any digit or a period character. But the period -itself, between square brackets, loses its special meaning. The same -goes for other special characters, such as `+`. +These backslash codes can also be used inside ((square brackets)). For example, `[\d.]` means any digit or a period character. The period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as the plus sign (`+`). {{index "square brackets", inversion, "caret character"}} -To _invert_ a set of characters—that is, to express that you want to -match any character _except_ the ones in the set—you can write a caret -(`^`) character after the opening bracket. +To _invert_ a set of characters—that is, to express that you want to match any character _except_ the ones in the set—you can write a caret (`^`) character after the opening bracket. ``` -let notBinary = /[^01]/; -console.log(notBinary.test("1100100010100110")); +let nonBinary = /[^01]/; +console.log(nonBinary.test("1100100010100110")); // → false -console.log(notBinary.test("1100100010200110")); +console.log(nonBinary.test("0111010112101001")); // → true ``` +## International characters + +{{index internationalization, Unicode, ["regular expression", internationalization]}} + +Because of JavaScript's initial simplistic implementation and the fact that this simplistic approach was later set in stone as ((standard)) behavior, JavaScript's regular expressions are rather dumb about characters that do not appear in the English language. For example, as far as JavaScript's regular expressions are concerned, a "((word character))" is only one of the 26 characters in the Latin alphabet (uppercase or lowercase), decimal digits, and, for some reason, the underscore character. Things like _é_ or _β_, which most definitely are word characters, will not match `\w` (and _will_ match uppercase `\W`, the nonword category). + +{{index [whitespace, matching]}} + +By a strange historical accident, `\s` (whitespace) does not have this problem and matches all characters that the Unicode standard considers whitespace, including things like the ((nonbreaking space)) and the ((Mongolian vowel separator)). + +{{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 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 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. + +```{test: never} +console.log(/\p{L}/u.test("α")); +// → true +console.log(/\p{L}/u.test("!")); +// → false +console.log(/\p{Script=Greek}/u.test("α")); +// → true +console.log(/\p{Script=Arabic}/u.test("α")); +// → false +``` + +{{index "Number function"}} + +On the other hand, if you are matching numbers in order to do something with them, you often do want `\d` for digits, since converting arbitrary numeric characters into a JavaScript number is not something that a function like `Number` can do for you. + ## Repeating parts of a pattern {{index ["regular expression", repetition]}} -We now know how to match a single digit. What if we want to match a -whole number—a ((sequence)) of one or more ((digit))s? +We now know how to match a single digit. What if we want to match a whole number—a ((sequence)) of one or more ((digit))s? {{index "plus character", repetition, "+ operator"}} -When you put a plus sign (`+`) after something in a regular -expression, it indicates that the element may be repeated more than -once. Thus, `/\d+/` matches one or more digit characters. +When you put a plus sign (`+`) after something in a regular expression, it indicates that the element may be repeated more than once. Thus, `/\d+/` matches one or more digit characters. ``` console.log(/'\d+'/.test("'123'")); @@ -219,17 +203,11 @@ console.log(/'\d*'/.test("''")); {{index "* operator", asterisk}} -The star (`*`) has a similar meaning but also allows the pattern to -match zero times. Something with a star after it never prevents a -pattern from matching—it'll just match zero instances if it can't find -any suitable text to match. +The star (`*`) has a similar meaning but also allows the pattern to match zero times. Something with a star after it never prevents a pattern from matching—it'll just match zero instances if it can't find any suitable text to match. {{index "British English", "American English", "question mark"}} -A question mark makes a part of a pattern _((optional))_, meaning it -may occur zero times or one time. In the following example, the _u_ -character is allowed to occur, but the pattern also matches when it is -missing. +A question mark (`?`) makes a part of a pattern _((optional))_, meaning it may occur zero times or one time. In the following example, the _u_ character is allowed to occur, but the pattern also matches when it is missing: ``` let neighbor = /neighbou?r/; @@ -241,17 +219,11 @@ console.log(neighbor.test("neighbor")); {{index repetition, [braces, "in regular expression"]}} -To indicate that a pattern should occur a precise number of times, use -braces. Putting `{4}` after an element, for example, requires it -to occur exactly four times. It is also possible to specify a -((range)) this way: `{2,4}` means the element must occur at least -twice and at most four times. +To indicate that a pattern should occur a precise number of times, use braces. Putting `{4}` after an element, for example, requires it to occur exactly four times. It is also possible to specify a ((range)) this way: `{2,4}` means the element must occur at least twice and at most four times. {{id date_regexp_counted}} -Here is another version of the ((date)) and ((time)) pattern that -allows both single- and double-((digit)) days, months, and hours. It -is also slightly easier to decipher. +Here is another version of the ((date)) and ((time)) pattern that allows both single- and double-((digit)) days, months, and hours. It is also slightly easier to decipher. ``` let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/; @@ -259,18 +231,13 @@ console.log(dateTime.test("1-30-2003 8:45")); // → true ``` -You can also specify open-ended ((range))s when using braces -by omitting the number after the comma. So, `{5,}` means five or more -times. +You can also specify open-ended ((range))s when using braces by omitting the number after the comma. For example, `{5,}` means five or more times. ## Grouping subexpressions {{index ["regular expression", grouping], grouping, [parentheses, "in regular expressions"]}} -To use an operator like `*` or `+` on more than one element at a time, -you have to use parentheses. A part of a regular expression that -is enclosed in parentheses counts as a single element as far as the -operators following it are concerned. +To use an operator like `*` or `+` on more than one element at a time, you must use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned. ``` let cartoonCrying = /boo+(hoo+)+/i; @@ -280,25 +247,17 @@ console.log(cartoonCrying.test("Boohoooohoohooo")); {{index crying}} -The first and second `+` characters apply only to the second _o_ in -_boo_ and _hoo_, respectively. The third `+` applies to the whole -group `(hoo+)`, matching one or more sequences like that. +The first and second `+` characters apply only to the second `o` in `boo` and `hoo`, respectively. The third `+` applies to the whole group `(hoo+)`, matching one or more sequences like that. {{index "case sensitivity", capitalization, ["regular expression", flags]}} -The `i` at the end of the expression in the example makes this regular -expression case insensitive, allowing it to match the uppercase _B_ in -the input string, even though the pattern is itself all lowercase. +The `i` at the end of the expression in the example makes this regular expression case insensitive, allowing it to match the uppercase _B_ in the input string, even though the pattern is itself all lowercase. ## Matches and groups {{index ["regular expression", grouping], "exec method", [array, "RegExp match"]}} -The `test` method is the absolute simplest way to match a regular -expression. It tells you only whether it matched and nothing else. -Regular expressions also have an `exec` (execute) method that will -return `null` if no match was found and return an object with -information about the match otherwise. +The `test` method is the absolute simplest way to match a regular expression. It tells you only whether it matched and nothing else. Regular expressions also have an `exec` (execute) method that will return `null` if no match was found and return an object with information about the match otherwise. ``` let match = /\d+/.exec("one two 100"); @@ -310,11 +269,7 @@ console.log(match.index); {{index "index property", [string, indexing]}} -An object returned from `exec` has an `index` property that tells us -_where_ in the string the successful match begins. Other than that, -the object looks like (and in fact is) an array of strings, whose -first element is the string that was matched. In the previous example, -this is the sequence of ((digit))s that we were looking for. +An object returned from `exec` has an `index` property that tells us _where_ in the string the successful match begins. Other than that, the object looks like (and in fact is) an array of strings, whose first element is the string that was matched. In the previous example, this is the sequence of ((digit))s that we were looking for. {{index [string, methods], "match method"}} @@ -327,12 +282,7 @@ console.log("one two 100".match(/\d+/)); {{index grouping, "capture group", "exec method"}} -When the regular expression contains subexpressions grouped with -parentheses, the text that matched those groups will also show up in -the array. The whole match is always the first element. The next -element is the part matched by the first group (the one whose opening -parenthesis comes first in the expression), then the second group, and -so on. +When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on. ``` let quotedText = /'([^']*)'/; @@ -342,10 +292,7 @@ console.log(quotedText.exec("she said 'hello'")); {{index "capture group"}} -When a group does not end up being matched at all (for example, when -followed by a question mark), its position in the output array will -hold `undefined`. Similarly, when a group is matched multiple times, -only the last match ends up in the array. +When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold `undefined`. When a group is matched multiple times (for example, when followed by a `+`), only the last match ends up in the array. ``` console.log(/bad(ly)?/.exec("bad")); @@ -354,28 +301,28 @@ console.log(/(\d)+/.exec("123")); // → ["123", "3"] ``` +If you want to use parentheses purely for grouping, without having them show up in the array of matches, you can put `?:` after the opening parenthesis. + +``` +console.log(/(?:na)+/.exec("banana")); +// → ["nana"] +``` + {{index "exec method", ["regular expression", methods], extraction}} -Groups can be useful for extracting parts of a string. If we don't -just want to verify whether a string contains a ((date)) but also -extract it and construct an object that represents it, we can wrap -parentheses around the digit patterns and directly pick the date out -of the result of `exec`. +Groups can be useful for extracting parts of a string. If we don't just want to verify whether a string contains a ((date)) but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of `exec`. -But first we'll take a brief detour, in which we discuss the built-in way to -represent date and ((time)) values in JavaScript. +But first we'll take a brief detour to discuss the built-in way to represent date and ((time)) values in JavaScript. ## The Date class {{index constructor, "Date class"}} -JavaScript has a standard class for representing ((date))s—or, rather, -points in ((time)). It is called `Date`. If you simply create a date -object using `new`, you get the current date and time. +JavaScript has a standard `Date` class for representing ((date))s, or rather, points in ((time)). If you simply create a date object using `new`, you get the current date and time. ```{test: no} console.log(new Date()); -// → Mon Nov 13 2017 16:19:11 GMT+0100 (CET) +// → Fri Feb 02 2024 18:03:06 GMT+0100 (CET) ``` {{index "Date class"}} @@ -391,20 +338,13 @@ console.log(new Date(2009, 11, 9, 12, 59, 59, 999)); {{index "zero-based counting", [interface, design]}} -JavaScript uses a convention where month numbers start at zero (so -December is 11), yet day numbers start at one. This is confusing and -silly. Be careful. +JavaScript uses a convention where month numbers start at zero (so December is 11), yet day numbers start at one. This is confusing and silly. Be careful. -The last four arguments (hours, minutes, seconds, and milliseconds) -are optional and taken to be zero when not given. +The last four arguments (hours, minutes, seconds, and milliseconds) are optional and taken to be zero when not given. -{{index "getTime method"}} +{{index "getTime method", timestamp}} -Timestamps are stored as the number of milliseconds since the start of -1970, in the UTC ((time zone)). This follows a convention set by -"((Unix time))", which was invented around that time. You can use -negative numbers for times before 1970. The `getTime` method on a date -object returns this number. It is big, as you can imagine. +Timestamps are stored as the number of milliseconds since the start of 1970, in the UTC ((time zone)). This follows a convention set by "((Unix time))", which was invented around that time. You can use negative numbers for times before 1970. The `getTime` method on a date object returns this number. It is big, as you can imagine. ``` console.log(new Date(2013, 11, 19).getTime()); @@ -415,22 +355,15 @@ console.log(new Date(1387407600000)); {{index "Date.now function", "Date class"}} -If you give the `Date` constructor a single argument, that argument is -treated as such a millisecond count. You can get the current -millisecond count by creating a new `Date` object and calling -`getTime` on it or by calling the `Date.now` function. +If you give the `Date` constructor a single argument, that argument is treated as such a millisecond count. You can get the current millisecond count by creating a new `Date` object and calling `getTime` on it or by calling the `Date.now` function. {{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"]}} -Putting parentheses around the parts of the expression that we are -interested in, we can now create a date object from a string. +Putting parentheses around the parts of the expression that we are interested in, we can now create a date object from a string. ``` function getDate(string) { @@ -444,129 +377,72 @@ console.log(getDate("1-30-2003")); {{index destructuring, "underscore character"}} -The `_` (underscore) binding is ignored and used only to skip the -full match element in the array returned by `exec`. +The underscore (`_`) binding is ignored and used only to skip the full match element in the array returned by `exec`. -## Word and string boundaries +## Boundaries and look-ahead {{index matching, ["regular expression", boundary]}} -Unfortunately, `getDate` will also happily extract the nonsensical -date 00-1-3000 from the string `"100-1-30000"`. A match may happen -anywhere in the string, so in this case, it'll just start at the -second character and end at the second-to-last character. +Unfortunately, `getDate` will also happily extract a date from the string `"100-1-30000"`. A match may happen anywhere in the string, so in this case, it'll just start at the second character and end at the second-to-last character. {{index boundary, "caret character", "dollar sign"}} -If we want to enforce that the match must span the whole string, we -can add the markers `^` and `$`. The caret matches the start of the -input string, whereas the dollar sign matches the end. So, `/^\d+$/` -matches a string consisting entirely of one or more digits, `/^!/` -matches any string that starts with an exclamation mark, and `/x^/` -does not match any string (there cannot be an _x_ before the start of -the string). +If we want to enforce that the match must span the whole string, we can add the markers `^` and `$`. The caret matches the start of the input string, whereas the dollar sign matches the end. Thus `/^\d+$/` matches a string consisting entirely of one or more digits, `/^!/` matches any string that starts with an exclamation mark, and `/x^/` does not match any string (there cannot be an `x` before the start of the string). {{index "word boundary", "word character"}} -If, on the other hand, we just want to make sure the date starts and -ends on a word boundary, we can use the marker `\b`. A word boundary -can be the start or end of the string or any point in the string that -has a word character (as in `\w`) on one side and a nonword character -on the other. +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. + +{{index "look-ahead"}} + +_Look-ahead_ tests do something similar. They provide a pattern and will make the match fail if the input doesn't match that pattern, but don't actually move the match position forward. They are written between `(?=` and `)`. ``` -console.log(/cat/.test("concatenate")); -// → true -console.log(/\bcat\b/.test("concatenate")); -// → false +console.log(/a(?=e)/.exec("braeburn")); +// → ["a"] +console.log(/a(?! )/.exec("a b")); +// → null ``` -{{index matching}} - -Note that a boundary marker doesn't match an actual character. It just -enforces that the regular expression matches only when a certain -condition holds at the place where it appears in the pattern. +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 {{index branching, ["regular expression", alternatives], "farm example"}} -Say we want to know whether a piece of text contains not only a number -but a number followed by one of the words _pig_, _cow_, or _chicken_, -or any of their plural forms. +Say we want to know whether a piece of text contains not only a number but a number followed by one of the words _pig_, _cow_, or _chicken_, or any of their plural forms. -We could write three regular expressions and test them in turn, but -there is a nicer way. The ((pipe character)) (`|`) denotes a -((choice)) between the pattern to its left and the pattern to its -right. So I can say this: +We could write three regular expressions and test them in turn, but there is a nicer way. The ((pipe character)) (`|`) denotes a ((choice)) between the pattern to its left and the pattern to its right. We can use it in expressions like this: ``` -let animalCount = /\b\d+ (pig|cow|chicken)s?\b/; +let animalCount = /\d+ (pig|cow|chicken)s?/; console.log(animalCount.test("15 pigs")); // → true -console.log(animalCount.test("15 pigchickens")); +console.log(animalCount.test("15 pugs")); // → false ``` {{index [parentheses, "in regular expressions"]}} -Parentheses can be used to limit the part of the pattern that the pipe -operator applies to, and you can put multiple such operators next to -each other to express a choice between more than two alternatives. +Parentheses can be used to limit the part of the pattern to which the pipe operator applies, and you can put multiple such operators next to each other to express a choice between more than two alternatives. ## The mechanics of matching {{index ["regular expression", matching], [matching, algorithm], "search problem"}} -Conceptually, when you use `exec` or `test`, the regular expression -engine looks for a match in your string by trying to match the -expression first from the start of the string, then from the second -character, and so on, until it finds a match or reaches the end of the -string. It'll either return the first match that can be found or fail -to find any match at all. +Conceptually, when you use `exec` or `test`, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on until it finds a match or reaches the end of the string. It'll either return the first match that can be found or fail to find any match at all. {{index ["regular expression", matching], [matching, algorithm]}} -To do the actual matching, the engine treats a regular expression -something like a ((flow diagram)). This is the diagram for the -livestock expression in the previous example: +To do the actual matching, the engine treats a regular expression something like a ((flow diagram)). This is the diagram for the livestock expression in the previous example: -{{figure {url: "img/re_pigchickens.svg", alt: "Visualization of /\\b\\d+ (pig|cow|chicken)s?\\b/"}}} +{{figure {url: "img/re_pigchickens.svg", alt: "Railroad diagram that first passes through a box labeled 'digit', which has a loop going back from after it to before it, and then a box for a space character. After that, the railroad splits in three, going through boxes for 'pig', 'cow', and 'chicken'. After those it rejoins, and goes through a box labeled 's', which, being optional, also has a railroad that passes it by. Finally, the line reaches the accepting state."}}} {{index traversal}} -Our expression matches if we can find a path from the left side of the -diagram to the right side. We keep a current position in the string, -and every time we move through a box, we verify that the part of the -string after our current position matches that box. - -So if we try to match `"the 3 pigs"` from position 4, our progress -through the flow chart would look like this: - - - At position 4, there is a word ((boundary)), so we can move past - the first box. - - - Still at position 4, we find a digit, so we can also move past the - second box. - - - At position 5, one path loops back to before the second (digit) - box, while the other moves forward through the box that holds a - single space character. There is a space here, not a digit, so we - must take the second path. - - - We are now at position 6 (the start of _pigs_) and at the three-way - branch in the diagram. We don't see _cow_ or _chicken_ here, but we - do see _pig_, so we take that branch. - - - At position 9, after the three-way branch, one path skips the _s_ - box and goes straight to the final word boundary, while the other - path matches an _s_. There is an _s_ character here, not a word - boundary, so we go through the _s_ box. - - - We're at position 10 (the end of the string) and can match only a - word ((boundary)). The end of a string counts as a word boundary, - so we go through the last box and have successfully matched this - string. +If we can find a path from the left side of the diagram to the right side, our expression matches. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box. {{id backtracking}} @@ -574,79 +450,39 @@ through the flow chart would look like this: {{index ["regular expression", backtracking], "binary number", "decimal number", "hexadecimal number", "flow diagram", [matching, algorithm], backtracking}} -The regular expression `/\b([01]+b|[\da-f]+h|\d+)\b/` matches either a -binary number followed by a _b_, a hexadecimal number (that is, base -16, with the letters _a_ to _f_ standing for the digits 10 to 15) -followed by an _h_, or a regular decimal number with no suffix -character. This is the corresponding diagram: +The regular expression `/^([01]+b|[\da-f]+h|\d+)$/` matches either a binary number followed by a `b`, a hexadecimal number (that is, base 16, with the letters `a` to `f` standing for the digits 10 to 15) followed by an `h`, or a regular decimal number with no suffix character. This is the corresponding diagram: -{{figure {url: "img/re_number.svg", alt: "Visualization of /\\b([01]+b|\\d+|[\\da-f]+h)\\b/"}}} +{{figure {url: "img/re_number.svg", alt: "Railroad diagram for the regular expression '^([01]+b|\\d+|[\\da-f]+h)$'"}}} {{index branching}} -When matching this expression, it will often happen that the top -(binary) branch is entered even though the input does not actually -contain a binary number. When matching the string `"103"`, for -example, it becomes clear only at the 3 that we are in the wrong -branch. The string _does_ match the expression, just not the branch we -are currently in. +When matching this expression, the top (binary) branch will often be entered even though the input does not actually contain a binary number. When matching the string `"103"`, for example, it becomes clear only at the `3` that we are in the wrong branch. The string _does_ match the expression, just not the branch we are currently in. {{index backtracking, "search problem"}} -So the matcher _backtracks_. When entering a branch, it remembers its -current position (in this case, at the start of the string, just past -the first boundary box in the diagram) so that it can go back and try -another branch if the current one does not work out. For the string -`"103"`, after encountering the 3 character, it will start trying the -branch for hexadecimal numbers, which fails again because there is no -_h_ after the number. So it tries the decimal number branch. This one -fits, and a match is reported after all. +So the matcher _backtracks_. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string `"103"`, after encountering the `3` character, the matcher starts trying the branch for hexadecimal numbers, which fails again because there is no `h` after the number. It then tries the decimal number branch. This one fits, and a match is reported after all. {{index [matching, algorithm]}} -The matcher stops as soon as it finds a full match. This means that if -multiple branches could potentially match a string, only the first one -(ordered by where the branches appear in the regular expression) is -used. - -Backtracking also happens for ((repetition)) operators like + and `*`. -If you match `/^.*x/` against `"abcxe"`, the `.*` part will first try -to consume the whole string. The engine will then realize that it -needs an _x_ to match the pattern. Since there is no _x_ past the end -of the string, the star operator tries to match one character less. -But the matcher doesn't find an _x_ after `abcx` either, so it -backtracks again, matching the star operator to just `abc`. _Now_ it -finds an _x_ where it needs it and reports a successful match from -positions 0 to 4. +The matcher stops as soon as it finds a full match. This means that if multiple branches could potentially match a string, only the first one (ordered by where the branches appear in the regular expression) is used. + +Backtracking also happens for ((repetition)) operators like + and `*`. If you match `/^.*x/` against `"abcxe"`, the `.*` part will first try to consume the whole string. The engine will then realize that it needs an `x` to match the pattern. Since there is no `x` past the end of the string, the star operator tries to match one character less. But the matcher doesn't find an `x` after `abcx` either, so it backtracks again, matching the star operator to just `abc`. _Now_ it finds an `x` where it needs it and reports a successful match from positions 0 to 4. {{index performance, complexity}} -It is possible to write regular expressions that will do a _lot_ of -backtracking. This problem occurs when a pattern can match a piece of -input in many different ways. For example, if we get confused while -writing a binary-number regular expression, we might accidentally -write something like `/([01]+)+b/`. +It is possible to write regular expressions that will do a _lot_ of backtracking. This problem occurs when a pattern can match a piece of input in many different ways. For example, if we get confused while writing a binary-number regular expression, we might accidentally write something like `/([01]+)+b/`. -{{figure {url: "img/re_slow.svg", alt: "Visualization of /([01]+)+b/",width: "6cm"}}} +{{figure {url: "img/re_slow.svg", alt: "Railroad diagram for the regular expression '([01]+)+b'",width: "6cm"}}} {{index "inner loop", [nesting, "in regexps"]}} -If that tries to match some long series of zeros and ones with no -trailing _b_ character, the matcher first goes through the inner -loop until it runs out of digits. Then it notices there is no _b_, so -it backtracks one position, goes through the outer loop once, and -gives up again, trying to backtrack out of the inner loop once more. -It will continue to try every possible route through these two loops. -This means the amount of work _doubles_ with each additional -character. For even just a few dozen characters, the resulting match -will take practically forever. +If that tries to match some long series of zeros and ones with no trailing _b_ character, the matcher first goes through the inner loop until it runs out of digits. Then it notices there is no _b_, so it backtracks one position, goes through the outer loop once, and gives up again, trying to backtrack out of the inner loop once more. It will continue to try every possible route through these two loops. This means the amount of work _doubles_ with each additional character. For even just a few dozen characters, the resulting match will take practically forever. ## The replace method {{index "replace method", "regular expression"}} -String values have a `replace` method that can be used to replace -part of the string with another string. +String values have a `replace` method that can be used to replace part of the string with another string. ``` console.log("papa".replace("p", "m")); @@ -655,10 +491,7 @@ console.log("papa".replace("p", "m")); {{index ["regular expression", flags], ["regular expression", global]}} -The first argument can also be a regular expression, in which case the -first match of the regular expression is replaced. When a `g` option -(for _global_) is added to the regular expression, _all_ matches in -the string will be replaced, not just the first. +The first argument can also be a regular expression, in which case the first match of the regular expression is replaced. When a `g` option (for _global_) is added after the regular expression, _all_ matches in the string will be replaced, not just the first. ``` console.log("Borobudur".replace(/[ou]/, "a")); @@ -667,54 +500,26 @@ console.log("Borobudur".replace(/[ou]/g, "a")); // → Barabadar ``` -{{index [interface, design], argument}} - -It would have been sensible if the choice between replacing one match -or all matches was made through an additional argument to `replace` or -by providing a different method, `replaceAll`. But for some -unfortunate reason, the choice relies on a property of the regular -expression instead. - {{index grouping, "capture group", "dollar sign", "replace method", ["regular expression", grouping]}} -The real power of using regular expressions with `replace` comes from -the fact that we can refer to matched groups in the replacement -string. For example, say we have a big string containing the names of -people, one name per line, in the format `Lastname, Firstname`. If we -want to swap these names and remove the comma to get a `Firstname -Lastname` format, we can use the following code: +The real power of using regular expressions with `replace` comes from the fact that we can refer to matched groups in the replacement string. For example, say we have a big string containing the names of people, one name per line, in the format `Lastname, Firstname`. If we want to swap these names and remove the comma to get a `Firstname Lastname` format, we can use the following code: ``` console.log( - "Liskov, Barbara\nMcCarthy, John\nWadler, Philip" - .replace(/(\w+), (\w+)/g, "$2 $1")); + "Liskov, Barbara\nMcCarthy, John\nMilner, Robin" + .replace(/(\p{L}+), (\p{L}+)/gu, "$2 $1")); // → Barbara Liskov // John McCarthy -// Philip Wadler +// Robin Milner ``` -The `$1` and `$2` in the replacement string refer to the parenthesized -groups in the pattern. `$1` is replaced by the text that matched -against the first group, `$2` by the second, and so on, up to `$9`. -The whole match can be referred to with `$&`. +The `$1` and `$2` in the replacement string refer to the parenthesized groups in the pattern. `$1` is replaced by the text that matched against the first group, `$2` by the second, and so on, up to `$9`. The whole match can be referred to with `$&`. {{index [function, "higher-order"], grouping, "capture group"}} -It is possible to pass a function—rather than a string—as the second -argument to `replace`. For each replacement, the function will be -called with the matched groups (as well as the whole match) as -arguments, and its return value will be inserted into the new string. - -Here's a small example: - -``` -let s = "the cia and fbi"; -console.log(s.replace(/\b(fbi|cia)\b/g, - str => str.toUpperCase())); -// → the CIA and FBI -``` +It is possible to pass a function—rather than a string—as the second argument to `replace`. For each replacement, the function will be called with the matched groups (as well as the whole match) as arguments, and its return value will be inserted into the new string. -Here's a more interesting one: +Here's an example: ``` let stock = "1 lemon, 2 cabbages, and 101 eggs"; @@ -727,26 +532,19 @@ function minusOne(match, amount, unit) { } return amount + " " + unit; } -console.log(stock.replace(/(\d+) (\w+)/g, minusOne)); +console.log(stock.replace(/(\d+) (\p{L}+)/gu, minusOne)); // → no lemon, 1 cabbage, and 100 eggs ``` -This takes a string, finds all occurrences of a number followed by an -alphanumeric word, and returns a string wherein every such occurrence -is decremented by one. +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 `(\w+)` group gets bound to `unit`. The function converts -`amount` to a number—which always works since it matched `\d+`—and -makes some adjustments in case there is only one or zero left. +The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works, since it matched `\d+` earlier—and makes some adjustments in case there is only one or zero left. ## Greed {{index greed, "regular expression"}} -It is possible to use `replace` to write a function that removes all -((comment))s from a piece of JavaScript ((code)). Here is a first -attempt: +We can use `replace` to write a function that removes all ((comment))s from a piece of JavaScript ((code)). Here is a first attempt: ```{test: wrap} function stripComments(code) { @@ -762,38 +560,17 @@ console.log(stripComments("1 /* a */+/* b */ 1")); {{index "period character", "slash character", "newline character", "empty set", "block comment", "line comment"}} -The part before the _or_ operator matches two slash characters -followed by any number of non-newline characters. The part for -multiline comments is more involved. We use `[^]` (any character that -is not in the empty set of characters) as a way to match any -character. We cannot just use a period here because block comments can -continue on a new line, and the period character does not match -newline characters. +The part before the `|` operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use `[^]` (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters. But the output for the last line appears to have gone wrong. Why? {{index backtracking, greed, "regular expression"}} -The `[^]*` part of the expression, as I described in the section on -backtracking, will first match as much as it can. If that causes the -next part of the pattern to fail, the matcher moves back one character -and tries again from there. In the example, the matcher first tries to -match the whole rest of the string and then moves back from there. It -will find an occurrence of `*/` after going back four characters and -match that. This is not what we wanted—the intention was to match a -single comment, not to go all the way to the end of the code and find -the end of the last block comment. - -Because of this behavior, we say the repetition operators (`+`, `*`, -`?`, and `{}`) are _((greed))y_, meaning they match as much as they -can and backtrack from there. If you put a ((question mark)) after -them (`+?`, `*?`, `??`, `{}?`), they become nongreedy and start by -matching as little as possible, matching more only when the remaining -pattern does not fit the smaller match. - -And that is exactly what we want in this case. By having the star -match the smallest stretch of characters that brings us to a `*/`, we -consume one block comment and nothing more. +The `[^]*` part of the expression, as I described in the section on backtracking, will first match as much as it can. If that causes the next part of the pattern to fail, the matcher moves back one character and tries again from there. In the example, the matcher first tries to match the whole rest of the string and then moves back from there. It will find an occurrence of `*/` after going back four characters and match that. This is not what we wanted—the intention was to match a single comment, not to go all the way to the end of the code and find the end of the last block comment. + +Because of this behavior, we say the repetition operators (`+`, `*`, `?`, and `{}`) are _((greed))y_, meaning they match as much as they can and backtrack from there. If you put a ((question mark)) after them (`+?`, `*?`, `??`, `{}?`), they become nongreedy and start by matching as little as possible, matching more only when the remaining pattern does not fit the smaller match. + +And that is exactly what we want in this case. By having the star match the smallest stretch of characters that brings us to a `*/`, we consume one block comment and nothing more. ```{test: wrap} function stripComments(code) { @@ -803,67 +580,46 @@ console.log(stripComments("1 /* a */+/* b */ 1")); // → 1 + 1 ``` -A lot of ((bug))s in ((regular expression)) programs can be traced to -unintentionally using a greedy operator where a nongreedy one would -work better. When using a ((repetition)) operator, consider the -nongreedy variant first. +A lot of ((bug))s in ((regular expression)) programs can be traced to unintentionally using a greedy operator where a nongreedy one would work better. When using a ((repetition)) operator, prefer the nongreedy variant. ## Dynamically creating RegExp objects {{index ["regular expression", creation], "underscore character", "RegExp class"}} -There are cases where you might not know the exact ((pattern)) you -need to match against when you are writing your code. Say you want to -look for the user's name in a piece of text and enclose it in -underscore characters to make it stand out. Since you will know the -name only once the program is actually running, you can't use the -slash-based notation. - -But you can build up a string and use the `RegExp` ((constructor)) on -that. Here's an example: +In some cases you may not know the exact ((pattern)) you need to match against when you are writing your code. Say you want to test for the user's name in a piece of text. You can build up a string and use the `RegExp` ((constructor)) on that. ``` let name = "harry"; -let text = "Harry is a suspicious character."; -let regexp = new RegExp("\\b(" + name + ")\\b", "gi"); -console.log(text.replace(regexp, "_$1_")); -// → _Harry_ is a suspicious character. +let regexp = new RegExp("(^|\\s)" + name + "($|\\s)", "gi"); +console.log(regexp.test("Harry is a dodgy character.")); +// → true ``` {{index ["regular expression", flags], ["backslash character", "in regular expressions"]}} -When creating the `\b` ((boundary)) markers, we have to use two -backslashes because we are writing them in a normal string, not a -slash-enclosed regular expression. The second argument to the `RegExp` -constructor contains the options for the regular expression—in this -case, `"gi"` for global and case insensitive. +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. +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. {{index ["backslash character", "in regular expressions"], [escaping, "in regexps"], ["regular expression", escaping]}} -To work around this, we can add backslashes before any character that -has a special meaning. +To work around this, we can add backslashes before any character that has a special meaning. ``` let name = "dea+hl[]rd"; -let text = "This dea+hl[]rd guy is super annoying."; let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&"); -let regexp = new RegExp("\\b" + escaped + "\\b", "gi"); -console.log(text.replace(regexp, "_$&_")); -// → This _dea+hl[]rd_ guy is super annoying. +let regexp = new RegExp("(^|\\s)" + escaped + "($|\\s)", + "gi"); +let text = "This dea+hl[]rd guy is super annoying."; +console.log(regexp.test(text)); +// → true ``` ## The search method {{index ["regular expression", methods], "indexOf method", "search method"}} -The `indexOf` method on strings cannot be called with a regular -expression. But there is another method, `search`, that does expect a -regular expression. Like `indexOf`, it returns the first index on -which the expression was found, or -1 when it wasn't found. +While the `indexOf` method on strings cannot be called with a regular expression, there is another method, `search`, that does expect a regular expression. Like `indexOf`, it returns the first index on which the expression was found, or -1 when it wasn't found. ``` console.log(" word".search(/\S/)); @@ -872,33 +628,21 @@ console.log(" ".search(/\S/)); // → -1 ``` -Unfortunately, there is no way to indicate that the match should start -at a given offset (like we can with the second argument to `indexOf`), -which would often be useful. +Unfortunately, there is no way to indicate that the match should start at a given offset (like we can with the second argument to `indexOf`), which would often be useful. ## The lastIndex property {{index "exec method", "regular expression"}} -The `exec` method similarly does not provide a convenient way to start -searching from a given position in the string. But it does provide an -*in*convenient way. +The `exec` method similarly does not provide a convenient way to start searching from a given position in the string. But it does provide an *in*convenient way. {{index ["regular expression", matching], matching, "source property", "lastIndex property"}} -Regular expression objects have properties. One such property is -`source`, which contains the string that expression was created from. -Another property is `lastIndex`, which controls, in some limited -circumstances, where the next match will start. +Regular expression objects have properties. One such property is `source`, which contains the string that expression was created from. Another property is `lastIndex`, which controls, in some limited circumstances, where the next match will start. {{index [interface, design], "exec method", ["regular expression", global]}} -Those circumstances are that the regular expression must have the -global (`g`) or sticky (`y`) option enabled, and the match must happen -through the `exec` method. Again, a less confusing solution would have -been to just allow an extra argument to be passed to `exec`, but -confusion is an essential feature of JavaScript's regular expression -interface. +Those circumstances are that the regular expression must have the global (`g`) or sticky (`y`) option enabled, and the match must happen through the `exec` method. Again, a less confusing solution would have been to just allow an extra argument to be passed to `exec`, but confusion is an essential feature of JavaScript's regular expression interface. ``` let pattern = /y/g; @@ -912,15 +656,9 @@ 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. +The difference between the global and the sticky options is that when sticky is enabled, the match will succeed only if it starts directly at `lastIndex`, whereas with global, it will search ahead for a position where a match can start. ``` let global = /abc/g; @@ -933,10 +671,7 @@ console.log(sticky.exec("xyz abc")); {{index bug}} -When using a shared regular expression value for multiple `exec` -calls, these automatic updates to the `lastIndex` property can cause -problems. Your regular expression might be accidentally starting at an -index that was left over from a previous call. +When using a shared regular expression value for multiple `exec` calls, these automatic updates to the `lastIndex` property can cause problems. Your regular expression might be accidentally starting at an index left over from a previous call. ``` let digit = /\d/g; @@ -948,35 +683,23 @@ console.log(digit.exec("and now: 1")); {{index ["regular expression", global], "match method"}} -Another interesting effect of the global option is that it changes the -way the `match` method on strings works. When called with a global -expression, instead of returning an array similar to that returned by -`exec`, `match` will find _all_ matches of the pattern in the string -and return an array containing the matched strings. +Another interesting effect of the global option is that it changes the way the `match` method on strings works. When called with a global expression, instead of returning an array similar to that returned by `exec`, `match` will find _all_ matches of the pattern in the string and return an array containing the matched strings. ``` console.log("Banana".match(/an/g)); // → ["an", "an"] ``` -So be cautious with global regular expressions. The cases where they -are necessary—calls to `replace` and places where you want to -explicitly use `lastIndex`—are typically the only places where you -want to use them. - -### Looping over matches +So be cautious with global regular expressions. The cases where they are necessary—calls to `replace` and places where you want to explicitly use `lastIndex`—are typically the situations where you want to use them. {{index "lastIndex property", "exec method", loop}} -A common thing to do is to scan through all occurrences of a pattern -in a string, in a way that gives us access to the match object in the -loop body. We can do this by using `lastIndex` and `exec`. +A common thing to do is to find all the matches of a regular expression in a string. We can do this by using the `matchAll` method. ``` let input = "A string with 3 numbers in it... 42 and 88."; -let number = /\b\d+\b/g; -let match; -while (match = number.exec(input)) { +let matches = input.matchAll(/\d+/g); +for (let match of matches) { console.log("Found", match[0], "at", match.index); } // → Found 3 at 14 @@ -984,27 +707,18 @@ while (match = number.exec(input)) { // Found 88 at 40 ``` -{{index "while loop", ["= operator", "as expression"], [binding, "as state"]}} +{{index ["regular expression", global]}} -This makes use of the fact that the value of an ((assignment)) -expression (`=`) is the assigned value. So by using `match = -number.exec(input)` as the condition in the `while` statement, we -perform the match at the start of each iteration, save its result in a -binding, and stop looping when no more matches are found. +This method returns an array of match arrays. The regular expression given to `matchAll` _must_ have `g` enabled. {{id ini}} ## Parsing an INI file {{index comment, "file format", "enemies example", "INI file"}} -To conclude the chapter, we'll look at a problem that calls for -((regular expression))s. Imagine we are writing a program to -automatically collect information about our enemies from the -((Internet)). (We will not actually write that program here, just the -part that reads the ((configuration)) file. Sorry.) The configuration -file looks like this: +To conclude the chapter, we'll look at a problem that calls for ((regular expression))s. Imagine we are writing a program to automatically collect information about our enemies from the ((internet)). (We will not actually write that program here, just the part that reads the ((configuration)) file. Sorry.) The configuration file looks like this: -```{lang: "text/plain"} +```{lang: "null"} searchengine=https://duckduckgo.com/?q=$1 spitefulness=9.7 @@ -1023,50 +737,37 @@ outputdir=/home/marijn/enemies/davaeorn {{index grammar}} -The exact rules for this format (which is a widely used format, -usually called an _INI_ file) are as follows: +The exact rules for this format—which is a widely used file format, usually called an _INI_ file—are as follows: - Blank lines and lines starting with semicolons are ignored. - Lines wrapped in `[` and `]` start a new ((section)). -- Lines containing an alphanumeric identifier followed by an `=` - character add a setting to the current section. +- Lines containing an alphanumeric identifier followed by an `=` character add a setting to the current section. - Anything else is invalid. -Our task is to convert a string like this into an object whose -properties hold strings for settings written before the first -section header and subobjects for sections, with those subobjects -holding the section's settings. +Our task is to convert a string like this into an object whose properties hold strings for settings written before the first section header and subobjects for sections, with those subobjects holding the section's settings. {{index "carriage return", "line break", "newline character"}} -Since the format has to be processed ((line)) by line, splitting up -the file into separate lines is a good start. We saw -the `split` method in [Chapter ?](data#split). -Some operating systems, however, use not just a newline character to -separate lines but a carriage return character followed by a newline -(`"\r\n"`). Given that the `split` method also allows a regular -expression as its argument, we can use a regular expression like -`/\r?\n/` to split in a way that allows both `"\n"` and `"\r\n"` -between lines. +Since the format has to be processed ((line)) by line, splitting up the file into separate lines is a good start. We saw the `split` method in [Chapter ?](data#split). Some operating systems, however, use not just a newline character to separate lines but a carriage return character followed by a newline (`"\r\n"`). Given that the `split` method also allows a regular expression as its argument, we can use a regular expression like `/\r?\n/` to split in a way that allows both `"\n"` and `"\r\n"` between lines. ```{startCode: true} function parseINI(string) { // Start with an object to hold the top-level fields let result = {}; let section = result; - string.split(/\r?\n/).forEach(line => { + for (let line of string.split(/\r?\n/)) { let match; if (match = line.match(/^(\w+)=(.*)$/)) { section[match[1]] = match[2]; } else if (match = line.match(/^\[(.*)\]$/)) { section = result[match[1]] = {}; - } else if (!/^\s*(;.*)?$/.test(line)) { + } else if (!/^\s*(;|$)/.test(line)) { throw new Error("Line '" + line + "' is not valid."); } - }); + }; return result; } @@ -1079,68 +780,25 @@ city=Tessaloniki`)); {{index "parseINI function", parsing}} -The code goes over the file's lines and builds up an object. -Properties at the top are stored directly into that object, whereas -properties found in sections are stored in a separate section object. -The `section` binding points at the object for the current section. +The code goes over the file's lines and builds up an object. Properties at the top are stored directly into that object, whereas properties found in sections are stored in a separate section object. The `section` binding points at the object for the current section. -There are two kinds of significant lines—section headers or property -lines. When a line is a regular property, it is stored in the current -section. When it is a section header, a new section object is created, -and `section` is set to point at it. +There are two kinds of significant lines—section headers or property lines. When a line is a regular property, it is stored in the current section. When it is a section header, a new section object is created, and `section` is set to point at it. {{index "caret character", "dollar sign", boundary}} -Note the recurring use of `^` and `$` to make sure the expression -matches the whole line, not just part of it. Leaving these out results -in code that mostly works but behaves strangely for some input, which -can be a difficult bug to track down. +Note the recurring use of `^` and `$` to make sure the expression matches the whole line, not just part of it. Leaving these out results in code that mostly works but behaves strangely for some input, which can be a difficult bug to track down. {{index "if keyword", assignment, ["= operator", "as expression"]}} -The pattern `if (match = string.match(...))` is similar to the trick -of using an assignment as the condition for `while`. You often aren't -sure that your call to `match` will succeed, so you can access the -resulting object only inside an `if` statement that tests for this. To -not break the pleasant chain of `else if` forms, we assign the result -of the match to a binding and immediately use that assignment as the -test for the `if` statement. +The pattern `if (match = string.match(...))` makes use of the fact that the value of an ((assignment)) expression (`=`) is the assigned value. You often aren't sure that your call to `match` will succeed, so you can access the resulting object only inside an `if` statement that tests for this. To not break the pleasant chain of `else if` forms, we assign the result of the match to a binding and immediately use that assignment as the test for the `if` statement. {{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*(;.*)?$/`. Do you see how it works? The part between the -parentheses will match comments, and the `?` makes sure it also -matches lines containing only whitespace. When a line doesn't match -any of the expected forms, the function throws an exception. - -## International characters - -{{index internationalization, Unicode, ["regular expression", internationalization]}} - -Because of JavaScript's initial simplistic implementation and the fact -that this simplistic approach was later set in stone as ((standard)) -behavior, JavaScript's regular expressions are rather dumb about -characters that do not appear in the English language. For example, as -far as JavaScript's regular expressions are concerned, a "((word -character))" is only one of the 26 characters in the Latin alphabet -(uppercase or lowercase), decimal digits, and, for some reason, the -underscore character. Things like _é_ or _β_, which most definitely -are word characters, will not match `\w` (and _will_ match uppercase -`\W`, the nonword category). - -{{index [whitespace, matching]}} +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. -By a strange historical accident, `\s` (whitespace) does not have this -problem and matches all characters that the Unicode standard considers -whitespace, including things like the ((nonbreaking space)) and the -((Mongolian vowel separator)). +## Code units and characters -Another problem is that, by default, regular expressions 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("🍎🍎🍎")); @@ -1151,47 +809,20 @@ console.log(/<.>/u.test("<🌹>")); // → true ``` -The problem is that the 🍎 in the first line is treated as two code -units, and the `{3}` part is applied only to the second one. -Similarly, the dot matches a single code unit, not the two that make -up the rose ((emoji)). - -You must add a `u` option (for ((Unicode))) to your regular -expression to make it treat such characters properly. The wrong -behavior remains the default, unfortunately, because changing that -might cause problems for existing code that depends on it. +The problem is that the 🍎 in the first line is treated as two code units, and `{3}` is applied only to the second unit. Similarly, the dot matches a single code unit, not the two that make up the rose ((emoji)). -{{index "character category", [Unicode, property]}} +You must add the `u` (Unicode) option to your regular expression to make it treat such characters properly. -Though this was only just standardized and is, at the time of writing, -not widely supported yet, it is possible to use `\p` in a regular -expression (that must have the Unicode option enabled) to match all -characters to which the Unicode standard assigns a given property. - -```{test: never} -console.log(/\p{Script=Greek}/u.test("α")); -// → true -console.log(/\p{Script=Arabic}/u.test("α")); -// → false -console.log(/\p{Alphabetic}/u.test("α")); +``` +console.log(/🍎{3}/u.test("🍎🍎🍎")); // → true -console.log(/\p{Alphabetic}/u.test("!")); -// → false ``` -Unicode defines a number of useful properties, though finding the one -that you need may not always be trivial. You can use the -`\p{Property=Value}` notation to match any character that has the -given value for that property. If the property name is left off, as in -`\p{Name}`, the name is assumed to be either a binary property such as -`Alphabetic` or a category such as `Number`. - {{id summary_regexp}} ## Summary -Regular expressions are objects that represent patterns in strings. -They use their own language to express these patterns. +Regular expressions are objects that represent patterns in strings. They use their own language to express these patterns. {{table {cols: [1, 5]}}} @@ -1210,64 +841,34 @@ They use their own language to express these patterns. | `/\w/` | An alphanumeric character ("word character") | `/\s/` | Any whitespace character | `/./` | Any character except newlines -| `/\b/` | A word boundary +| `/\p{L}/u` | Any letter character | `/^/` | Start of input | `/$/` | End of input +| `/(?=a)/` | A look-ahead test + +A regular expression has a method `test` to test whether a given string matches it. It also has a method `exec` that, when a match is found, returns an array containing all matched groups. Such an array has an `index` property that indicates where the match started. + +Strings have a `match` method to match them against a regular expression and a `search` method to search for one, returning only the starting position of the match. Their `replace` method can replace matches of a pattern with a replacement string or function. + +Regular expressions can have options, which are written after the closing slash. The `i` option makes the match case insensitive. The `g` option makes the expression _global_, which, among other things, causes the `replace` method to replace all instances instead of just the first. The `y` option makes and expression sticky, which means that it will not search ahead and skip part of the string when looking for a match. The `u` option turns on Unicode mode, which enables `\p` syntax and fixes a number of problems around the handling of characters that take up two code units. -A regular expression has a method `test` to test whether a given -string matches it. It also has a method `exec` that, when a match is -found, returns an array containing all matched groups. Such an array -has an `index` property that indicates where the match started. - -Strings have a `match` method to match them against a regular -expression and a `search` method to search for one, returning only the -starting position of the match. Their `replace` method can replace -matches of a pattern with a replacement string or function. - -Regular expressions can have options, which are written after the -closing slash. The `i` option makes the match case insensitive. The -`g` option makes the expression _global_, which, among other things, -causes the `replace` method to replace all instances instead of just -the first. The `y` option makes it sticky, which means that it will -not search ahead and skip part of the string when looking for a match. -The `u` option turns on Unicode mode, which fixes a number of problems -around the handling of characters that take up two code units. - -Regular expressions are a sharp ((tool)) with an awkward handle. They -simplify some tasks tremendously but can quickly become unmanageable -when applied to complex problems. Part of knowing how to use them is -resisting the urge to try to shoehorn things that they cannot cleanly -express into them. +Regular expressions are a sharp ((tool)) with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things into them that they cannot cleanly express. ## Exercises {{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 -[_https://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 {{index "program size", "code golf", "regexp golf (exercise)"}} -_Code golf_ is a term used for the game of trying to express a -particular program in as few characters as possible. Similarly, -_regexp golf_ is the practice of writing as tiny a regular expression -as possible to match a given pattern, and _only_ that pattern. +_Code golf_ is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, _regexp golf_ is the practice of writing as tiny a regular expression as possible to match a given pattern and _only_ that pattern. {{index boundary, matching}} -For each of the following items, write a ((regular expression)) to -test whether any of the given substrings occur in a string. The -regular expression should match only strings containing one of the -substrings described. Do not worry about word boundaries unless -explicitly mentioned. When your expression works, see whether you can -make it any smaller. +For each of the following items, write a ((regular expression)) to test whether the given pattern occurs in a string. The regular expression should match only strings containing the pattern. When your expression works, see whether you can make it any smaller. 1. _car_ and _cat_ 2. _pop_ and _prop_ @@ -1277,8 +878,7 @@ make it any smaller. 6. A word longer than six letters 7. A word without the letter _e_ (or _E_) -Refer to the table in the [chapter summary](regexp#summary_regexp) for -help. Test each solution with a few test strings. +Refer to the table in the [chapter summary](regexp#summary_regexp) for help. Test each solution with a few test strings. {{if interactive ``` @@ -1310,7 +910,7 @@ verify(/.../, verify(/.../, ["red platypus", "wobbling nest"], - ["earth bed", "learning ape", "BEET"]); + ["earth bed", "bedrøvet abe", "BEET"]); function verify(regexp, yes, no) { @@ -1331,16 +931,11 @@ if}} {{index "quoting style (exercise)", "single-quote character", "double-quote character"}} -Imagine you have written a story and used single ((quotation mark))s -throughout to mark pieces of dialogue. Now you want to replace all the -dialogue quotes with double quotes, while keeping the single quotes -used in contractions like _aren't_. +Imagine you have written a story and used single ((quotation mark))s throughout to mark pieces of dialogue. Now you want to replace all the dialogue quotes with double quotes, while keeping the single quotes used in contractions like _aren't_. {{index "replace method"}} -Think of a pattern that distinguishes these two -kinds of quote usage and craft a call to the `replace` method that -does the proper replacement. +Think of a pattern that distinguishes these two kinds of quote usage and craft a call to the `replace` method that does the proper replacement. {{if interactive ```{test: no} @@ -1355,17 +950,11 @@ if}} {{index "quoting style (exercise)", boundary}} -The most obvious solution is to replace only quotes with a nonword -character on at least one side—something like `/\W'|'\W/`. 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"]}} -In addition, you must ensure that the replacement also includes the -characters that were matched by the `\W` pattern so that those are not -dropped. This can be done by wrapping them in parentheses and -including their groups in the replacement string (`$1`, `$2`). Groups -that are not matched will be replaced by nothing. +In addition, you must ensure that the replacement also includes the characters that were matched by the `\P{L}` pattern so that those are not dropped. This can be done by wrapping them in parentheses and including their groups in the replacement string (`$1`, `$2`). Groups that are not matched will be replaced by nothing. hint}} @@ -1373,13 +962,7 @@ hint}} {{index sign, "fractional number", [syntax, number], minus, "plus character", exponent, "scientific notation", "period character"}} -Write an expression that matches only JavaScript-style ((number))s. It -must support an optional minus _or_ plus sign in front of the number, -the decimal dot, and exponent notation—`5e-3` or `1E10`—again with an -optional sign in front of the exponent. Also note that it is not -necessary for there to be digits in front of or after the dot, but the -number cannot be a dot alone. That is, `.5` and `5.` are valid -JavaScript numbers, but a lone dot _isn't_. +Write an expression that matches only JavaScript-style ((number))s. It must support an optional minus _or_ plus sign in front of the number, the decimal dot, and exponent notation—`5e-3` or `1E10`—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, `.5` and `5.` are valid JavaScript numbers, but a lone dot isn't. {{if interactive ```{test: no} @@ -1409,21 +992,14 @@ if}} First, do not forget the backslash in front of the period. -Matching the optional ((sign)) in front of the ((number)), as well as -in front of the ((exponent)), can be done with `[+\-]?` or `(\+|-|)` -(plus, minus, or nothing). +Matching the optional ((sign)) in front of the ((number)), as well as in front of the ((exponent)), can be done with `[+\-]?` or `(\+|-|)` (plus, minus, or nothing). {{index "pipe character"}} -The more complicated part of the exercise is the problem of matching -both `"5."` and `".5"` without also matching `"."`. For this, a good -solution is to use the `|` operator to separate the two cases—either -one or more digits optionally followed by a dot and zero or more -digits _or_ a dot followed by one or more digits. +The more complicated part of the exercise is the problem of matching both `"5."` and `".5"` without also matching `"."`. For this, a good solution is to use the `|` operator to separate the two cases—either one or more digits optionally followed by a dot and zero or more digits _or_ a dot followed by one or more digits. {{index exponent, "case sensitivity", ["regular expression", flags]}} -Finally, to make the _e_ case insensitive, either add an `i` option to -the regular expression or use `[eE]`. +Finally, to make the _e_ case insensitive, either add an `i` option to the regular expression or use `[eE]`. hint}} diff --git a/10_modules.md b/10_modules.md index 530a29d36..0f253a7e7 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. @@ -10,185 +10,183 @@ quote}} {{index "Yuan-Ma", "Book of Programming"}} -{{figure {url: "img/chapter_picture_10.jpg", alt: "Picture of a building built from modular pieces", chapter: framed}}} +{{figure {url: "img/chapter_picture_10.jpg", alt: "Illustration of a complicated building built from modular pieces", chapter: framed}}} {{index organization, [code, "structure of"]}} -The ideal program has a crystal-clear structure. The way it works is -easy to explain, and each part plays a well-defined role. +Ideally, a program has a clear, straightforward structure. The way it works is easy to explain, and each part plays a well-defined role. {{index "organic growth"}} -A typical real program grows organically. New pieces of functionality -are added as new needs come up. Structuring—and preserving -structure—is additional work. It's work that will pay off only in the -future, the _next_ time someone works on the program. So it is -tempting to neglect it and allow the 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 such a 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. +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 your hands get -dirty. +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. -## Modules +## Modular programs {{index dependency, [interface, module]}} -_Modules_ are an attempt to avoid these problems. A ((module)) is a -piece of program that specifies which other pieces it relies on -and which functionality it provides for other modules -to use (its _interface_). +_Modules_ are an attempt to avoid these problems. A ((module)) is a piece of program that specifies which other pieces it relies on and which functionality it provides for other modules to use (its _interface_). {{index "big ball of mud"}} -Module interfaces have a lot in common with object interfaces, as we -saw them in [Chapter ?](object#interface). They make part of the -module available to the outside world and keep the rest private. By -restricting the ways in which modules interact with each other, the -system becomes more like ((LEGO)), where pieces interact through -well-defined connectors, and less like mud, where everything mixes -with everything. +Module interfaces have a lot in common with object interfaces, as we saw them in [Chapter ?](object#interface). They make part of the module available to the outside world and keep the rest private. {{index dependency}} -The relations between modules are called _dependencies_. When a module -needs a piece from another module, it is said to depend on that -module. When this fact is clearly specified in the module itself, it -can be used to figure out which other modules need to be present to be -able to use a given module and to automatically load dependencies. +But the interface that a module provides for others to use is only half the story. A good module system also requires modules to specify which code _they_ use from other modules. These relations are called _dependencies_. If module A uses functionality from module B, it is said to _depend_ on that module. When these are clearly specified in the module itself, they can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies. -To separate modules in that way, each needs its own private ((scope)). +When the ways in which modules interact with each other are explicit, a system becomes more like ((LEGO)), where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything else. -Just putting your JavaScript code into different ((file))s does not -satisfy these requirements. The files still share the same global -namespace. They can, intentionally or accidentally, interfere with -each other's bindings. And the dependency structure remains unclear. -We can do better, as we'll see later in the chapter. +{{id es}} -{{index design}} +## ES modules + +{{index "global scope", [binding, global]}} + +The original JavaScript language did not have any concept of a module. All scripts ran in the same scope, and accessing a function defined in another script was done by referencing the global bindings created by that script. This actively encouraged accidental, hard-to-see entanglement of code and invited problems like unrelated scripts trying to use the same binding name. + +{{index "ES modules"}} + +Since ECMAScript 2015, JavaScript supports two different types of programs. _Scripts_ behave in the old way: their bindings are defined in the global scope, and they have no way to directly reference other scripts. _Modules_ get their own separate scope and support the `import` and `export` keywords, which aren't available in scripts, to declare their dependencies and interface. This module system is usually called _ES modules_ (where _ES_ stands for ECMAScript). + +A modular program is composed of a number of such modules, wired together via their imports and exports. + +{{index "Date class", "weekDay module"}} + +The following example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant that is not part of its interface, and two functions that are. It has no dependencies. + +``` +const names = ["Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"]; + +export function dayName(number) { + return names[number]; +} +export function dayNumber(name) { + return names.indexOf(name); +} +``` + +The `export` keyword can be put in front of a function, class, or binding definition to indicate that that binding is part of the module's interface. This makes it possible for other modules to use that binding by importing it. + +```{test: no} +import {dayName} from "./dayname.js"; +let now = new Date(); +console.log(`Today is ${dayName(now.getDay())}`); +// → Today is Monday +``` + +{{index "import keyword", dependency, "ES modules"}} + +The `import` keyword, followed by a list of binding names in braces, makes bindings from another module available in the current module. Modules are identified by quoted strings. + +{{index [module, resolution], resolution}} -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 it 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. +How such a module name is resolved to an actual program differs by platform. The browser treats them as web addresses, whereas Node.js resolves them to files. When you run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. + +Import and export declarations cannot appear inside of functions, loops, or other blocks. They are immediately resolved when the module is loaded, regardless of how the code in the module executes. To reflect this, they must appear only in the outer module body. + +A module's interface thus consists of a collection of named bindings, which other modules that depend on the module can access. Imported bindings can be renamed to give them a new local name using `as` after their name. + +``` +import {dayName as nomDeJour} from "./dayname.js"; +console.log(nomDeJour(3)); +// → Wednesday +``` + +A module may also have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. + +``` +export default ["Winter", "Spring", "Summer", "Autumn"]; +``` + +Such a binding is imported by omitting the braces around the name of the import. + +``` +import seasonNames from "./seasonname.js"; +``` + +To import all bindings from a module at the same time, you can use `import *`. You provide a name, and that name will be bound to an object holding all the module's exports. This can be useful when you are using a lot of different exports. + +``` +import * as dayName from "./dayname.js"; +console.log(dayName.dayName(3)); +// → Wednesday +``` ## Packages {{index bug, dependency, structure, reuse}} -One of the advantages of building a program out of separate pieces, -and being actually able to run those pieces on their own, is that you -might be able to apply the same piece in different programs. +One of the advantages of building a program out of separate pieces and being able to run some of those pieces on their own is that you might be able to use the same piece in different programs. {{index "parseINI function"}} -But how do you set this up? Say I want to use the `parseINI` function -from [Chapter ?](regexp#ini) in another program. If it is clear what -the function depends on (in this case, nothing), I can just copy all the -necessary code into my new project and use it. But then, if I find a -mistake in that code, I'll probably fix it in whichever program -I'm working with at the time and forget to also fix it in the other -program. +But how do you set this up? Say I want to use the `parseINI` function from [Chapter ?](regexp#ini) in another program. If it is clear what the function depends on (in this case, nothing), I can just copy that module into my new project and use it. But then, if I find a mistake in the code, I'll probably fix it in whichever program I'm working with at the time and forget to also fix it in the other program. {{index duplication, "copy-paste programming"}} -Once you start duplicating code, you'll quickly find yourself wasting -time and energy moving copies around and keeping them up-to-date. - -That's where _((package))s_ come in. A package is a chunk of code that -can be distributed (copied and installed). It may contain one or more -modules and has information about which other packages it depends on. -A package also usually comes with documentation explaining what it -does so that people who didn't write it might still be able to use -it. +Once you start duplicating code, you'll quickly find yourself wasting time and energy moving copies around and keeping them up to date. That's where _((package))s_ come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn't write it might still be able to use it. -When a problem is found in a package or a new feature is added, the -package is updated. Now the programs that depend on it (which may also -be packages) can upgrade to the new ((version)). +When a problem is found in a package or a new feature is added, the package is updated. Now the programs that depend on it (which may also be packages) can copy the new ((version)) to get the improvements that were made to the code. {{id modules_npm}} {{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 one can download (and -upload) packages and a program (bundled with Node.js) that helps you -install and manage them. +NPM is two things: an online service where you can download (and upload) packages, and a program (bundled with Node.js) that helps you install and manage them. {{index "ini package"}} -At the time of writing, there are more than half a million different -packages available on NPM. A large portion of those are rubbish, I -should mention, but almost every useful, publicly available package -can be found on there. For example, an INI file parser, similar to the -one we built in [Chapter ?](regexp), is available under the package -name `ini`. +At the time of writing, there are more than three million different packages available on NPM. A large portion of those are rubbish, to be fair. But almost every useful, publicly available JavaScript package can be found on NPM. For example, an INI file parser, similar to the one we built in [Chapter ?](regexp), is available under the package name `ini`. {{index "command line"}} -[Chapter ?](node) will show how to install such packages locally using -the `npm` command line program. +[Chapter ?](node) will show how to install such packages locally using the `npm` command line program. -Having quality packages available for download is extremely valuable. -It means that we can often avoid reinventing a program that 100 -people have written before and get a solid, well-tested -implementation at the press of a few keys. +Having quality packages available for download is extremely valuable. It means that we can often avoid reinventing a program that 100 people have written before and get a solid, well-tested implementation at the press of a few keys. {{index maintenance}} -Software is cheap to copy, so once someone has written it, -distributing it to other people is an efficient process. But writing -it in the first place _is_ work, and responding to people who have -found problems in the code, or who want to propose new features, is -even more work. +Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. Writing it in the first place _is_ work, though, and responding to people who have found problems in the code or who want to propose new features is even more work. -By default, you own the ((copyright)) to the code you write, and other -people may use it only with your permission. But because some people -are just nice and because publishing good software can help make you -a little bit famous among programmers, many packages are published -under a ((license)) that explicitly allows other people to use it. +By default, you own the ((copyright)) to the code you write, and other people may use it only with your permission. But because some people are just nice and because publishing good software can help make you a little bit famous among programmers, many packages are published under a ((license)) that explicitly allows other people to use it. -Most code on ((NPM)) is licensed this way. Some licenses require you -to also publish code that you build on top of the package under the -same license. Others are less demanding, just requiring that you keep -the license with the code as you distribute it. The JavaScript -community mostly uses the latter type of license. When using other -people's packages, make sure you are aware of their license. +Most code on ((NPM)) is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, requiring only that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people's packages, make sure you are aware of their licenses. -## Improvised modules +{{id modules_ini}} -Until 2015, the JavaScript language had no built-in module system. -Yet people had been building large systems in JavaScript for more than a decade, and they _needed_ ((module))s. +{{index "ini package"}} -{{index [function, scope], [interface, module], [object, as module]}} +Now, instead of writing our own INI file parser, we can use one from ((NPM)). -So they designed their own ((module system))s on top of the language. -You can use JavaScript functions to create local scopes and -objects to represent module interfaces. +``` +import {parse} from "ini"; -{{index "Date class", "weekDay module"}} +console.log(parse("x = 10\ny = 20")); +// → {x: "10", y: "20"} +``` + +{{id commonjs}} + +## CommonJS modules + +Before 2015, when the JavaScript language had no built-in module system, people were already building large systems in JavaScript. To make that workable, they _needed_ ((module))s. + +{{index [function, scope], [interface, module], [object, as module]}} -This is a module for going between day names and numbers (as returned -by `Date`'s `getDay` method). Its interface consists of `weekDay.name` -and `weekDay.number`, and it hides its local binding `names` inside -the scope of a function expression that is immediately invoked. +The community designed its own improvised ((module system))s on top of the language. These use functions to create a local scope for the modules and regular objects to represent module interfaces. + +Initially, people just manually wrapped their entire module in an “((immediately invoked function +expression))” to create the module's scope and assigned their interface objects to a single global +variable. ``` const weekDay = function() { @@ -206,99 +204,21 @@ console.log(weekDay.name(weekDay.number("Sunday"))); {{index dependency, [interface, module]}} -This style of modules provides ((isolation)), to a certain degree, but -it does not declare dependencies. Instead, it just puts its -interface into the ((global scope)) and expects its dependencies, -if any, to do the same. For a long time this was the main approach -used in web programming, but it is mostly obsolete now. - -If we want to make dependency relations part of the code, we'll have -to take control of loading dependencies. Doing that requires being -able to execute strings as code. JavaScript can do this. - -{{id eval}} - -## Evaluating data as code - -{{index evaluation, interpretation}} - -There are several ways to take data (a string of code) and run it as -part of the current program. - -{{index isolation, eval}} - -The most obvious way is the special operator `eval`, which will -execute a string in the _current_ ((scope)). This is usually a bad idea -because it breaks some of the properties that scopes normally have, -such as it being easily predictable which binding a given name refers -to. - -``` -const x = 1; -function evalAndReturnX(code) { - eval(code); - return x; -} - -console.log(evalAndReturnX("var x = 2")); -// → 2 -console.log(x); -// → 1 -``` - -{{index "Function constructor"}} - -A less scary way of interpreting data as code is to use the `Function` -constructor. It takes two arguments: a string containing a -comma-separated list of argument names and a string containing the -function body. It wraps the code in a function value so that it gets -its own scope and won't do odd things with other scopes. - -``` -let plusOne = Function("n", "return n + 1;"); -console.log(plusOne(4)); -// → 5 -``` - -This is precisely what we need for a module system. We can wrap the -module's code in a function and use that function's scope as module -((scope)). - -## CommonJS - -{{id commonjs}} +This style of modules provides ((isolation)), to a certain degree, but it does not declare dependencies. Instead, it just puts its interface into the ((global scope)) and expects its dependencies, if any, to do the same. This is not ideal. {{index "CommonJS modules"}} -The most widely used approach to bolted-on JavaScript modules is -called _CommonJS modules_. ((Node.js)) uses it and is the system used -by most packages on ((NPM)). +If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used this module system from the start (though it now also knows how to load ES modules), and it is the module system used by many packages on ((NPM)). -{{index "require function", [interface, module]}} +{{index "require function", [interface, module], "exports object"}} -The main concept in CommonJS modules is a function called `require`. -When you call this with the module name of a dependency, it makes sure -the module is loaded and returns its interface. - -{{index "exports object"}} - -Because the loader wraps the module code in a function, modules -automatically get their own local scope. All they have to -do is call `require` to access their dependencies and put their -interface in the object bound to `exports`. +A CommonJS module looks like a regular script, but it has access to two bindings that it uses to interact with other modules. The first is a function called `require`. When you call this with the module name of your dependency, it makes sure the module is loaded and returns its interface. The second is an object named `exports`, which is the interface object for the module. It starts out empty and you add properties to it to define exported values. {{index "formatDate module", "Date class", "ordinal package", "date-names package"}} -This example module provides a date-formatting function. It uses two -((package))s from NPM—`ordinal` to convert numbers to strings like -`"1st"` and `"2nd"`, and `date-names` to get the English names for -weekdays and months. It exports a single function, `formatDate`, which -takes a `Date` object and a ((template)) string. +This CommonJS example module provides a date-formatting function. It uses two ((package))s from NPM—`ordinal` to convert numbers to strings like `"1st"` and `"2nd"`, and `date-names` to get the English names for weekdays and months. It exports a single function, `formatDate`, which takes a `Date` object and a ((template)) string. -The template string may contain codes that direct the format, such as -`YYYY` for the full year and `Do` for the ordinal day of the month. -You could give it a string like `"MMMM Do YYYY"` to get output like -"November 22nd 2017". +The template string may contain codes that direct the format, such as `YYYY` for the full year and `Do` for the ordinal day of the month. You could give it a string like `"MMMM Do YYYY"` to get output like `November 22nd 2017`. ``` const ordinal = require("ordinal"); @@ -318,333 +238,116 @@ exports.formatDate = function(date, format) { {{index "destructuring binding"}} -The interface of `ordinal` is a single function, whereas `date-names` -exports an object containing multiple things—`days` and `months` are -arrays of names. Destructuring is very convenient when creating -bindings for imported interfaces. +The interface of `ordinal` is a single function, whereas `date-names` exports an object containing multiple things—`days` and `months` are arrays of names. Destructuring is very convenient when creating bindings for imported interfaces. -The module adds its interface function to `exports` so that modules -that depend on it get access to it. We could use the module like this: +The module adds its interface function to `exports` so that modules that depend on it get access to it. We could use the module like this: ``` -const {formatDate} = require("./format-date"); +const {formatDate} = require("./format-date.js"); console.log(formatDate(new Date(2017, 9, 13), "dddd the Do")); // → Friday the 13th ``` -{{index "require function", "CommonJS modules", "readFile function"}} +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}} -We can define `require`, in its most minimal form, like this: +{{index "require function", "CommonJS modules", "readFile function"}} -```{test: wrap, sandbox: require} -require.cache = Object.create(null); +If we assume we have access to a `readFile` function that reads a file by name and gives us its content, we can define a simplified form of `require` like this: +```{test: wrap, sandbox: require} function require(name) { if (!(name in require.cache)) { let code = readFile(name); - let module = {exports: {}}; - require.cache[name] = module; - let wrapper = Function("require, exports, module", code); - wrapper(require, module.exports, module); + let exports = require.cache[name] = {}; + let wrapper = Function("require, exports", code); + wrapper(require, exports); } - return require.cache[name].exports; + return require.cache[name]; } +require.cache = Object.create(null); ``` -{{index [file, access]}} - -In this code, `readFile` is a made-up function that reads a file and -returns its contents as a string. Standard JavaScript provides no such -functionality—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. - -{{index cache, "Function constructor"}} - -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. - -{{index "ordinal package", "exports object", "module object", [interface, module]}} - -The interface of the `ordinal` package we saw before is not an -object but a function. A quirk of the CommonJS modules is that, -though the module system will create an empty interface object for you -(bound to `exports`), you can replace that with any value by -overwriting `module.exports`. This is done by many modules to export a -single value instead of an interface object. - -By defining `require`, `exports`, and `module` 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)). - -{{index resolution, "relative path"}} - -The way the string given to `require` is translated to an actual -filename or web address differs in different systems. When it starts with -`"./"` or `"../"`, it is generally interpreted as relative to the -current module's filename. So `"./format-date"` would be the file -named `format-date.js` in the same directory. - -When the name isn't relative, Node.js will look for an installed -package by that name. In the example code in this chapter, we'll -interpret such names as referring to NPM packages. We'll go into more -detail on how to install and use NPM modules in [Chapter ?](node). - -{{id modules_ini}} - -{{index "ini package"}} - -Now, instead of writing our own INI file parser, we can use one from -((NPM)). - -``` -const {parse} = require("ini"); - -console.log(parse("x = 10\ny = 20")); -// → {x: "10", y: "20"} -``` - -## ECMAScript modules - -((CommonJS modules)) work quite well and, in combination with NPM, -have allowed the JavaScript community to start sharing code on a large -scale. - -{{index "exports object", linter}} - -But they remain a bit of a duct-tape ((hack)). The ((notation)) is -slightly awkward—the things you add to `exports` are not available in -the local ((scope)), for example. And because `require` is a normal -function call taking any kind of argument, not just a string literal, -it can be hard to determine the dependencies of a module without -running its code. - -{{index "import keyword", dependency, "ES modules"}} - -{{id es}} - -This is why the JavaScript standard from 2015 introduces its own, -different module system. It is usually called _((ES modules))_, where -_ES_ stands for ((ECMAScript)). The main concepts of dependencies and -interfaces remain the same, but the details differ. For one thing, the -notation is now integrated into the language. Instead of calling a -function to access a dependency, you use a special `import` keyword. - -``` -import ordinal from "ordinal"; -import {days, months} from "date-names"; - -export function formatDate(date, format) { /* ... */ } -``` - -{{index "export keyword", "formatDate module"}} - -Similarly, the `export` keyword is used to export things. It may -appear in front of a function, class, or binding definition (`let`, -`const`, or `var`). - -{{index [binding, exported]}} - -An ES module's interface is not a single value but a set of named -bindings. The preceding module binds `formatDate` to a function. When -you import from another module, you import the _binding_, not the -value, which means an exporting module may change the value of the -binding at any time, and the modules that import it will see its new -value. - -{{index "default export"}} +{{id eval}} -When there is a binding named `default`, it is treated as the module's -main exported value. If you import a module like `ordinal` in the -example, without braces around the binding name, you get its `default` -binding. Such modules can still export other bindings under different -names alongside their `default` export. +{{index "Function constructor", eval, security}} -To create a default export, you write `export default` before an -expression, a function declaration, or a class declaration. +`Function` is a built-in JavaScript function that takes a list of arguments (as a comma-separated string) and a string containing the function body and returns a function value with those arguments and that body. This is an interesting concept—it allows a program to create new pieces of program from string data—but also a dangerous one, since if someone can trick your program into putting a string they provide into `Function`, they can make the program do anything they want. -``` -export default ["Winter", "Spring", "Summer", "Autumn"]; -``` +{{index [file, access]}} -It is possible to rename imported bindings using the word `as`. +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. -``` -import {days as dayNames} from "date-names"; +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. -console.log(dayNames.length); -// → 7 -``` +{{index "ordinal package", "exports object", "module object", [interface, module]}} -Another important difference is that ES module imports happen before -a module's script starts running. That means `import` declarations -may not appear inside functions or blocks, and the names of -dependencies must be quoted strings, not arbitrary expressions. +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)). -At the time of writing, the JavaScript community is in the process of -adopting this module style. But it has been a slow process. It took -a few years, after the format was specified, for browsers and Node.js -to start supporting it. And though they mostly support it now, this -support still has issues, and the discussion on how such modules -should be distributed through ((NPM)) is still ongoing. +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. -Many projects are written using ES modules and then automatically -converted to some other format when published. We are in a -transitional period in which two different module systems are used -side by side, and it is useful to be able to read and write code in -either of them. +The transition of the JavaScript community from CommonJS style to ES modules has been a slow and somewhat rough one. Fortunately we are now at a point where most of the popular packages on NPM provide their code as ES modules, and Node.js allows ES modules to import from CommonJS modules. While CommonJS code is still something you will run across, there is no real reason to write new programs in this style anymore. ## Building and bundling {{index compilation, "type checking"}} -In fact, many JavaScript projects aren't even, technically, written in -JavaScript. There are extensions, such as the type checking -((dialect)) mentioned in [Chapter ?](error#typing), that are widely -used. People also often start using planned extensions to the language -long before they have been added to the platforms that actually run -JavaScript. - -To make this possible, they _compile_ their code, translating it from -their chosen JavaScript dialect to plain old JavaScript—or even to a -past version of JavaScript—so that old ((browsers)) can run it. +Many JavaScript packages aren't technically written in JavaScript. Language extensions such as TypeScript, the type checking ((dialect)) mentioned in [Chapter ?](error#typing), are widely used. People also often start using planned new language features long before they have been added to the platforms that actually run JavaScript. To make this possible, they _compile_ their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that ((browsers)) can run it. {{index latency, performance, [file, access], [network, speed]}} -Including a modular program that consists of 200 different -files in a ((web page)) produces its own problems. If fetching a -single file over the network takes 50 milliseconds, loading -the whole program takes 10 seconds, or maybe half that if you can -load several files simultaneously. That's a lot of wasted time. -Because fetching a single big file tends to be faster than fetching a -lot of tiny ones, web programmers have started using tools that roll -their programs (which they painstakingly split into modules) back into -a single big file before they publish it to the Web. Such tools are -called _((bundler))s_. +Including a modular program that consists of 200 different files in a ((web page)) produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That's a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that combine their programs (which they painstakingly split into modules) into a single big file before they publish it to the web. Such tools are called _((bundler))s_. {{index "file size"}} -And we can go further. Apart from the number of files, the _size_ of -the files also determines how fast they can be transferred over the -network. Thus, the JavaScript community has invented _((minifier))s_. -These are tools that take a JavaScript program and make it smaller by -automatically removing comments and whitespace, renaming bindings, and -replacing pieces of code with equivalent code that take up less space. +And we can go further. Apart from the number of files, the _size_ of the files also determines how fast they can be transferred over the network. Thus, the JavaScript community has invented _((minifier))s_. These are tools that take a JavaScript program and make it smaller by automatically removing comments and whitespace, renaming bindings, and replacing pieces of code with equivalent code that take up less space. {{index pipeline, tool}} -So it is not uncommon for the code that you find in an NPM package or -that runs on a web page to have gone through _multiple_ stages of -transformation—converted from modern JavaScript to historic -JavaScript, from ES module format to CommonJS, bundled, and minified. -We won't go into the details of these tools in this book since they -tend to be boring and change rapidly. Just be aware that the -JavaScript code you run is often not the code as it was written. +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 {{index [module, design], [interface, module], [code, "structure of"]}} -Structuring programs is one of the subtler aspects of programming. Any -nontrivial piece of functionality can be modeled in various ways. +Structuring programs is one of the subtler aspects of programming. Any nontrivial piece of functionality can be organized in various ways. -Good program design is subjective—there are trade-offs involved and -matters of taste. The best way to learn the value of well-structured -design is to read or work on a lot of programs and notice what works -and what doesn't. Don't assume that a painful mess is "just the way it -is". You can improve the structure of almost everything by putting -more thought into it. +Good program design is subjective—there are trade-offs involved, and matters of taste. The best way to learn the value of well-structured design is to read or work on a lot of programs and notice what works and what doesn't. Don't assume that a painful mess is “just the way it is”. You can improve the structure of almost everything by putting more thought into it. {{index [interface, module]}} -One aspect of module design is ease of use. If you are designing -something that is intended to be used by multiple people—or even by -yourself, in three months when you no longer remember the specifics of -what you did—it is helpful if your interface is simple and -predictable. +One aspect of module design is ease of use. If you are designing something that is intended to be used by multiple people—or even by yourself, in three months when you no longer remember the specifics of what you did—it is helpful if your interface is simple and predictable. {{index "ini package", JSON}} -That may mean following existing conventions. A good example is the -`ini` package. This module imitates the standard `JSON` object by -providing `parse` and `stringify` (to write an INI file) functions, -and, like `JSON`, converts between strings and plain objects. So the -interface is small and familiar, and after you've worked with it once, -you're likely to remember how to use it. +That may mean following existing conventions. A good example is the `ini` package. This module imitates the standard `JSON` object by providing `parse` and `stringify` (to write an INI file) functions, and, like `JSON`, converts between strings and plain objects. The interface is small and familiar, and after you've worked with it once, you're likely to remember how to use it. {{index "side effect", "hard disk", composability}} -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"}} -This points to another helpful aspect of module design—the ease with -which something can be composed with other code. Focused modules that -compute values are applicable in a wider range of programs than bigger -modules that perform complicated actions with side effects. An INI -file reader that insists on reading the file from disk is useless in a -scenario where the file's content comes from some other source. +This points to another helpful aspect of module design—the ease with which something can be composed with other code. Focused modules that compute values are applicable in a wider range of programs than bigger modules that perform complicated actions with side effects. An INI file reader that insists on reading the file from disk is useless in a scenario where the file's content comes from some other source. {{index "object-oriented programming"}} -Relatedly, stateful objects are sometimes useful or even necessary, -but if something can be done with a function, use a function. Several -of the INI file readers on NPM provide an interface style that -requires you to first create an object, then load the file into your -object, and finally use specialized methods to get at the results. -This type of thing is common in the object-oriented tradition, and -it's terrible. Instead of making a single function call and moving on, -you have to perform the ritual of moving your object through various -states. And because the data is now wrapped in a specialized object -type, all code that interacts with it has to know about that type, -creating unnecessary interdependencies. - -Often defining new data structures can't be avoided—only a few -basic ones are provided by the language standard, and many types of -data have to be more complex than an array or a map. But when an -array suffices, use an array. - -An example of a slightly more complex data structure is the graph from -[Chapter ?](robot). There is no single obvious way to represent a -((graph)) in JavaScript. In that chapter, we used an object whose -properties hold arrays of strings—the other nodes reachable from that -node. - -There are several different pathfinding packages on ((NPM)), but none -of them uses this graph format. They usually allow the graph's edges to -have a weight, which is the cost or distance associated with it. That isn't -possible in our representation. +Relatedly, stateful objects are sometimes useful or even necessary, but if something can be done with a function, use a function. Several of the INI file readers on NPM provide an interface style that requires you to first create an object, then load the file into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it's terrible. Instead of making a single function call and moving on, you have to perform the ritual of moving your object through its various states. And because the data is now wrapped in a specialized object type, all code that interacts with it has to know about that type, creating unnecessary interdependencies. + +Often, defining new data structures can't be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array. + +An example of a slightly more complex data structure is the graph from [Chapter ?](robot). There is no single obvious way to represent a ((graph)) in JavaScript. In that chapter, we used an object whose properties hold arrays of strings—the other nodes reachable from that node. + +There are several different pathfinding packages on ((NPM)), but none of them uses this graph format. They usually allow the graph's edges to have a weight, which is the cost or distance associated with it. That isn't possible in our representation. {{index "Dijkstra, Edsger", pathfinding, "Dijkstra's algorithm", "dijkstrajs package"}} -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. +For example, there's the `dijkstrajs` package. A well-known approach to pathfinding, quite similar to our `findRoute` function, is called _Dijkstra's algorithm_, after Edsger Dijkstra, who first wrote it down. The `js` suffix is often added to package names to indicate the fact that they are written in JavaScript. This `dijkstrajs` package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges. -So if we wanted to use that package, we'd have to make sure that our -graph was stored in the format it expects. All edges get the same -weight since our simplified model treats each road as having the -same cost (one turn). +If we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight, since our simplified model treats each road as having the same cost (one turn). ``` const {find_path} = require("dijkstrajs"); @@ -661,27 +364,19 @@ console.log(find_path(graph, "Post Office", "Cabin")); // → ["Post Office", "Alice's House", "Cabin"] ``` -This can be a barrier to composition—when various packages are using -different data structures to describe similar things, combining them -is difficult. Therefore, if you want to design for composability, -find out what ((data structure))s other people are using and, when -possible, follow their example. +This can be a barrier to composition—when various packages are using different data structures to describe similar things, combining them is difficult. Therefore, if you want to design for composability, find out what ((data structure))s other people are using and, when possible, follow their example. + +{{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. ## Summary -Modules provide structure to bigger programs by separating the code -into pieces with clear interfaces and dependencies. The interface is -the part of the module that's visible from other modules, and the -dependencies are the other modules that it makes use of. +Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that's visible to other modules, and the dependencies are the other modules it makes use of. -Because JavaScript historically did not provide a module system, the -CommonJS system was built on top of it. Then at some point it _did_ get -a built-in system, which now coexists uneasily with the CommonJS -system. +Because JavaScript historically did not provide a module system, the CommonJS system was built on top of it. Then at some point it _did_ get a built-in system, which now coexists uneasily with the CommonJS system. -A package is a chunk of code that can be distributed on its own. NPM -is a repository of JavaScript packages. You can download all kinds of -useful (and useless) packages from it. +A package is a chunk of code that can be distributed on its own. NPM is a repository of JavaScript packages. You can download all kinds of useful (and useless) packages from it. ## Exercises @@ -691,10 +386,9 @@ useful (and useless) packages from it. {{id modular_robot}} -These are the bindings that the project from [Chapter ?](robot) -creates: +These are the bindings that the project from [Chapter ?](robot) creates: -```{lang: "text/plain"} +```{lang: "null"} roads buildGraph roadGraph @@ -708,80 +402,35 @@ findRoute goalOrientedRobot ``` -If you were to write that project as a modular program, what modules -would you create? Which module would depend on which other module, and -what would their interfaces look like? +If you were to write that project as a modular program, what modules would you create? Which module would depend on which other module, and what would their interfaces look like? -Which pieces are likely to be available prewritten on NPM? Would you -prefer to use an NPM package or write them yourself? +Which pieces are likely to be available prewritten on NPM? Would you prefer to use an NPM package or write them yourself? {{hint {{index "modular robot (exercise)"}} -Here's what I would have done (but again, there is no single _right_ -way to design a given module): +Here's what I would have done (but again, there is no single _right_ way to design a given module): {{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` 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 good -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`. - -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). - -Is it a good idea to use NPM modules for things that we could have -written ourselves? In principle, yes—for nontrivial things like the -pathfinding function you are likely to make mistakes and waste time -writing them yourself. For tiny functions like `random-item`, writing -them yourself is easy enough. But adding them wherever you need them -does tend to clutter your modules. - -However, you should also not underestimate the work involved in -_finding_ an appropriate NPM package. And even if you find one, it -might not work well or may be missing some feature you need. On top -of that, depending on NPM packages means you have to make sure they -are installed, you have to distribute them with your program, and you -might have to periodically upgrade them. - -So again, this is a trade-off, and you can decide either way depending -on how much the packages help you. +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.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). + +Is it a good idea to use NPM modules for things that we could have written ourselves? In principle, yes—for nontrivial things like the pathfinding function you are likely to make mistakes and waste time writing them yourself. For tiny functions like `random-item`, writing them yourself is easy enough. But adding them wherever you need them does tend to clutter your modules. + +However, you should also not underestimate the work involved in _finding_ an appropriate NPM package. And even if you find one, it might not work well or may be missing some feature you need. On top of that, depending on NPM packages means you have to make sure they are installed, you have to distribute them with your program, and you might have to periodically upgrade them. + +So again, this is a trade-off, and you can decide either way depending on how much a given package actually helps you. hint}} @@ -789,12 +438,7 @@ hint}} {{index "roads module (exercise)"}} -Write a ((CommonJS module)), based on the example from [Chapter -?](robot), that contains the array of roads and exports the graph -data structure representing them as `roadGraph`. It should depend on a -module `./graph`, which exports a function `buildGraph` that is used -to build the graph. This function expects an array of two-element -arrays (the start and end points of the roads). +Write an ES module based on the example from [Chapter ?](robot) that contains the array of roads and exports the graph data structure representing them as `roadGraph`. It depends on a module `./graph.js` that exports a function `buildGraph`, used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads). {{if interactive @@ -818,15 +462,9 @@ if}} {{index "roads module (exercise)", "destructuring binding", "exports object"}} -Since this is a ((CommonJS module)), you have to use `require` to -import the graph module. That was described as exporting a -`buildGraph` function, which you can pick out of its interface object -with a destructuring `const` declaration. +Since this is an ES module, you have to use `import` to access the graph module. That was described as exporting a `buildGraph` function, which you can pick out of its interface object with a destructuring `const` declaration. -To export `roadGraph`, you add a property to the `exports` object. -Because `buildGraph` takes a data structure that doesn't precisely -match `roads`, the splitting of the road strings must happen in your -module. +To export `roadGraph`, you put the keyword `export` before its definition. Because `buildGraph` takes a data structure that doesn't precisely match `roads`, the splitting of the road strings must happen in your module. hint}} @@ -834,35 +472,16 @@ hint}} {{index dependency, "circular dependency", "require function"}} -A circular dependency is a situation where module A depends on B, and -B also, directly or indirectly, depends on A. Many module systems -simply forbid this because whichever order you choose for loading such -modules, you cannot make sure that each module's dependencies have -been loaded before it runs. +A circular dependency is a situation where module A depends on B, and B also, directly or indirectly, depends on A. Many module systems simply forbid this because whichever order you choose for loading such modules, you cannot make sure that each module's dependencies have been loaded before it runs. -((CommonJS modules)) allow a limited form of cyclic dependencies. As -long as the modules do not replace their default `exports` object and -don't access each other's interface until after they finish loading, -cyclic dependencies are okay. +((CommonJS modules)) allow a limited form of cyclic dependencies. As long as the modules don't access each other's interface until after they finish loading, cyclic dependencies are okay. -The `require` function given [earlier in this -chapter](modules#require) supports this type of dependency cycle. Can -you see how it handles cycles? What would go wrong when a module in a -cycle _does_ replace its default `exports` object? +The `require` function given [earlier in this chapter](modules#require) supports this type of dependency cycle. Can you see how it handles cycles? {{hint {{index overriding, "circular dependency", "exports object"}} -The trick is that `require` adds modules to its cache _before_ it -starts loading the module. That way, if any `require` call made while -it is running tries to load it, it is already known, and the current -interface will be returned, rather than starting to load the module -once more (which would eventually overflow the stack). - -If a module overwrites its `module.exports` value, any other module -that has received its interface value before it finished loading will -have gotten hold of the default interface object (which is likely -empty), rather than the intended interface value. +The trick is that `require` adds the interface object for a module to its cache _before_ it starts loading the module. That way, if any `require` call made while it is running tries to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack). hint}} diff --git a/11_async.md b/11_async.md index 9ee8bd96b..ee30fd7fd 100644 --- a/11_async.md +++ b/11_async.md @@ -1,4 +1,4 @@ -{{meta {load_files: ["code/crow-tech.js", "code/chapter/11_async.js"]}}} +{{meta {load_files: ["code/hangar2.js", "code/chapter/11_async.js"], zip: "node/html"}}} # Asynchronous Programming @@ -11,289 +11,108 @@ quote}} {{index "Laozi"}} -{{figure {url: "img/chapter_picture_11.jpg", alt: "Picture of two crows on a branch", chapter: framed}}} +{{figure {url: "img/chapter_picture_11.jpg", alt: "Illustration showing two crows on a tree branch", chapter: framed}}} -The central part of a computer, the part that carries out the -individual steps that make up our programs, is called the -_((processor))_. The programs we have seen so far are things that will -keep the processor busy until they have finished their work. The speed -at which something like a loop that manipulates numbers can be -executed depends pretty much entirely on the speed of the processor. +The central part of a computer, the part that carries out the individual steps that make up our programs, is called the _((processor))_. The programs we have seen so far will keep the processor busy until they have finished their work. The speed at which something like a loop that manipulates numbers can be executed depends pretty much entirely on the speed of the computer's processor and memory. {{index [memory, speed], [network, speed]}} -But many programs interact with things outside of the processor. For -example, they may communicate over a computer network or request -data from the ((hard disk))—which is a lot slower than getting it from -memory. +But many programs interact with things outside of the processor. For example, they may communicate over a computer network or request data from the ((hard disk))—which is a lot slower than getting it from memory. -When such a thing is happening, it would be a shame to let the -processor sit idle—there might be some other work it could do in the -meantime. In part, this is handled by your operating system, which -will switch the processor between multiple running programs. But that -doesn't help when we want a _single_ program to be able to make -progress while it is waiting for a network request. +When such a thing is happening, it would be a shame to let the processor sit idle—there might be some other work it could do in the meantime. In part, this is handled by your operating system, which will switch the processor between multiple running programs. But that doesn't help when we want a _single_ program to be able to make progress while it is waiting for a network request. ## Asynchronicity {{index "synchronous programming"}} -In a _synchronous_ programming model, things happen one at a time. -When you call a function that performs a long-running action, it -returns only when the action has finished and it can return the result. -This stops your program for the time the action takes. +In a _synchronous_ programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes. {{index "asynchronous programming"}} -An _asynchronous_ model allows multiple things to happen at the same -time. When you start an action, your program continues to run. When -the action finishes, the program is informed and gets access to the -result (for example, the data read from disk). +An _asynchronous_ model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk). -We can compare synchronous and asynchronous programming using a small -example: a program that fetches two resources from the ((network)) and -then combines results. +We can compare synchronous and asynchronous programming using a small example: a program that makes two requests over the ((network)) and then combines the results. {{index "synchronous programming"}} -In a synchronous environment, where the request function returns only -after it has done its work, the easiest way to perform this task is to -make the requests one after the other. This has the drawback that the -second request will be started only when the first has finished. The -total time taken will be at least the sum of the two response times. +In a synchronous environment, where the request function returns only after it has done its work, the easiest way to perform this task is to make the requests one after the other. This has the drawback that the second request will be started only when the first has finished. The total time taken will be at least the sum of the two response times. {{index parallelism}} -The solution to this problem, in a synchronous system, is to start -additional ((thread))s of control. A _thread_ is another running program -whose execution may be interleaved with other programs by the -operating system—since most modern computers contain multiple -processors, multiple threads may even run at the same time, on -different processors. A second thread could start the second request, -and then both threads wait for their results to come back, after which -they resynchronize to combine their results. +The solution to this problem, in a synchronous system, is to start additional ((thread))s of control. A _thread_ is another running program whose execution may be interleaved with other programs by the operating system—since most modern computers contain multiple processors, multiple threads may even run at the same time, on different processors. A second thread could start the second request, and then both threads wait for their results to come back, after which they resynchronize to combine their results. {{index CPU, blocking, "asynchronous programming", timeline, "callback function"}} -In the following diagram, the thick lines represent time the program -spends running normally, and the thin lines represent time spent -waiting for the network. In the synchronous model, the time taken by -the network is _part_ of the timeline for a given thread of control. -In the asynchronous model, starting a network action conceptually -causes a _split_ in the timeline. The program that initiated the -action continues running, and the action happens alongside it, -notifying the program when it is finished. - -{{figure {url: "img/control-io.svg", alt: "Control flow for synchronous and asynchronous programming",width: "8cm"}}} - -{{index ["control flow", asynchronous], "asynchronous programming", verbosity}} - -Another way to describe the difference is that waiting for actions to -finish is _implicit_ in the synchronous model, while it is _explicit_, -under our control, in the asynchronous one. - -Asynchronicity cuts both ways. It makes expressing programs that do -not fit the straight-line model of control easier, but it can also -make expressing programs that do follow a straight line more awkward. -We'll see some ways to address this awkwardness later in the chapter. - -Both of the important JavaScript programming platforms—((browser))s -and ((Node.js))—make operations that might take a while asynchronous, -rather than relying on ((thread))s. Since programming with threads is -notoriously hard (understanding what a program does is much more -difficult when it's doing multiple things at once), this is generally -considered a good thing. - -## Crow tech - -Most people are aware of the fact that ((crow))s are very smart birds. -They can use tools, plan ahead, remember things, and even communicate -these things among themselves. - -What most people don't know is that they are capable of many things -that they keep well hidden from us. I've been told by a reputable (if -somewhat eccentric) expert on ((corvid))s that crow technology is not -far behind human technology, and they are catching up. - -For example, many crow cultures have the ability to construct -computing devices. These are not electronic, as human computing -devices are, but operate through the actions of tiny insects, a -species closely related to the ((termite)), which has developed a -((symbiotic relationship)) with the crows. The birds provide them with -food, and in return the insects build and operate their complex -colonies that, with the help of the living creatures inside them, -perform computations. - -Such colonies are usually located in big, long-lived nests. The birds -and insects work together to build a network of bulbous clay -structures, hidden between the twigs of the nest, in which the insects -live and work. - -To communicate with other devices, these machines use light signals. -The crows embed pieces of reflective material in special communication -stalks, and the insects aim these to reflect light at another nest, -encoding data as a sequence of quick flashes. This means that only -nests that have an unbroken visual connection can communicate. - -Our friend the corvid expert has mapped the network of crow nests in -the village of ((Hières-sur-Amby)), on the banks of the river Rhône. -This map shows the nests and their connections: - -{{figure {url: "img/Hieres-sur-Amby.png", alt: "A network of crow nests in a small village"}}} - -In an astounding example of ((convergent evolution)), crow computers -run JavaScript. In this chapter we'll write some basic networking -functions for them. +In the following diagram, the thick lines represent time the program spends running normally, and the thin lines represent time spent waiting for the network. In the synchronous model, the time taken by the network is _part_ of the timeline for a given thread of control. In the asynchronous model, starting a network action allows the program to continue running while the network communication happens alongside it, notifying the program when it is finished. + +{{figure {url: "img/control-io.svg", alt: "Diagram of showing control flow in synchronous and asynchronous programs. The first part shows a synchronous program, where the program's active and waiting phases all happen on a single, sequential line. The second part shows a multi-threaded synchronous program, with two parallel lines, on which the waiting parts happen alongside each other, causing the program to finish faster. The last part shows an asynchronous program, where the multiple asynchronous actions branch off from the main program, which at some point stops, and then resumes whenever the first thing it was waiting for finishes.",width: "8cm"}}} + +{{index ["control flow", asynchronous], "asynchronous programming", verbosity, performance}} + +Another way to describe the difference is that waiting for actions to finish is _implicit_ in the synchronous model, while it is _explicit_—under our control—in the asynchronous one. + +Asynchronicity cuts both ways. It makes expressing programs that do not fit the straight-line model of control easier, but it can also make expressing programs that do follow a straight line more awkward. We'll see some ways to reduce this awkwardness later in the chapter. + +Both prominent JavaScript programming platforms—((browser))s and ((Node.js))—make operations that might take a while asynchronous, rather than relying on ((thread))s. Since programming with threads is notoriously hard (understanding what a program does is much more difficult when it's doing multiple things at once), this is generally considered a good thing. ## Callbacks {{indexsee [function, callback], "callback function"}} -One approach to ((asynchronous programming)) is to make functions that -perform a slow action take an extra argument, a _((callback -function))_. The action is started, and when it finishes, the callback -function is called with the result. +One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. The asynchronous function starts a process, sets things up so that the callback function is called when the process finishes, and then returns. {{index "setTimeout function", waiting}} -As an example, the `setTimeout` function, available both in Node.js -and in browsers, waits a given number of milliseconds (a second is a -thousand milliseconds) and then calls a function. +As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds and then calls a function. ```{test: no} setTimeout(() => console.log("Tick"), 500); ``` -Waiting is not generally a very important type of work, but it can be -useful when doing something like updating an animation or checking whether -something is taking longer than a given amount of ((time)). - -Performing multiple asynchronous actions in a row using callbacks -means that you have to keep passing new functions to handle the -((continuation)) of the computation after the actions. - -{{index "hard disk"}} - -Most crow nest computers have a long-term data storage bulb, where -pieces of information are etched into twigs so that they can be -retrieved later. Etching, or finding a piece of data, takes a moment, so -the interface to long-term storage is asynchronous and uses callback -functions. - -Storage bulbs store pieces of ((JSON))-encodable data under names. A -((crow)) might store information about the places where it's hidden food under the name `"food caches"`, which could hold an array of -names that point at other pieces of data, describing the actual cache. -To look up a food ((cache)) in the storage bulbs of the _Big Oak_ -nest, a crow could run code like this: +Waiting is not generally important work, but it can be very useful when you need to arrange for something to happen at a certain time or check whether some action is taking longer than expected. -{{index "readStorage function"}} +{{index "readTextFile function"}} -```{includeCode: "top_lines: 1"} -import {bigOak} from "./crow-tech"; +Another example of a common asynchronous operation is reading a file from a device's storage. Imagine you have a function `readTextFile` that reads a file's content as a string and passes it to a callback function. -bigOak.readStorage("food caches", caches => { - let firstCache = caches[0]; - bigOak.readStorage(firstCache, info => { - console.log(info); - }); +``` +readTextFile("shopping_list.txt", content => { + console.log(`Shopping List:\n${content}`); }); +// → Shopping List: +// → Peanut butter +// → Bananas ``` -(All binding names and strings have been translated from crow language -to English.) - -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 running multiple actions -at the same time, can get a little awkward. - -Crow nest computers are built to communicate using -((request))-((response)) pairs. That means one nest sends a message to -another nest, which then immediately sends a message back, confirming -receipt and possibly including a reply to a question asked in the -message. - -Each message is tagged with a _type_, which determines how it is -handled. Our code can define handlers for specific request types, and -when such a request comes in, the handler is called to produce a -response. +The `readTextFile` function is not part of standard JavaScript. We will see how to read files in the browser and in Node.js in later chapters. -{{index "crow-tech module", "send method"}} +Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the ((continuation)) of the computation after the actions. An asynchronous function that compares two files and produces a boolean indicating whether their content is the same might look like this: -The interface exported by the `"./crow-tech"` module provides -callback-based functions for communication. Nests have a `send` method -that sends off a request. It expects the name of the target nest, the -type of the request, and the content of the request as its first three -arguments, and it expects a function to call when a response comes in as its -fourth and last argument. - -``` -bigOak.send("Cow Pasture", "note", "Let's caw loudly at 7PM", - () => console.log("Note delivered.")); ``` - -But to make nests capable of receiving that request, we first have to -define a ((request type)) named `"note"`. The code that handles the -requests has to run not just on this nest-computer but on all nests -that can receive messages of this type. We'll just assume that a crow -flies over and installs our handler code on all the nests. - -{{index "defineRequestType function"}} - -```{includeCode: true} -import {defineRequestType} from "./crow-tech"; - -defineRequestType("note", (nest, content, source, done) => { - console.log(`${nest.name} received note: ${content}`); - done(); -}); +function compareFiles(fileA, fileB, callback) { + readTextFile(fileA, contentA => { + readTextFile(fileB, contentB => { + callback(contentA == contentB); + }); + }); +} ``` -The `defineRequestType` function defines a new type of request. The -example adds support for `"note"` requests, which just sends a note to -a given nest. Our implementation calls `console.log` so that we can -verify that the request arrived. Nests have a `name` property that -holds their name. - -{{index "asynchronous programming"}} +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. -The fourth argument given to the handler, `done`, is a callback -function that it must call when it is done with the request. If we had -used the handler's ((return value)) as the response value, that would -mean that a request handler can't itself perform asynchronous actions. -A function doing asynchronous work typically returns before the work -is done, having arranged for a callback to be called when it -completes. So we need some asynchronous mechanism—in this case, -another ((callback function))—to signal when a response is available. - -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 -Working with abstract concepts is often easier when those concepts can -be represented by ((value))s. In the case of asynchronous actions, you -could, instead of arranging for a function to be called at some point -in the future, return an object that represents this future event. +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"}} +{{index "Promise class", "asynchronous programming", "resolving (a promise)", "then method", "callback function"}} -This is what the standard class `Promise` is for. A _promise_ is an -asynchronous action that may complete at some point and produce a -value. It is able to notify anyone who is interested when its value is -available. +This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provides a `then` method that allows you to register a function that should be called when the action for which it is waiting finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible to call `then` on a promise that has already resolved—your function will still be called. -{{index "Promise.resolve function", "resolving (a promise)"}} +{{index "Promise.resolve function"}} -The easiest way to create a promise is by calling `Promise.resolve`. -This function ensures that the value you give it is wrapped in a -promise. If it's already a promise, it is simply returned—otherwise, -you get a new promise that immediately finishes with your -value as its result. +The easiest way to create a promise is by calling `Promise.resolve`. This function ensures that the value you give it is wrapped in a promise. If it's already a promise, it is simply returned. Otherwise, you get a new promise that immediately resolves with your value as its result. ``` let fifteen = Promise.resolve(15); @@ -301,810 +120,455 @@ fifteen.then(value => console.log(`Got ${value}`)); // → Got 15 ``` -{{index "then method"}} - -To get the result of a promise, you can use its `then` method. This -registers a ((callback function)) to be called when the promise -resolves and produces a value. You can add multiple callbacks to a -single promise, and they will be called, even if you add them after -the promise has already _resolved_ (finished). - -But that's not all the `then` method does. It returns another promise, -which resolves to the value that the handler function returns or, if -that returns a promise, waits for that promise and then resolves to -its result. - -It is useful to think of promises as a device to move values into an -asynchronous reality. A normal value is simply there. A promised value -is a value that _might_ already be there or might appear at some point -in the future. Computations defined in terms of promises act on such -wrapped values and are executed asynchronously as the values become -available. - {{index "Promise class"}} -To create a promise, 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. It works this way, instead of for example with a -`resolve` method, so that only the code that created the promise can -resolve it. +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. -{{index "storage function"}} +For example, this is how you could create a promise-based interface for the `readTextFile` function: -This is how you'd create a promise-based interface for the -`readStorage` function: +{{index "textFile function"}} -```{includeCode: "top_lines: 5"} -function storage(nest, name) { +``` +function textFile(filename) { return new Promise(resolve => { - nest.readStorage(name, result => resolve(result)); + readTextFile(filename, text => resolve(text)); }); } -storage(bigOak, "enemies") - .then(value => console.log("Got", value)); +textFile("plans.txt").then(console.log); ``` -This asynchronous function returns a meaningful value. This is the -main advantage of promises—they simplify the use of asynchronous -functions. Instead of having to pass around callbacks, promise-based -functions look similar to regular ones: they take input as arguments -and return their output. The only difference is that the output may -not be available yet. +Note how, in contrast to callback-style functions, this asynchronous function returns a meaningful value—a promise to give you the contents of the file at some point in the future. -## Failure +{{index "then method"}} -{{index "exception handling"}} +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. -Regular JavaScript computations can fail by throwing an exception. -Asynchronous computations often need something like that. A network -request may fail, or some code that is part of the asynchronous -computation may throw an exception. +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: -{{index "callback function", error}} +``` +function randomFile(listFile) { + return textFile(listFile) + .then(content => content.trim().split("\n")) + .then(ls => ls[Math.floor(Math.random() * ls.length)]) + .then(filename => textFile(filename)); +} +``` -One of the most pressing problems with the callback style of -asynchronous programming is that it makes it extremely difficult to -make sure failures are properly reported to the callbacks. +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. -A widely used convention is that the first argument to the callback is -used to indicate that the action failed, and the second contains the -value produced by the action when it was successful. Such callback -functions must always check whether they received an exception and -make sure that any problems they cause, including exceptions thrown by -functions they call, are caught and given to the right function. +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. -{{index "rejecting (a promise)", "resolving (a promise)", "then method"}} +It would also have been possible to perform all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, such as when you want to return a promise that produces a processed version of some asynchronous result. -Promises make this easier. They can be either resolved (the action -finished successfully) or rejected (it failed). Resolve handlers (as -registered with `then`) are called only when the action is successful, -and rejections are automatically propagated to the new promise that is -returned by `then`. And when a handler throws an exception, this -automatically causes the promise produced by its `then` call to be -rejected. So if any element in a chain of asynchronous actions fails, -the outcome of the whole chain is marked as rejected, and no success -handlers are called beyond the point where it failed. +``` +function jsonFile(filename) { + return textFile(filename).then(JSON.parse); +} -{{index "Promise.reject function", "Promise class"}} +jsonFile("package.json").then(console.log); +``` -Much like resolving a promise provides a value, rejecting one also -provides one, usually called the _reason_ of the rejection. When an -exception in a handler function causes the rejection, the exception -value is used as the reason. Similarly, when a handler returns a -promise that is rejected, that rejection flows into the next promise. -There's a `Promise.reject` function that creates a new, -immediately rejected promise. +Generally, it is useful to think of a promise as a device that lets code ignore the question of when a value is going to arrive. A normal value has to actually exist before we can reference it. A promised value is a value that _might_ already be there or might appear at some point in the future. Computations defined in terms of promises, by wiring them together with `then` calls, are executed asynchronously as their inputs become available. -{{index "catch method"}} +## Failure -To explicitly handle such rejections, promises have a `catch` method -that registers a handler to be called when the promise is rejected, -similar to how `then` handlers handle normal resolution. It's also very -much like `then` in that it returns a new promise, which resolves to -the original promise's value if it resolves normally and to the -result of the `catch` handler otherwise. If a `catch` handler throws -an error, the new promise is also rejected. +{{index "exception handling"}} -{{index "then method"}} +Regular JavaScript computations can fail by throwing an exception. Asynchronous computations often need something like that. A network request may fail, a file may not exist, or some code that is part of the asynchronous computation may throw an exception. -As a shorthand, `then` also accepts a rejection handler as a second -argument, so you can install both types of handlers in a single method -call. +{{index "callback function", error}} -A function passed to the `Promise` constructor receives a second -argument, alongside the resolve function, which it can use to reject -the new promise. +One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to ensure failures are properly reported to the callbacks. -The chains of promise values created by calls to `then` and `catch` -can be seen as a pipeline through which asynchronous values or -failures move. Since such chains are created by registering handlers, -each link has a success handler or a rejection handler (or both) -associated with it. Handlers that don't match the type of outcome -(success or failure) are ignored. But those that do match are called, -and their outcome determines what kind of value comes next—success -when it returns a non-promise value, rejection when it throws an -exception, and the outcome of a promise when it returns one of those. +A common convention is to use the first argument to the callback to indicate that the action failed, and the second to pass the value produced by the action when it was successful. -```{test: no} -new Promise((_, reject) => reject(new Error("Fail"))) - .then(value => console.log("Handler 1")) - .catch(reason => { - console.log("Caught failure " + reason); - return "nothing"; - }) - .then(value => console.log("Handler 2", value)); -// → Caught failure Error: Fail -// → Handler 2 nothing +``` +someAsyncFunction((error, value) => { + if (error) handleError(error); + else processValue(value); +}); ``` -{{index "uncaught exception", "exception handling"}} - -Much like an uncaught exception is handled by the environment, -JavaScript environments can detect when a promise rejection isn't -handled and will report this as an error. +Such callback functions must always check whether they received an exception and make sure that any problems they cause, including exceptions thrown by functions they call, are caught and given to the right function. -## Networks are hard +{{index "rejecting (a promise)", "resolving (a promise)", "then method"}} -{{index [network, reliability]}} +Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with `then`) are called only when the action is successful, and rejections are propagated to the new promise returned by `then`. When a handler throws an exception, this automatically causes the promise produced by its `then` call to be rejected. If any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed. -Occasionally, there isn't enough light for the ((crow))s' mirror -systems to transmit a signal or something is blocking the path of the -signal. It is possible for a signal to be sent but never received. +{{index "Promise.reject function", "Promise class"}} -{{index "send method", error, timeout}} +Much like resolving a promise provides a value, rejecting one also provides a value, usually called the _reason_ of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There's a `Promise.reject` function that creates a new, immediately rejected promise. -As it is, that will just cause the callback given to `send` to never -be called, which will probably cause the program to stop without even -noticing there is a problem. It would be nice if, after a given period -of not getting a response, a request would _time out_ and report -failure. +{{index "catch method"}} -Often, transmission failures are random accidents, like a car's -headlight interfering with the light signals, and simply retrying the -request may cause it to succeed. So while we're at it, let's make our -request function automatically retry the sending of the request a few -times before it gives up. +To explicitly handle such rejections, promises have a `catch` method that registers a handler to be called when the promise is rejected, similar to how `then` handlers handle normal resolution. It's also very much like `then` in that it returns a new promise, which resolves to the original promise's value when that resolves normally and to the result of the `catch` handler otherwise. If a `catch` handler throws an error, the new promise is also rejected. -{{index "Promise class", "callback function", [interface, object]}} +{{index "then method"}} -And, since we've established that promises are a good thing, we'll -also make our request function return a promise. In terms of what they -can express, callbacks and promises are equivalent. Callback-based -functions can be wrapped to expose a promise-based interface, and -vice versa. +As a shorthand, `then` also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call: `.then(acceptHandler, rejectHandler)`. -Even when a ((request)) and its ((response)) are successfully -delivered, the response may indicate failure—for example, if the -request tries to use a request type that hasn't been defined or the -handler throws an error. To support this, `send` and -`defineRequestType` follow the convention mentioned before, where the -first argument passed to callbacks is the failure reason, if any, and -the second is the actual result. +A function passed to the `Promise` constructor receives a second argument, alongside the resolve function, which it can use to reject the new promise. -These can be translated to promise resolution and rejection by our -wrapper. +{{index "textFile function"}} -{{index "Timeout class", "request function", retry}} +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} -class Timeout extends Error {} - -function request(nest, target, type, content) { +function textFile(filename) { return new Promise((resolve, reject) => { - let done = false; - function attempt(n) { - nest.send(target, type, content, (failed, value) => { - done = true; - if (failed) reject(failed); - else resolve(value); - }); - setTimeout(() => { - if (done) return; - else if (n < 3) attempt(n + 1); - else reject(new Timeout("Timed out")); - }, 250); - } - attempt(1); + readTextFile(filename, (text, error) => { + if (error) reject(error); + else resolve(text); + }); }); } ``` -{{index "Promise class", "resolving (a promise)", "rejecting (a promise)"}} +The chains of promise values created by calls to `then` and `catch` thus form a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don't match the type of outcome (success or failure) are ignored. Handlers that do match are called, and their outcome determines what kind of value comes next—success when they return a non-promise value, rejection when they throw an exception, and the outcome of the promise when they return a promise. -Because promises can be resolved (or rejected) only once, this will -work. The first time `resolve` or `reject` is called determines the -outcome of the promise, and further calls caused by a request coming -back after another request finished are ignored. +```{test: no} +new Promise((_, reject) => reject(new Error("Fail"))) + .then(value => console.log("Handler 1:", value)) + .catch(reason => { + console.log("Caught failure " + reason); + return "nothing"; + }) + .then(value => console.log("Handler 2:", value)); +// → Caught failure Error: Fail +// → Handler 2: nothing +``` -{{index recursion}} +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. -To build an asynchronous ((loop)), for the retries, we need to use a -recursive function—a regular loop doesn't allow us to stop and wait -for an asynchronous action. The `attempt` function makes a single -attempt to send a request. It also sets a timeout that, if no response -has come back after 250 milliseconds, either starts the next attempt -or, if this was the third attempt, rejects the promise with an -instance of `Timeout` as the reason. +{{index "uncaught exception", "exception handling"}} -{{index idempotence}} +Much like an uncaught exception is handled by the environment, JavaScript environments can detect when a promise rejection isn't handled and will report this as an error. -Retrying every quarter-second and giving up when no response has come -in after three-quarter second is definitely somewhat arbitrary. It is even -possible, if the request did come through but the handler is just -taking a bit longer, for requests to be delivered multiple times. -We'll write our handlers with that problem in mind—duplicate messages -should be harmless. +## Carla -In general, we will not be building a world-class, robust network -today. But that's okay—crows don't have very high expectations yet -when it comes to computing. +{{index "Carla the crow"}} -{{index "defineRequestType function", "requestType function"}} +It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container, a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. -To isolate ourselves from callbacks altogether, we'll go ahead and -also define a wrapper for `defineRequestType` that allows the handler -function to return a promise or plain value and wires that up to the -callback for us. +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. -```{includeCode: true} -function requestType(name, handler) { - defineRequestType(name, (nest, content, source, - callback) => { - try { - Promise.resolve(handler(nest, content, source)) - .then(response => callback(null, response), - failure => callback(failure)); - } catch (exception) { - callback(exception); - } - }); -} -``` +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. -{{index "Promise.resolve function"}} +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. -`Promise.resolve` is used to convert the value returned by `handler` -to a promise if it isn't already. +This crow is known to her peers as “cāāw-krö”. But since those sounds are poorly suited for human vocal chords, we'll refer to her as Carla. -{{index "try keyword", "callback function"}} +Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she had a good grasp of what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of asynchronous JavaScript code. -Note that the call to `handler` had to be wrapped in a `try` block to -make sure any exception it raises directly is given to the callback. -This nicely illustrates the difficulty of properly handling errors -with raw callbacks—it is easy to forget to properly route -exceptions like that, and if you don't do it, failures won't get -reported to the right callback. Promises make this mostly automatic -and thus less error-prone. +## Breaking In -## Collections of promises +{{index "Carla the crow"}} -{{index "neighbors property", "ping request"}} +Carla loves the internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. -Each nest computer keeps an array of other nests within transmission -distance in its `neighbors` property. To check which of those are -currently reachable, you could write a function that tries to send a -`"ping"` request (a request that simply asks for a response) to each -of them and see which ones come back. +Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device must send along the correct six-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending a partial code (say, only three digits), the response is different based on whether those digits are the correct start of the code or not. Sending incorrect numbers immediately returns a failure message. When sending the correct ones, the access point waits for more digits. -{{index "Promise.all function"}} +This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find the second digit in the same way, and so on, until she knows the entire passcode. -When working with collections of promises running at the same time, -the `Promise.all` function can be useful. It returns a promise that -waits for all of the promises in the array to resolve and then -resolves to an array of the values that these promises produced (in -the same order as the original array). If any promise is rejected, the -result of `Promise.all` is itself rejected. +Assume Carla has a `joinWifi` function. Given the network name and the passcode (as a string), the function tries to join the network, returning a promise that resolves if successful and rejects if the authentication failed. The first thing she needs is a way to wrap a promise so that it automatically rejects after it takes too much time, to allow the program to quickly move on if the access point doesn't respond. ```{includeCode: true} -requestType("ping", () => "pong"); - -function availableNeighbors(nest) { - let requests = nest.neighbors.map(neighbor => { - return request(nest, neighbor, "ping") - .then(() => true, () => false); - }); - return Promise.all(requests).then(result => { - return nest.neighbors.filter((_, i) => result[i]); +function withTimeout(promise, time) { + return new Promise((resolve, reject) => { + promise.then(resolve, reject); + setTimeout(() => reject("Timed out"), time); }); } ``` -{{index "then method"}} - -When a neighbor isn't available, we don't want the entire combined -promise to fail since then we still wouldn't know anything. So the -function that is mapped over the set of neighbors to turn them into -request promises attaches handlers that make successful requests -produce `true` and rejected ones produce `false`. - -{{index "filter method", "map method", "some method"}} - -In the handler for the combined promise, `filter` is used to remove -those elements from the `neighbors` array whose corresponding value is -false. This makes use of the fact that `filter` passes the array index -of the current element as a second argument to its filtering function -(`map`, `some`, and similar higher-order array methods do the same). - -## Network flooding - -The fact that nests can talk only to their neighbors greatly inhibits -the usefulness of this network. +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. -For broadcasting information to the whole network, one solution is to -set up a type of request that is automatically forwarded to neighbors. -These neighbors then in turn forward it to their neighbors, until the -whole network has received the message. +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. -{{index "sendGossip function"}} +Because you cannot wait for a promise inside a `for` loop, Carla uses a recursive function to drive this process. On each call, this function gets the code as we know it so far, as well as the next digit to try. Depending on what happens, it may return a finished code or call through to itself, to either start cracking the next position in the code or to try again with another digit. ```{includeCode: true} -import {everywhere} from "./crow-tech"; - -everywhere(nest => { - nest.state.gossip = []; -}); - -function sendGossip(nest, message, exceptFor = null) { - nest.state.gossip.push(message); - for (let neighbor of nest.neighbors) { - if (neighbor == exceptFor) continue; - request(nest, neighbor, "gossip", message); +function crackPasscode(networkID) { + function nextDigit(code, digit) { + let newCode = code + digit; + return withTimeout(joinWifi(networkID, newCode), 50) + .then(() => newCode) + .catch(failure => { + if (failure == "Timed out") { + return nextDigit(newCode, 0); + } else if (digit < 9) { + return nextDigit(code, digit + 1); + } else { + throw failure; + } + }); } + return nextDigit("", 0); } - -requestType("gossip", (nest, message, source) => { - if (nest.state.gossip.includes(message)) return; - console.log(`${nest.name} received gossip '${ - message}' from ${source}`); - sendGossip(nest, message, source); -}); ``` -{{index "everywhere function", "gossip property"}} - -To avoid sending the same message around the network forever, each -nest keeps an array of gossip strings that it has already seen. To -define this array, we use the `everywhere` function—which runs code on -every nest—to add a property to the nest's `state` object, which is -where we'll keep nest-local state. - -When a nest receives a duplicate gossip message, which is very likely -to happen with everybody blindly resending them, it ignores it. But -when it receives a new message, it excitedly tells all its neighbors -except for the one who sent it the message. - -This will cause a new piece of gossip to spread through the network -like an ink stain in water. Even when some connections aren't -currently working, if there is an alternative route to a given nest, -the gossip will reach it through there. - -{{index "flooding"}} - -This style of network communication is called _flooding_—it floods the -network with a piece of information until all nodes have it. - -{{if interactive - -We can call `sendGossip` to see a message flow through the village. +The access point tends to respond to bad authentication requests in about 20 milliseconds, so to be safe, this function waits for 50 milliseconds before timing out a request. ``` -sendGossip(bigOak, "Kids with airgun in the park"); +crackPasscode("HANGAR 2").then(console.log); +// → 555555 ``` -if}} - -## Message routing +Carla tilts her head and sighs. This would have been more satisfying if the code had been a bit harder to guess. -{{index efficiency}} - -If a given node wants to talk to a single other node, flooding is not -a very efficient approach. Especially when the network is big, that -would lead to a lot of useless data transfers. +## Async functions -{{index "routing"}} +{{index "Promise class", recursion}} -An alternative approach is to set up a way for messages to hop from -node to node until they reach their destination. The difficulty with -that is it requires knowledge about the layout of the network. To -send a request in the direction of a faraway nest, it is necessary to -know which neighboring nest gets it closer to its destination. Sending -it in the wrong direction will not do much good. +Even with promises, this kind of asynchronous code is annoying to write. Promises often need to be tied together in verbose, arbitrary-looking ways. To create an asynchronous loop, Carla was forced to introduce a recursive function. -Since each nest knows only about its direct neighbors, it doesn't have -the information it needs to compute a route. We must somehow spread -the information about these connections to all nests, preferably in a -way that allows it to change over time, when nests are abandoned or -new nests are built. +{{index "synchronous programming", "asynchronous programming"}} -{{index flooding}} +The thing the cracking function actually does is completely linear—it always waits for the previous action to complete before starting the next one. In a synchronous programming model, it'd be more straightforward to express. -We can use flooding again, but instead of checking whether a given -message has already been received, we now check whether the new set of -neighbors for a given nest matches the current set we have for it. +{{index "async function", "await keyword"}} -{{index "broadcastConnections function", "connections binding"}} +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. -```{includeCode: true} -requestType("connections", (nest, {name, neighbors}, - source) => { - let connections = nest.state.connections; - if (JSON.stringify(connections.get(name)) == - JSON.stringify(neighbors)) return; - connections.set(name, neighbors); - broadcastConnections(nest, name, source); -}); +{{index "findInStorage function"}} -function broadcastConnections(nest, name, exceptFor = null) { - for (let neighbor of nest.neighbors) { - if (neighbor == exceptFor) continue; - request(nest, neighbor, "connections", { - name, - neighbors: nest.state.connections.get(name) - }); +We can rewrite `crackPasscode` like this: + +``` +async function crackPasscode(networkID) { + for (let code = "";;) { + for (let digit = 0;; digit++) { + let newCode = code + digit; + try { + await withTimeout(joinWifi(networkID, newCode), 50); + return newCode; + } catch (failure) { + if (failure == "Timed out") { + code = newCode; + break; + } else if (digit == 9) { + throw failure; + } + } + } } } - -everywhere(nest => { - nest.state.connections = new Map(); - nest.state.connections.set(nest.name, nest.neighbors); - broadcastConnections(nest, nest.name); -}); ``` -{{index JSON, "== operator"}} - -The comparison uses `JSON.stringify` because `==`, on objects or -arrays, will return true only when the two are the exact same value, -which is not what we need here. Comparing the JSON strings is a crude -but effective way to compare their content. - -The nodes immediately start broadcasting their connections, which -should, unless some nests are completely unreachable, quickly give -every nest a map of the current network ((graph)). +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 pathfinding}} +{{index "async function", "return keyword", "exception handling"}} -A thing you can do with graphs is find routes in them, as we saw in -[Chapter ?](robot). If we have a route toward a message's -destination, we know which direction to send it in. +An `async` function is marked by the word `async` before the `function` keyword. Methods can also be made `async` by writing `async` before their name. When such a function or method is called, it returns a promise. As soon as the function returns something, that promise is resolved. If the body throws an exception, the promise is rejected. -{{index "findRoute function"}} +{{index "await keyword", ["control flow", asynchronous]}} -This `findRoute` function, which greatly resembles the `findRoute` -from [Chapter ?](robot#findRoute), searches for a way to reach a given -node in the network. But instead of returning the whole route, it just -returns the next step. That next nest will itself, using its current -information about the network, decide where _it_ sends the message. +Inside an `async` function, the word `await` can be put in front of an expression to wait for a promise to resolve and only then continue the execution of the function. If the promise rejects, an exception is raised at the point of the `await`. -```{includeCode: true} -function findRoute(from, to, connections) { - let work = [{at: from, via: null}]; - for (let i = 0; i < work.length; i++) { - let {at, via} = work[i]; - for (let next of connections.get(at) || []) { - if (next == to) return via; - if (!work.some(w => w.at == next)) { - work.push({at: next, via: via || next}); - } - } - } - return null; -} -``` +Such a function no longer runs from start to completion in one go like a regular JavaScript function. Instead, it can be _frozen_ at any point that has an `await` and can be resumed at a later time. -Now we can build a function that can send long-distance messages. If -the message is addressed to a direct neighbor, it is delivered as -usual. If not, it is packaged in an object and sent to a neighbor that -is closer to the target, using the `"route"` request type, which will -cause that neighbor to repeat the same behavior. +For most asynchronous code, this notation is more convenient than directly using promises. You do still need an understanding of promises, since in many cases you'll still interact with them directly. But when wiring them together, `async` functions are generally more pleasant to write than chains of `then` calls. -{{index "routeRequest function"}} +{{id generator}} -```{includeCode: true} -function routeRequest(nest, target, type, content) { - if (nest.neighbors.includes(target)) { - return request(nest, target, type, content); - } else { - let via = findRoute(nest.name, target, - nest.state.connections); - if (!via) throw new Error(`No route to ${target}`); - return request(nest, via, "route", - {target, type, content}); - } -} +## Generators -requestType("route", (nest, {target, type, content}) => { - return routeRequest(nest, target, type, content); -}); -``` +{{index "async function"}} -{{if interactive +This ability of functions to be paused and then resumed again is not exclusive to `async` functions. JavaScript also has a feature called _((generator))_ functions. These are similar, but without the promises. -We can now send a message to the nest in the church tower, which is -four network hops removed. +When you define a function with `function*` (placing an asterisk after the word `function`), it becomes a generator. When you call a generator, it returns an ((iterator)), which we already saw in [Chapter ?](object). ``` -routeRequest(bigOak, "Church Tower", "note", - "Incoming jackdaws!"); -``` - -if}} - -{{index [network, abstraction], layering}} +function* powers(n) { + for (let current = n;; current *= n) { + yield current; + } +} -We've constructed several layers of functionality on top of a -primitive communication system to make it convenient to use. -This is a nice (though simplified) model of how real computer networks -work. +for (let power of powers(3)) { + if (power > 50) break; + console.log(power); +} +// → 3 +// → 9 +// → 27 +``` -{{index error}} +{{index "next method", "yield keyword"}} -A distinguishing property of computer networks is that they aren't -reliable—abstractions built on top of them can help, but you can't -abstract away network failure. So network programming is typically -very much about anticipating and dealing with failures. +Initially, when you call `powers`, the function is frozen at its start. Every time you call `next` on the iterator, the function runs until it hits a `yield` expression, which pauses it and causes the yielded value to become the next value produced by the iterator. When the function returns (the one in the example never does), the iterator is done. -## Async functions +Writing iterators is often much easier when you use generator functions. The iterator for the `Group` class (from the exercise in [Chapter ?](object#group_iterator)) can be written with this generator: -To store important information, ((crow))s are known to duplicate it -across nests. That way, when a hawk destroys a nest, the information -isn't lost. +{{index "Group class"}} -To retrieve a given piece of information that it doesn't have in its -own storage bulb, a nest computer might consult random other nests in -the network until it finds one that has it. +``` +Group.prototype[Symbol.iterator] = function*() { + for (let i = 0; i < this.members.length; i++) { + yield this.members[i]; + } +}; +``` -{{index "findInStorage function", "network function"}} +```{hidden: true, includeCode: true} +class Group { + constructor() { this.members = []; } + add(m) { this.members.add(m); } +} +``` -```{includeCode: true} -requestType("storage", (nest, name) => storage(nest, name)); +{{index [state, in iterator]}} -function findInStorage(nest, name) { - return storage(nest, name).then(found => { - if (found != null) return found; - else return findInRemoteStorage(nest, name); - }); -} +There's no longer a need to create an object to hold the iteration state—generators automatically save their local state every time they yield. -function network(nest) { - return Array.from(nest.state.connections.keys()); -} +Such `yield` expressions may occur only directly in the generator function itself and not in an inner function you define inside of it. The state a generator saves, when yielding, is only its _local_ environment and the position where it yielded. -function findInRemoteStorage(nest, name) { - let sources = network(nest).filter(n => n != nest.name); - function next() { - if (sources.length == 0) { - return Promise.reject(new Error("Not found")); - } else { - let source = sources[Math.floor(Math.random() * - sources.length)]; - sources = sources.filter(n => n != source); - return routeRequest(nest, source, "storage", name) - .then(value => value != null ? value : next(), - next); - } - } - return next(); -} -``` +{{index "await keyword"}} -{{index "Map class", "Object.keys function", "Array.from function"}} +An `async` function is a special type of generator. It produces a promise when called, which is resolved when it returns (finishes) and rejected when it throws an exception. Whenever it yields (awaits) a promise, the result of that promise (value or thrown exception) is the result of the `await` expression. -Because `connections` is a `Map`, `Object.keys` doesn't work on it. It -has a `keys` _method_, but that returns an iterator rather than an -array. An iterator (or iterable value) can be converted to an array -with the `Array.from` function. +## A Corvid Art Project -{{index "Promise class", recursion}} +{{index "Carla the crow"}} -Even with promises this is some rather awkward code. Multiple -asynchronous actions are chained together in non-obvious ways. We -again need a recursive function (`next`) to model looping through the -nests. +One morning, Carla wakes up to unfamiliar noise from the tarmac outside of her hangar. Hopping onto the edge of the roof, she sees the humans are setting up for something. There's a lot of electric cabling, a stage, and some kind of big black wall being built up. -{{index "synchronous programming", "asynchronous programming"}} +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”. -And the thing the code actually does is completely linear—it always -waits for the previous action to complete before starting the next -one. In a synchronous programming model, it'd be simpler to express. +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? -{{index "async function", "await keyword"}} +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. -The good news is that JavaScript allows you to write pseudo-synchronous -code to describe asynchronous computation. An `async` function is a -function that implicitly returns a -promise and that can, in its body, `await` other promises in a way -that _looks_ synchronous. +[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. -{{index "findInStorage function"}} +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. -We can rewrite `findInStorage` like this: +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. ``` -async function findInStorage(nest, name) { - let local = await storage(nest, name); - if (local != null) return local; - - let sources = network(nest).filter(n => n != nest.name); - while (sources.length > 0) { - let source = sources[Math.floor(Math.random() * - sources.length)]; - sources = sources.filter(n => n != source); - try { - let found = await routeRequest(nest, source, "storage", - name); - if (found != null) return found; - } catch (_) {} +for (let addr = 1; addr < 256; addr++) { + let data = []; + for (let n = 0; n < 1500; n++) { + data.push(n < addr ? 3 : 0); } - throw new Error("Not found"); + let ip = `10.0.0.${addr}`; + request(ip, {command: "display", data}) + .then(() => console.log(`Request to ${ip} accepted`)) + .catch(() => {}); } ``` -{{index "async function", "return keyword", "exception handling"}} - -An `async` function is marked by the word `async` before the -`function` keyword. Methods can also be made `async` by writing -`async` before their name. When such a function or method is called, -it returns a promise. As soon as the body returns something, that -promise is resolved. If it throws an exception, the promise is -rejected. +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. -{{if interactive +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: -```{startCode: true} -findInStorage(bigOak, "events on 2017-12-21") - .then(console.log); +```{includeCode: true} +const screenAddresses = [ + "10.0.0.44", "10.0.0.45", "10.0.0.41", + "10.0.0.31", "10.0.0.40", "10.0.0.42", + "10.0.0.48", "10.0.0.47", "10.0.0.46" +]; ``` -if}} - -{{index "await keyword", ["control flow", asynchronous]}} - -Inside an `async` function, the word `await` can be put in front of an -expression to wait for a promise to resolve and only then continue -the execution of the function. - -Such a function no longer, like a regular JavaScript function, runs -from start to completion in one go. Instead, it can be _frozen_ at any -point that has an `await`, and can be resumed at a later time. +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. -For non-trivial asynchronous code, this notation is usually more -convenient than directly using promises. Even if you need to do -something that doesn't fit the synchronous model, such as perform -multiple actions at the same time, it is easy to combine `await` with -the direct use of promises. +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. -## Generators - -{{index "async function"}} +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. -This ability of functions to be paused and then resumed again is not -exclusive to `async` functions. JavaScript also has a feature called -_((generator))_ functions. These are similar, but without the -promises. +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. -When you define a function with `function*` (placing an asterisk after -the word `function`), it becomes a generator. When you call a -generator, it returns an ((iterator)), which we already saw in -[Chapter ?](object). +{{index "Promise.all function"}} -``` -function* powers(n) { - for (let current = n;; current *= n) { - yield current; - } -} +`Promise` has a static method `all` that can be used to convert an array of promises into a single promise that resolves to an array of results. This provides a convenient way to have some asynchronous actions happen alongside each other, wait for them all to finish, and then do something with their results (or at least wait for them to make sure they don't fail). -for (let power of powers(3)) { - if (power > 50) break; - console.log(power); +```{includeCode: true} +function displayFrame(frame) { + return Promise.all(frame.map((data, i) => { + return request(screenAddresses[i], { + command: "display", + data + }); + })); } -// → 3 -// → 9 -// → 27 ``` -{{index "next method", "yield keyword"}} +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. -Initially, when you call `powers`, the function is frozen at its -start. Every time you call `next` on the iterator, the function runs -until it hits a `yield` expression, which pauses it and causes the -yielded value to become the next value produced by the iterator. When -the function returns (the one in the example never does), the iterator -is done. +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. -Writing iterators is often much easier when you use generator -functions. The iterator for the `Group` class (from the exercise in -[Chapter ?](object#group_iterator)) can be written with this -generator: +```{includeCode: true} +function wait(time) { + return new Promise(accept => setTimeout(accept, time)); +} -{{index "Group class"}} +class VideoPlayer { + constructor(frames, frameTime) { + this.frames = frames; + this.frameTime = frameTime; + this.stopped = true; + } -``` -Group.prototype[Symbol.iterator] = function*() { - for (let i = 0; i < this.members.length; i++) { - yield this.members[i]; + async play() { + this.stopped = false; + for (let i = 0; !this.stopped; i++) { + let nextFrame = wait(this.frameTime); + await displayFrame(this.frames[i % this.frames.length]); + await nextFrame; + } } -}; -``` -```{hidden: true, includeCode: true} -class Group { - constructor() { this.members = []; } - add(m) { this.members.add(m); } + stop() { + this.stopped = true; + } } ``` -{{index [state, in iterator]}} - -There's no longer a need to create an object to hold the iteration -state—generators automatically save their local state every time -they yield. - -Such `yield` expressions may occur only directly in the generator -function itself and not in an inner function you define inside of it. -The state a generator saves, when yielding, is only its _local_ -environment and the position where it yielded. +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. -{{index "await keyword"}} +```{startCode: true} +let video = new VideoPlayer(clipImages, 100); +video.play().catch(e => { + console.log("Playback failed: " + e); +}); +setTimeout(() => video.stop(), 15000); +``` -An `async` function is a special type of generator. It produces a -promise when called, which is resolved when it returns (finishes) and -rejected when it throws an exception. Whenever it yields (awaits) a -promise, the result of that promise (value or thrown exception) is the -result of the `await` expression. +For the entire week that the screen wall stands, every evening, when it is dark, a huge glowing orange bird mysteriously appears on it. ## The event loop {{index "asynchronous programming", scheduling, "event loop", timeline}} -Asynchronous programs are executed piece by piece. Each piece may -start some actions and schedule code to be executed when the action -finishes or fails. In between these pieces, the program sits idle, -waiting for the next action. +An asynchronous program starts by running its main script, which will often set up callbacks to be called later. That main script, as well as the callbacks, run to completion in one piece, uninterrupted. But between them, the program may sit idle, waiting for something to happen. {{index "setTimeout function"}} -So callbacks are not directly called by the code that scheduled them. -If I call `setTimeout` from within a function, that function will have -returned by the time the callback function is called. And when the -callback returns, control does not go back to the function that -scheduled it. +So callbacks are not directly called by the code that scheduled them. If I call `setTimeout` from within a function, that function will have returned by the time the callback function is called. And when the callback returns, control does not go back to the function that scheduled it. {{index "Promise class", "catch keyword", "exception handling"}} -Asynchronous behavior happens on its own empty function ((call -stack)). This is one of the reasons that, without promises, managing -exceptions across asynchronous code is hard. Since each callback -starts with a mostly empty stack, your `catch` handlers won't be on -the stack when they throw an exception. +Asynchronous behavior happens on its own empty function ((call stack)). This is one of the reasons that, without promises, managing exceptions across asynchronous code is so hard. Since each callback starts with a mostly empty stack, your `catch` handlers won't be on the stack when they throw an exception. ``` try { setTimeout(() => { throw new Error("Woosh"); }, 20); -} catch (_) { +} catch (e) { // This will not run - console.log("Caught!"); + console.log("Caught", e); } ``` {{index thread, queue}} -No matter how closely together events—such as timeouts or incoming -requests—happen, a JavaScript environment will run only one program at -a time. You can think of this as it running a big loop _around_ your -program, called the _event loop_. When there's nothing to be done, -that loop is stopped. But as events come in, they are added to a queue, -and their code is executed one after the other. Because no two things -run at the same time, slow-running code might delay the handling of -other events. +No matter how closely together events—such as timeouts or incoming requests—happen, a JavaScript environment will run only one program at a time. You can think of this as it running a big loop _around_ your program, called the _event loop_. When there's nothing to be done, that loop is paused. But as events come in, they are added to a queue, and their code is executed one after the other. Because no two things run at the same time, slow-running code can delay the handling of other events. -This example sets a timeout but then dallies until after the -timeout's intended point of time, causing the timeout to be late. +This example sets a timeout but then dallies until after the timeout's intended point of time, causing the timeout to be late. ``` let start = Date.now(); @@ -1119,9 +583,7 @@ console.log("Wasted time until", Date.now() - start); {{index "resolving (a promise)", "rejecting (a promise)", "Promise class"}} -Promises always resolve or reject as a new event. Even if a promise is -already resolved, waiting for it will cause your callback to run after -the current script finishes, rather than right away. +Promises always resolve or reject as a new event. Even if a promise is already resolved, waiting for it will cause your callback to run after the current script finishes, rather than right away. ``` Promise.resolve("Done").then(console.log); @@ -1130,37 +592,24 @@ console.log("Me first!"); // → Done ``` -In later chapters we'll see various other types of events that run on -the event loop. +In later chapters we'll see various other types of events that run on the event loop. ## Asynchronous bugs {{index "asynchronous programming", [state, transitions]}} -When your program runs synchronously, in a single go, there are no -state changes happening except those that the program itself -makes. For asynchronous programs this is different—they may have -_gaps_ in their execution during which other code can run. +When your program runs synchronously, in a single go, there are no state changes happening except those that the program itself makes. For asynchronous programs this is different—they may have _gaps_ in their execution during which other code can run. -Let's look at an example. One of the hobbies of our crows is to count -the number of chicks that hatch throughout the village every year. -Nests store this count in their storage bulbs. The following code tries to -enumerate the counts from all the nests for a given year: +Let's look at an example. This is a function that tries to report the size of each file in an array of files, making sure to read them all at the same time rather than in sequence. -{{index "anyStorage function", "chicks function"}} +{{index "fileSizes function"}} ```{includeCode: true} -function anyStorage(nest, source, name) { - if (source == nest.name) return storage(nest, name); - else return routeRequest(nest, source, "storage", name); -} - -async function chicks(nest, year) { +async function fileSizes(files) { let list = ""; - await Promise.all(network(nest).map(async name => { - list += `${name}: ${ - await anyStorage(nest, name, `chicks in ${year}`) - }\n`; + await Promise.all(files.map(async fileName => { + list += fileName + ": " + + (await textFile(fileName)).length + "\n"; })); return list; } @@ -1168,23 +617,19 @@ async function chicks(nest, year) { {{index "async function"}} -The `async name =>` part shows that ((arrow function))s can also be -made `async` by putting the word `async` in front of them. +The `async fileName =>` part shows how ((arrow function))s can also be made `async` by putting the word `async` in front of them. {{index "Promise.all function"}} -The code doesn't immediately look suspicious...it maps the `async` -arrow function over the set of nests, creating an array of promises, -and then uses `Promise.all` to wait for all of these before returning -the list they build up. +The code doesn't immediately look suspicious... it maps the `async` arrow function over the array of names, creating an array of promises, and then uses `Promise.all` to wait for all of these before returning the list they build up. -But it is seriously broken. It'll always return only a single line of -output, listing the nest that was slowest to respond. +But this program is entirely broken. It'll always return only a single line of output, listing the file that took the longest to read. {{if interactive ``` -chicks(bigOak, 2017).then(console.log); +fileSizes(["plans.txt", "shopping_list.txt"]) + .then(console.log); ``` if}} @@ -1193,143 +638,162 @@ 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 a single-line list—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 "chicks function"}} +{{index "fileSizes function"}} ``` -async function chicks(nest, year) { - let lines = network(nest).map(async name => { - return name + ": " + - await anyStorage(nest, name, `chicks in ${year}`); +async function fileSizes(files) { + let lines = files.map(async fileName => { + return fileName + ": " + + (await textFile(fileName)).length; }); return (await Promise.all(lines)).join("\n"); } ``` -Mistakes like this are easy to make, especially when using `await`, -and you should be aware of where the gaps in your code occur. An -advantage of JavaScript's _explicit_ asynchronicity (whether through -callbacks, promises, or `await`) is that spotting these gaps is -relatively easy. +Mistakes like this are easy to make, especially when using `await`, and you should be aware of where the gaps in your code occur. An advantage of JavaScript's _explicit_ asynchronicity (whether through callbacks, promises, or `await`) is that spotting these gaps is relatively easy. ## Summary -Asynchronous programming makes it possible to express waiting for -long-running actions without freezing the program during these -actions. JavaScript environments typically implement this style of -programming using callbacks, functions that are called when the -actions complete. An event loop schedules such callbacks to be called -when appropriate, one after the other, so that their execution does -not overlap. +Asynchronous programming makes it possible to express waiting for long-running actions without freezing the whole program. JavaScript environments typically implement this style of programming using callbacks, functions that are called when the actions complete. An event loop schedules such callbacks to be called when appropriate, one after the other, so that their execution does not overlap. -Programming asynchronously is made easier by promises, objects that -represent actions that might complete in the future, and `async` -functions, which allow you to write an asynchronous program as if it -were synchronous. +Programming asynchronously is made easier by promises, objects that represent actions that might complete in the future, and `async` functions, which allow you to write an asynchronous program as if it were synchronous. ## Exercises -### Tracking the scalpel +### Quiet Times -{{index "scalpel (exercise)"}} +{{index "quiet times (exercise)", "security camera", "Carla the crow", "async function"}} -The village crows own an old scalpel that they occasionally use on -special missions—say, to cut through screen doors or packaging. To be -able to quickly track it down, every time the scalpel is moved to -another nest, an entry is added to the storage of both the nest that -had it and the nest that took it, under the name `"scalpel"`, with its -new location as the value. +There's a security camera near Carla's lab that's activated by a motion sensor. It is connected to the network and starts sending out a video stream when it is active. Because she'd rather not be discovered, Carla has set up a system that notices this kind of wireless network traffic and turns on a light in her lair whenever there is activity outside, so she knows when to keep quiet. -This means that finding the scalpel is a matter of following the -breadcrumb trail of storage entries, until you find a nest where that -points at the nest itself. +{{index "Date class", "Date.now function", timestamp}} -{{index "anyStorage function", "async function"}} +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. -Write an `async` function `locateScalpel` that does this, starting at -the nest on which it runs. You can use the `anyStorage` function -defined earlier to access storage in arbitrary nests. The scalpel has -been going around long enough that you may assume that every nest has -a `"scalpel"` entry in its data storage. +```{lang: null} +1695709940692 +1695701068331 +1695701189163 +``` -Next, write the same function again without using `async` and `await`. +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. -{{index "exception handling"}} +The `activityGraph` function, provided by the sandbox, summarizes such a table into a string. -Do request failures properly show up as rejections of the returned -promise in both versions? How? +{{index "textFile function"}} + +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 logfiles and the logfiles themselves—have each piece of data on its own line, separated by newline (`"\n"`) characters. {{if interactive ```{test: no} -async function locateScalpel(nest) { - // Your code here. +async function activityTable(day) { + let logFileList = await textFile("camera_logs.txt"); + // Your code here } -function locateScalpel2(nest) { - // Your code here. +activityTable(1) + .then(table => console.log(activityGraph(table))); +``` + +if}} + +{{hint + +{{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 logfiles, that will still give you an array of strings, which you have to convert to numbers before passing them to `new Date`. + +Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the logfiles and the numbers in every 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"}} + +Make sure you use `await` on the result of asynchronous functions before doing anything with it, or you'll end up with a `Promise` where you expected a string. + +hint}} + + +### Real Promises + +{{index "real promises (exercise)", "Promise class"}} + +Rewrite the function from the previous exercise without `async`/`await`, using plain `Promise` methods. + +{{if interactive + +```{test: no} +function activityTable(day) { + // Your code here } -locateScalpel(bigOak).then(console.log); -// → Butcher Shop +activityTable(6) + .then(table => console.log(activityGraph(table))); ``` 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 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)"}} + +If one of the files listed in the file list has a typo, and reading it fails, how does that failure end up in the `Promise` object that your function returns? + {{hint -{{index "scalpel (exercise)"}} +{{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 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`. -This can be done with a single loop that searches through the nests, -moving forward to the next when it finds a value that doesn't match -the current nest's name and returning the name when it finds a -matching value. In the `async` function, a regular `for` or `while` -loop can be used. +{{index "asynchronous programming"}} + +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) { + return textFile("camera_logs.txt").then(files => { + return Promise.all(files.split("\n").map(textFile)); + }).then(logs => { + // analyze... + }); +} +``` -{{index recursion}} +Or you could, for even better work scheduling, put the analysis of each file inside of the `Promise.all`, so that that work can be started for the first file that comes back from disk, even before the other files come back. -To do the same in a plain function, you will have to build your loop -using a recursive function. The easiest way to do this is to have that -function return a promise by calling `then` on the promise that -retrieves the storage value. Depending on whether that value matches -the name of the current nest, the handler returns that value or a -further promise created by calling the loop function again. +```{test: no} +function activityTable(day) { + let table = []; // init... + return textFile("camera_logs.txt").then(files => { + return Promise.all(files.split("\n").map(name => { + return textFile(name).then(log => { + // analyze... + }); + })); + }).then(() => table); +} +``` -Don't forget to start the loop by calling the recursive function once -from the main function. +{{index "await keyword", scheduling}} -{{index "exception handling"}} +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. -In the `async` function, rejected promises are converted to exceptions -by `await`. When an `async` function throws an exception, its promise -is rejected. So that works. +{{index "rejecting (a promise)", "then method"}} -If you implemented the non-`async` function as outlined earlier, the way -`then` works also automatically causes a failure to end up in the -returned promise. If a request fails, the handler passed to `then` -isn't called, and the promise it returns is rejected with the same -reason. +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}} @@ -1337,19 +801,11 @@ hint}} {{index "Promise class", "Promise.all function", "building Promise.all (exercise)"}} -Given an array of ((promise))s, `Promise.all` returns a promise that -waits for all of the promises in the array to finish. It then -succeeds, yielding an array of result values. If a promise -in the array fails, the promise returned by `all` fails too, with the -failure reason from the failing promise. +As we saw, given an array of ((promise))s, `Promise.all` returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by `all` fails too, passing on the failure reason from the failing promise. -Implement something like this yourself as a regular function -called `Promise_all`. +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 @@ -1389,25 +845,12 @@ if}} {{index "Promise.all function", "Promise class", "then method", "building Promise.all (exercise)"}} -The function passed to the `Promise` constructor will have to call -`then` on each of the promises in the given array. When one of them -succeeds, two things need to happen. The resulting value needs to be -stored in the correct position of a result array, and we must check -whether this was the last pending ((promise)) and finish our own -promise if it was. +The function passed to the `Promise` constructor will have to call `then` on each of the promises in the given array. When one of them succeeds, two things need to happen. The resulting value needs to be stored in the correct position of a result array, and we must check whether this was the last pending ((promise)) and finish our own promise if it was. {{index "counter variable"}} -The latter can be done with a counter that is initialized to the -length of the input array and from which we subtract 1 every time a -promise succeeds. When it reaches 0, we are done. Make sure you take -into account the situation where the input array is empty (and thus no -promise will ever resolve). - -Handling failure requires some thought but turns out to be extremely -simple. Just pass the `reject` function of the wrapping promise to -each of the promises in the array as a `catch` handler or as a second -argument to `then` so that a failure in one of them triggers the -rejection of the whole wrapper promise. +The latter can be done with a counter that is initialized to the length of the input array and from which we subtract 1 every time a promise succeeds. When it reaches 0, we are done. Make sure you take into account the situation where the input array is empty (and thus no promise will ever resolve). + +Handling failure requires some thought but turns out to be extremely simple. Just pass the `reject` function of the wrapping promise to each of the promises in the array as a `catch` handler or as a second argument to `then` so that a failure in one of them triggers the rejection of the whole wrapper promise. hint}} diff --git a/12_language.md b/12_language.md index 4e4413398..04a287336 100644 --- a/12_language.md +++ b/12_language.md @@ -4,30 +4,21 @@ {{quote {author: "Hal Abelson and Gerald Sussman", title: "Structure and Interpretation of Computer Programs", chapter: true} -The evaluator, which determines the meaning of expressions in a -programming language, is just another program. +The evaluator, which determines the meaning of expressions in a programming language, is just another program. quote}} {{index "Abelson, Hal", "Sussman, Gerald", SICP, "project chapter"}} -{{figure {url: "img/chapter_picture_12.jpg", alt: "Picture of an egg with smaller eggs inside", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_12.jpg", alt: "Illustration showing an egg with holes in it, showing smaller eggs inside, which in turn have even smaller eggs in them, and so on", chapter: "framed"}}} -Building your own ((programming language)) is surprisingly easy (as -long as you do not aim too high) and very enlightening. +Building your own ((programming language)) is surprisingly easy (as long as you do not aim too high) and very enlightening. -The main thing I want to show in this chapter is that there is no -((magic)) involved in building your own language. I've often felt that -some human inventions were so immensely clever and complicated that -I'd never be able to understand them. But with a little reading and -experimenting, they often turn out to be quite mundane. +The main thing I want to show in this chapter is that there's no ((magic)) involved in building a programming language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane. {{index "Egg language", [abstraction, "in Egg"]}} -We will build a programming language called Egg. It will be a tiny, -simple language—but one that is powerful enough to express any -computation you can think of. It will allow simple ((abstraction)) -based on ((function))s. +We will build a programming language called Egg. It will be a tiny, simple language—but one that is powerful enough to express any computation you can think of. It will allow simple ((abstraction)) based on ((function))s. {{id parsing}} @@ -35,32 +26,19 @@ based on ((function))s. {{index parsing, validation, [syntax, "of Egg"]}} -The most immediately visible part of a programming language is its -_syntax_, or notation. A _parser_ is a program that reads a piece -of text and produces a data structure that reflects the structure of -the program contained in that text. If the text does not form a valid -program, the parser should point out the error. +The most immediately visible part of a programming language is its _syntax_, or notation. A _parser_ is a program that reads a piece of text and produces a data structure that reflects the structure of the program contained in that text. If the text does not form a valid program, the parser should point out the error. {{index "special form", [function, application]}} -Our language will have a simple and uniform syntax. Everything in Egg -is an ((expression)). An expression can be the name of a binding, a -number, a string, or an _application_. Applications are used for -function calls but also for constructs such as `if` or `while`. +Our language will have a simple and uniform syntax. Everything in Egg is an ((expression)). An expression can be the name of a binding, a number, a string, or an _application_. Applications are used for function calls but also for constructs such as `if` or `while`. {{index "double-quote character", parsing, [escaping, "in strings"], [whitespace, syntax]}} -To keep the parser simple, strings in Egg do not support anything like -backslash escapes. A string is simply a sequence of characters that -are not double quotes, wrapped in double quotes. A number is a -sequence of digits. Binding names can consist of any character that is -not whitespace and that does not have a special meaning in the syntax. +To keep the parser simple, strings in Egg do not support anything like backslash escapes. A string is simply a sequence of characters that are not double quotes, wrapped in double quotes. A number is a sequence of digits. Binding names can consist of any character that is not whitespace and that does not have a special meaning in the syntax. {{index "comma character", [parentheses, arguments]}} -Applications are written the way they are in JavaScript, by putting -parentheses after an expression and having any number of -((argument))s between those parentheses, separated by commas. +Applications are written the way they are in JavaScript, by putting parentheses after an expression and having any number of ((argument))s between those parentheses, separated by commas. ```{lang: null} do(define(x, 10), @@ -71,33 +49,19 @@ do(define(x, 10), {{index block, [syntax, "of Egg"]}} -The ((uniformity)) of the ((Egg language)) means that things that are -((operator))s in JavaScript (such as `>`) are normal bindings in this -language, applied just like other ((function))s. And since the -syntax has no concept of a block, we need a `do` construct to -represent doing multiple things in sequence. +The ((uniformity)) of the ((Egg language)) means that things that are ((operator))s in JavaScript (such as `>`) are normal bindings in this language, applied just like other ((function))s. Since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence. {{index "type property", parsing, ["data structure", tree]}} -The data structure that the parser will use to describe a program -consists of ((expression)) objects, each of which has a `type` -property indicating the kind of expression it is and other properties -to describe its content. +The data structure that the parser will use to describe a program consists of ((expression)) objects, each of which has a `type` property indicating the kind of expression it is and other properties to describe its content. {{index identifier}} -Expressions of type `"value"` represent literal strings or numbers. -Their `value` property contains the string or number value that they -represent. Expressions of type `"word"` are used for identifiers -(names). Such objects have a `name` property that holds the -identifier's name as a string. Finally, `"apply"` expressions -represent applications. They have an `operator` property that refers -to the expression that is being applied, as well as an `args` property that -holds an array of argument expressions. +Expressions of type `"value"` represent literal strings or numbers. Their `value` property contains the string or number value that they represent. Expressions of type `"word"` are used for identifiers (names). Such objects have a `name` property that holds the identifier's name as a string. Finally, `"apply"` expressions represent applications. They have an `operator` property that refers to the expression that is being applied, as well as an `args` property that holds an array of argument expressions. The `>(x, 5)` part of the previous program would be represented like this: -```{lang: "application/json"} +```{lang: "json"} { type: "apply", operator: {type: "word", name: ">"}, @@ -110,44 +74,25 @@ The `>(x, 5)` part of the previous program would be represented like this: {{indexsee "abstract syntax tree", "syntax tree", ["data structure", tree]}} -Such a data structure is called a _((syntax tree))_. If you -imagine the objects as dots and the links between them as lines -between those dots, it has a ((tree))like shape. The fact that -expressions contain other expressions, which in turn might contain -more expressions, is similar to the way tree branches split and split -again. +Such a data structure is called a _((syntax tree))_. If you imagine the objects as dots and the links between them as lines between those dots, as shown in the following diagram, the structure has a ((tree))like shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again. -{{figure {url: "img/syntax_tree.svg", alt: "The structure of a syntax tree",width: "5cm"}}} +{{figure {url: "img/syntax_tree.svg", alt: "A diagram showing the structure of the syntax tree for the example program. The root is labeled 'do' and has two children, one labeled 'define' and one labeled 'if'. Those in turn have more children, describing their content.", width: "5cm"}}} {{index parsing}} -Contrast this to the parser we wrote for the configuration file format -in [Chapter ?](regexp#ini), which had a simple structure: it split the -input into lines and handled those lines one at a time. There were -only a few simple forms that a line was allowed to have. +Contrast this to the parser we wrote for the configuration file format in [Chapter ?](regexp#ini), which had a simple structure: it split the input into lines and handled those lines one at a time. There were only a few simple forms that a line was allowed to have. {{index recursion, [nesting, "of expressions"]}} -Here we must find a different approach. Expressions are not separated -into lines, and they have a recursive structure. Application -expressions _contain_ other expressions. +Here we must find a different approach. Expressions are not separated into lines, and they have a recursive structure. Application expressions _contain_ other expressions. {{index elegance}} -Fortunately, this problem can be solved very well by writing a parser -function that is recursive in a way that reflects the recursive nature -of the language. +Fortunately, this problem can be solved very well by writing a parser function that is recursive in a way that reflects the recursive nature of the language. {{index "parseExpression function", "syntax tree"}} -We define a function `parseExpression`, which takes a string as input -and returns an object containing the data structure for the expression -at the start of the string, along with the part of the string left -after parsing this expression. When parsing subexpressions (the -argument to an application, for example), this function can be called -again, yielding the argument expression as well as the text that -remains. This text may in turn contain more arguments or may be the -closing parenthesis that ends the list of arguments. +We define a function `parseExpression` that takes a string as input. It returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments. This is the first part of the parser: @@ -177,30 +122,15 @@ function skipSpace(string) { {{index "skipSpace function", [whitespace, syntax]}} -Because Egg, like JavaScript, allows any amount of whitespace -between its elements, we have to repeatedly cut the whitespace off the -start of the program string. That is what the `skipSpace` function -helps with. +Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. The `skipSpace` function helps with this. {{index "literal expression", "SyntaxError type"}} -After skipping any leading space, `parseExpression` uses three -((regular expression))s to spot the three atomic elements that Egg -supports: strings, numbers, and words. The parser constructs a -different kind of data structure depending on which one matches. If -the input does not match one of these three forms, it is not a valid -expression, and the parser throws an error. We use `SyntaxError` -instead of `Error` as the exception constructor, which is another standard -error type, because it is a little more specific—it is also the error -type thrown when an attempt is made to run an invalid JavaScript -program. +After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which expression matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use the `SyntaxError` constructor here. This is an exception class defined by the standard, like `Error`, but more specific. {{index "parseApply function"}} -We then cut off the part that was matched from the program string and -pass that, along with the object for the expression, to `parseApply`, -which checks whether the expression is an application. If so, it -parses a parenthesized list of arguments. +We then cut off the part that was matched from the program string and pass that, along with the object for the expression, to `parseApply`, which checks whether the expression is an application. If so, it parses a parenthesized list of arguments. ```{includeCode: true} function parseApply(expr, program) { @@ -225,31 +155,15 @@ function parseApply(expr, program) { } ``` -{{index parsing}} - -If the next character in the program is not an opening parenthesis, -this is not an application, and `parseApply` returns the expression it -was given. +{{index parsing, recursion}} -{{index recursion}} +If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` returns the expression it was given. Otherwise, it skips the opening parenthesis and creates the ((syntax tree)) object for this application expression. It then recursively calls `parseExpression` to parse each argument until a closing parenthesis is found. The recursion is indirect, through `parseApply` and `parseExpression` calling each other. -Otherwise, it skips the opening parenthesis and creates the ((syntax -tree)) object for this application expression. It then recursively -calls `parseExpression` to parse each argument until a closing -parenthesis is found. The recursion is indirect, through `parseApply` -and `parseExpression` calling each other. - -Because an application expression can itself be applied (such as in -`multiplier(2)(1)`), `parseApply` must, after it has parsed an -application, call itself again to check whether another pair of -parentheses follows. +Because an application expression can itself be applied (such as in `multiplier(2)(1)`), `parseApply` must, after it has parsed an application, call itself again to check whether another pair of parentheses follows. {{index "syntax tree", "Egg language", "parse function"}} -This is all we need to parse Egg. We wrap it in a convenient `parse` -function that verifies that it has reached the end of the input string -after parsing the expression (an Egg program is a single expression), -and that gives us the program's data structure. +This is all we need to parse Egg. We wrap it in a convenient `parse` function that verifies that it has reached the end of the input string after parsing the expression (an Egg program is a single expression), and that gives us the program's data structure. ```{includeCode: strip_log, test: join} function parse(program) { @@ -269,20 +183,13 @@ console.log(parse("+(a, 10)")); {{index "error message"}} -It works! It doesn't give us very helpful information when it fails -and doesn't store the line and column on which each expression starts, -which might be helpful when reporting errors later, but it's good -enough for our purposes. +It works! It doesn't give us very helpful information when it fails and doesn't store the line and column on which each expression starts, which might be helpful when reporting errors later, but it's good enough for our purposes. ## The evaluator {{index "evaluate function", evaluation, interpretation, "syntax tree", "Egg language"}} -What can we do with the syntax tree for a program? Run it, of course! -And that is what the evaluator does. You give it a syntax tree and a -scope object that associates names with values, and it will evaluate -the expression that the tree represents and return the value that this -produces. +What can we do with the syntax tree for a program? Run it, of course! And that is what the evaluator does. You give it a syntax tree and a scope object that associates names with values, and it will evaluate the expression that the tree represents and return the value that this produces. ```{includeCode: true} const specialForms = Object.create(null); @@ -316,45 +223,27 @@ function evaluate(expr, scope) { {{index "literal expression", scope}} -The evaluator has code for each of the ((expression)) types. A literal -value expression produces its value. (For example, the expression -`100` just evaluates to the number 100.) For a binding, we must check -whether it is actually defined in the scope and, if it is, fetch the -binding's value. +The evaluator has code for each of the ((expression)) types. A literal value expression produces its value. (For example, the expression `100` evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding's value. {{index [function, application]}} -Applications are more involved. If they are a ((special form)), like -`if`, we do not evaluate anything and pass the argument expressions, -along with the scope, to the function that handles this form. If it is -a normal call, we evaluate the operator, verify that it is a function, -and call it with the evaluated arguments. +Applications are more involved. If they are a ((special form)), like `if`, we do not evaluate anything—we just and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments. -We use plain JavaScript function values to represent Egg's function -values. We will come back to this [later](language#egg_fun), when the -special form called `fun` is defined. +We use plain JavaScript function values to represent Egg's function values. We will come back to this [later](language#egg_fun), when the special form `fun` is defined. {{index readability, "evaluate function", recursion, parsing}} -The recursive structure of `evaluate` resembles the similar structure -of the parser, and both mirror the structure of the language itself. -It would also be possible to integrate the parser with the evaluator -and evaluate during parsing, but splitting them up this way makes the -program clearer. +The recursive structure of `evaluate` resembles the structure of the parser, and both mirror the structure of the language itself. It would also be possible to combine the parser and the evaluator into one function and evaluate during parsing, but splitting them up this way makes the program clearer and more flexible. {{index "Egg language", interpretation}} -This is really all that is needed to interpret Egg. It is that simple. -But without defining a few special forms and adding some useful values -to the ((environment)), you can't do much with this language yet. +This is really all that's needed to interpret Egg. It's that simple. But without defining a few special forms and adding some useful values to the ((environment)), you can't do much with this language yet. ## Special forms {{index "special form", "specialForms object"}} -The `specialForms` object is used to define special syntax in Egg. It -associates words with functions that evaluate such forms. It is -currently empty. Let's add `if`. +The `specialForms` object is used to define special syntax in Egg. It associates words with functions that evaluate such forms. It is currently empty. Let's add `if`. ```{includeCode: true} specialForms.if = (args, scope) => { @@ -370,26 +259,15 @@ 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}} -Egg also differs from JavaScript in how it handles the condition value -to `if`. It will not treat things like zero or the empty string as -false, only the precise value `false`. +Egg also differs from JavaScript in how it handles the condition value to `if`. It will treat only the value `false` as false, not things like zero or the empty string. {{index "short-circuit evaluation"}} -The reason we need to represent `if` as a special form, rather than a -regular function, is that all arguments to functions are evaluated -before the function is called, whereas `if` should evaluate only -_either_ its second or its third argument, depending on the value of -the first. +The reason we need to represent `if` as a special form rather than a regular function is that all arguments to functions are evaluated before the function is called, whereas `if` should evaluate only _either_ its second or its third argument, depending on the value of the first. The `while` form is similar. @@ -403,14 +281,12 @@ 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; }; ``` -Another basic building block is `do`, which executes all its arguments -from top to bottom. Its value is the value produced by the last -argument. +Another basic building block is `do`, which executes all its arguments from top to bottom. Its value is the value produced by the last argument. ```{includeCode: true} specialForms.do = (args, scope) => { @@ -424,12 +300,7 @@ specialForms.do = (args, scope) => { {{index ["= operator", "in Egg"], [binding, "in Egg"]}} -To be able to create bindings and give them new values, we also -create a form called `define`. It expects a word as its first argument -and an expression producing the value to assign to that word as its -second argument. Since `define`, like everything, is an expression, it -must return a value. We'll make it return the value that was assigned -(just like JavaScript's `=` operator). +To be able to create bindings and give them new values, we also create a form called `define`. It expects a word as its first argument and an expression producing the value to assign to that word as its second argument. Since `define`, like everything, is an expression, it must return a value. We'll make it return the value that was assigned (just like JavaScript's `=` operator). ```{includeCode: true} specialForms.define = (args, scope) => { @@ -446,15 +317,9 @@ specialForms.define = (args, scope) => { {{index "Egg language", "evaluate function", [binding, "in Egg"]}} -The ((scope)) accepted by `evaluate` is an object with properties -whose names correspond to binding names and whose values correspond to -the values those bindings are bound to. Let's define an object to -represent the ((global scope)). +The ((scope)) accepted by `evaluate` is an object with properties whose names correspond to binding names and whose values correspond to the values those bindings are bound to. Let's define an object to represent the ((global scope)). -To be able to use the `if` construct we just defined, we must have -access to ((Boolean)) values. Since there are only two Boolean values, -we do not need special syntax for them. We simply bind two names to -the values `true` and `false` and use them. +To be able to use the `if` construct we just defined, we must have access to ((Boolean)) values. Since there are only two Boolean values, we do not need special syntax for them. We simply bind two names to the values `true` and `false` and use them. ```{includeCode: true} const topScope = Object.create(null); @@ -473,11 +338,7 @@ console.log(evaluate(prog, topScope)); {{index arithmetic, "Function constructor"}} -To supply basic ((arithmetic)) and ((comparison)) ((operator))s, we -will also add some function values to the ((scope)). In the interest -of keeping the code short, we'll use `Function` to synthesize a bunch -of operator functions in a loop, instead of defining them -individually. +To supply basic ((arithmetic)) and ((comparison)) ((operator))s, we will also add some function values to the ((scope)). In the interest of keeping the code short, we'll use `Function` to synthesize a bunch of operator functions in a loop instead of defining them individually. ```{includeCode: true} for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { @@ -485,8 +346,7 @@ for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { } ``` -A way to ((output)) values is also useful, so we'll wrap -`console.log` in a function and call it `print`. +It is also useful to have a way to ((output)) values, so we'll wrap `console.log` in a function and call it `print`. ```{includeCode: true} topScope.print = value => { @@ -497,9 +357,7 @@ topScope.print = value => { {{index parsing, "run function"}} -That gives us enough elementary tools to write simple programs. The -following function provides a convenient way to parse a program and -run it in a fresh scope: +That gives us enough elementary tools to write simple programs. The following function provides a convenient way to parse a program and run it in a fresh scope: ```{includeCode: true} function run(program) { @@ -509,9 +367,7 @@ function run(program) { {{index "Object.create function", prototype}} -We'll use object prototype chains to represent nested scopes so that -the program can add bindings to its local scope without changing the -top-level scope. +We'll use object prototype chains to represent nested scopes so that the program can add bindings to its local scope without changing the top-level scope. ``` run(` @@ -527,10 +383,7 @@ do(define(total, 0), {{index "summing example", "Egg language"}} -This is the program we've seen several times before, which computes -the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier -than the equivalent JavaScript program—but not bad for a language -implemented in less than 150 ((lines of code)). +This is the program we've seen several times before that computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in fewer than 150 ((lines of code)). {{id egg_fun}} @@ -538,12 +391,7 @@ implemented in less than 150 ((lines of code)). {{index function, "Egg language"}} -A programming language without functions is a poor programming -language indeed. - -Fortunately, it isn't hard to add a `fun` construct, which treats its -last argument as the function's body and uses all arguments before -that as the names of the function's parameters. +A programming language without functions is a poor programming language indeed. Fortunately, it isn't hard to add a `fun` construct, which treats its last argument as the function's body and uses all arguments before that as the names of the function's parameters. ```{includeCode: true} specialForms.fun = (args, scope) => { @@ -558,13 +406,13 @@ specialForms.fun = (args, scope) => { return expr.name; }); - return function() { - if (arguments.length != params.length) { + return function(...args) { + if (args.length != params.length) { throw new TypeError("Wrong number of arguments"); } let localScope = Object.create(scope); - for (let i = 0; i < arguments.length; i++) { - localScope[params[i]] = arguments[i]; + for (let i = 0; i < args.length; i++) { + localScope[params[i]] = args[i]; } return evaluate(body, localScope); }; @@ -573,10 +421,7 @@ specialForms.fun = (args, scope) => { {{index "local scope"}} -Functions in Egg get their own local scope. The function produced by -the `fun` form creates this local scope and adds the argument bindings -to it. It then evaluates the function body in this scope and returns -the result. +Functions in Egg get their own local scope. The function produced by the `fun` form creates this local scope and adds the argument bindings to it. It then evaluates the function body in this scope and returns the result. ```{startCode: true} run(` @@ -599,94 +444,49 @@ do(define(pow, fun(base, exp, {{index interpretation, compilation}} -What we have built is an interpreter. During evaluation, it acts -directly on the representation of the program produced by the parser. +What we have built is an interpreter. During evaluation, it acts directly on the representation of the program produced by the parser. {{index efficiency, performance, [binding, definition], [memory, speed]}} -_Compilation_ is the process of adding another step between the -parsing and the running of a program, which transforms the program -into something that can be evaluated more efficiently by doing as much -work as possible in advance. For example, in well-designed languages -it is obvious, for each use of a binding, which binding is being -referred to, without actually running the program. This can be used to -avoid looking up the binding by name every time it is accessed, -instead directly fetching it from some predetermined memory -location. - -Traditionally, ((compilation)) involves converting the program to -((machine code)), the raw format that a computer's processor can -execute. But any process that converts a program to a different -representation can be thought of as compilation. +_Compilation_ is the process of adding another step between the parsing and the running of a program, which transforms the program into something that can be evaluated more efficiently by doing as much work as possible in advance. For example, in well-designed languages it is obvious, for each use of a binding, which binding is being referred to, without actually running the program. This can be used to avoid looking up the binding by name every time it is accessed, instead directly fetching it from some predetermined memory location. + +Traditionally, ((compilation)) involves converting the program to ((machine code)), the raw format that a computer's processor can execute. But any process that converts a program to a different representation can be thought of as compilation. {{index simplicity, "Function constructor", transpilation}} -It would be possible to write an alternative ((evaluation)) strategy -for Egg, one that first converts the program to a JavaScript program, -uses `Function` to invoke the JavaScript compiler on it, and then runs -the result. When done right, this would make Egg run very fast while -still being quite simple to implement. +It would be possible to write an alternative ((evaluation)) strategy for Egg, one that first converts the program to a JavaScript program, uses `Function` to invoke the JavaScript compiler on it, and runs the result. When done right, this would make Egg run very fast while still being quite simple to implement. -If you are interested in this topic and willing to spend some time on -it, I encourage you to try to implement such a compiler as an -exercise. +If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise. ## Cheating {{index "Egg language"}} -When we defined `if` and `while`, you probably noticed that they were -more or less trivial wrappers around JavaScript's own `if` and -`while`. Similarly, the values in Egg are just regular old JavaScript -values. - -If you compare the implementation of Egg, built on top of JavaScript, -with the amount of work and complexity required to build a programming -language directly on the raw functionality provided by a machine, the -difference is huge. Regardless, this example ideally gave you an -impression of the way ((programming language))s work. +When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. Bridging the gap to a more primitive system, such as the machine code the processor understands, takes more effort—but the way it works resembles what we are doing here. -And when it comes to getting something done, cheating is more -effective than doing everything yourself. Though the toy language in -this chapter doesn't do anything that couldn't be done better in -JavaScript, there _are_ situations where writing small languages helps -get real work done. +Though the toy language in this chapter doesn't do anything that couldn't be done better in JavaScript, there _are_ situations where writing small languages helps get real work done. -Such a language does not have to resemble a typical programming -language. If JavaScript didn't come equipped with regular expressions, -for example, you could write your own parser and evaluator for regular -expressions. +Such a language does not have to resemble a typical programming language. If JavaScript didn't come equipped with regular expressions, for example, you could write your own parser and evaluator for regular expressions. -{{index "artificial intelligence"}} +{{index "parser generator"}} -Or imagine you are building a giant robotic ((dinosaur)) and need to -program its ((behavior)). JavaScript might not be the most effective -way to do this. You might instead opt for a language that looks like -this: +Or imagine you are building a program that makes it possible to quickly create parsers by providing a logical description of the language they need to parse. You could define a specific notation for that, and a compiler that compiles it to a parser program. ```{lang: null} -behavior walk - perform when - destination ahead - actions - move left-foot - move right-foot - -behavior attack - perform when - Godzilla in-view - actions - fire laser-eyes - launch arm-rockets +expr = number | string | name | application + +number = digit+ + +name = letter+ + +string = '"' (! '"')* '"' + +application = expr '(' (expr (',' expr)*)? ')' ``` {{index expressivity}} -This is what is usually called a _((domain-specific language))_, a -language tailored to express a narrow domain of knowledge. Such a -language can be more expressive than a general-purpose language -because it is designed to describe exactly the things that need to be -described in its domain, and nothing else. +This is what is usually called a _((domain-specific language))_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain and nothing else. ## Exercises @@ -694,11 +494,7 @@ described in its domain, and nothing else. {{index "Egg language", "arrays in egg (exercise)", [array, "in Egg"]}} -Add support for arrays to Egg by adding the following three -functions to the top scope: `array(...values)` to construct an array -containing the argument values, `length(array)` to get an array's -length, and `element(array, n)` to fetch the n^th^ element from an -array. +Add support for arrays to Egg by adding the following three functions to the top scope: `array(...values)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the *n*th element from an array. {{if interactive @@ -730,14 +526,11 @@ if}} {{index "arrays in egg (exercise)"}} -The easiest way to do this is to represent Egg arrays with JavaScript -arrays. +The easiest way to do this is to represent Egg arrays with JavaScript arrays. {{index "slice method"}} -The values added to the top scope must be functions. By using a rest -argument (with triple-dot notation), the definition of `array` can be -_very_ simple. +The values added to the top scope must be functions. By using a rest argument (with triple-dot notation), the definition of `array` can be _very_ simple. hint}} @@ -745,15 +538,9 @@ hint}} {{index closure, [function, scope], "closure in egg (exercise)"}} -The way we have defined `fun` allows functions in Egg to reference -the surrounding scope, allowing the function's body to use local -values that were visible at the time the function was defined, just -like JavaScript functions do. +The way we have defined `fun` allows functions in Egg to reference the surrounding scope, allowing the function's body to use local values that were visible at the time the function was defined, just like JavaScript functions do. -The following program illustrates this: function `f` returns a -function that adds its argument to `f`'s argument, meaning that it -needs access to the local ((scope)) inside `f` to be able to use -binding `a`. +The following program illustrates this: function `f` returns a function that adds its argument to `f`'s argument, meaning that it needs access to the local ((scope)) inside `f` to be able to use binding `a`. ``` run(` @@ -763,27 +550,17 @@ do(define(f, fun(a, fun(b, +(a, b)))), // → 9 ``` -Go back to the definition of the `fun` form and explain which -mechanism causes this to work. +Go back to the definition of the `fun` form and explain which mechanism causes this to work. {{hint {{index closure, "closure in egg (exercise)"}} -Again, we are riding along on a JavaScript mechanism to get the -equivalent feature in Egg. Special forms are passed the local scope in -which they are evaluated so that they can evaluate their subforms in -that scope. The function returned by `fun` has access to the `scope` -argument given to its enclosing function and uses that to create the -function's local ((scope)) when it is called. +Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local scope in which they are evaluated so that they can evaluate their subforms in that scope. The function returned by `fun` has access to the `scope` argument given to its enclosing function and uses that to create the function's local ((scope)) when it is called. {{index compilation}} -This means that the ((prototype)) of the local scope will be the scope -in which the function was created, which makes it possible to access -bindings in that scope from the function. This is all there is to -implementing closure (though to compile it in a way that is actually -efficient, you'd need to do some more work). +This means that the ((prototype)) of the local scope will be the scope in which the function was created, which makes it possible to access bindings in that scope from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you'd need to do some more work). hint}} @@ -791,16 +568,11 @@ hint}} {{index "hash character", "Egg language", "comments in egg (exercise)"}} -It would be nice if we could write ((comment))s in Egg. For example, -whenever we find a hash sign (`#`), we could treat the rest of the -line as a comment and ignore it, similar to `//` in JavaScript. +It would be nice if we could write ((comment))s in Egg. For example, whenever we find a hash sign (`#`), we could treat the rest of the line as a comment and ignore it, similar to `//` in JavaScript. {{index "skipSpace function"}} -We do not have to make any big changes to the parser to support this. -We can simply change `skipSpace` to skip comments as if they are -((whitespace)) so that all the points where `skipSpace` is called will -now also skip comments. Make this change. +We do not have to make any big changes to the parser to support this. We can simply change `skipSpace` to skip comments as if they are ((whitespace)) so that all the points where `skipSpace` is called will now also skip comments. Make this change. {{if interactive @@ -826,14 +598,9 @@ 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. +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. hint}} @@ -841,36 +608,19 @@ hint}} {{index [binding, definition], assignment, "fixing scope (exercise)"}} -Currently, the only way to assign a binding a value is `define`. -This construct acts as a way both to define new bindings and to give -existing ones a new value. +Currently, the only way to assign a binding a value is `define`. This construct acts as a way both to define new bindings and to give existing ones a new value. {{index "local binding"}} -This ((ambiguity)) causes a problem. When you try to give a nonlocal -binding a new value, you will end up defining a local one with the -same name instead. Some languages work like this by design, but I've -always found it an awkward way to handle ((scope)). +This ((ambiguity)) causes a problem. When you try to give a nonlocal binding a new value, you will end up defining a local one with the same name instead. Some languages work like this by design, but I've always found it an awkward way to handle ((scope)). {{index "ReferenceError type"}} -Add a special form `set`, similar to `define`, which gives a binding a -new value, updating the binding in an outer scope if it doesn't -already exist in the inner scope. If the binding is not defined at -all, throw a `ReferenceError` (another standard error type). +Add a special form `set`, similar to `define`, which gives a binding a new value, updating the binding in an outer scope if it doesn't already exist in the inner scope. If the binding is not defined at all, throw a `ReferenceError` (another standard error type). -{{index "hasOwnProperty method", prototype, "getPrototypeOf function"}} +{{index "hasOwn function", prototype, "getPrototypeOf function"}} -The technique of representing scopes as simple objects, which has made -things convenient so far, will get in your way a little at this point. -You might want to use the `Object.getPrototypeOf` function, which -returns the prototype of an object. Also remember that scopes do not -derive from `Object.prototype`, so if you want to call -`hasOwnProperty` on them, you have to use this clumsy expression: - -```{test: no} -Object.prototype.hasOwnProperty.call(scope, name); -``` +The technique of representing scopes as simple objects, which has made things convenient so far, will get in your way a little at this point. You might want to use the `Object.getPrototypeOf` function, which returns the prototype of an object. Also remember that you can use `Object.hasOwn` to find out if a given object has a property. {{if interactive @@ -893,19 +643,12 @@ if}} {{hint -{{index [binding, "compilation of"], assignment, "getPrototypeOf function", "hasOwnProperty method", "fixing scope (exercise)"}} +{{index [binding, "compilation of"], assignment, "getPrototypeOf function", "hasOwn function", "fixing scope (exercise)"}} -You will have to loop through one ((scope)) at a time, using -`Object.getPrototypeOf` to go to the next outer scope. For each scope, -use `hasOwnProperty` to find out whether the binding, indicated by the -`name` property of the first argument to `set`, exists in that scope. -If it does, set it to the result of evaluating the second argument to -`set` and then return that value. +You will have to loop through one ((scope)) at a time, using `Object.getPrototypeOf` to go to the next outer scope. For each scope, use `Object.hasOwn` to find out whether the binding, indicated by the `name` property of the first argument to `set`, exists in that scope. If it does, set it to the result of evaluating the second argument to `set` and then return that value. {{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 3ff233770..7d040e243 100644 --- a/13_browser.md +++ b/13_browser.md @@ -1,143 +1,78 @@ # 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. +The dream behind the web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished. quote}} {{index "Berners-Lee, Tim", "World Wide Web", HTTP, [JavaScript, "history of"], "World Wide Web"}} -{{figure {url: "img/chapter_picture_13.jpg", alt: "Picture of a telephone switchboard", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_13.jpg", alt: "Illustration showing a telephone switchboard", chapter: "framed"}}} -The next chapters of this book will talk about web browsers. Without -web ((browser))s, there would be no JavaScript. Or even if there were, -no one would ever have paid any attention to it. +The next chapters of this book will discuss web browsers. Without ((browser))s, there would be no JavaScript—or if there were, no one would ever have paid any attention to it. {{index decentralization, compatibility}} -Web technology has been decentralized from the start, not just -technically but also in the way it evolved. Various browser vendors -have added new functionality in ad hoc and sometimes poorly thought-out -ways, which then, sometimes, ended up being adopted by others—and -finally set down as in ((standards)). +Web technology has been decentralized from the start, not just technically but also in terms of the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which were then—sometimes—adopted by others, and finally set down 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 poorly conceived. +This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally, open hostility). On the other hand, the haphazard way in which the web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. ## Networks and the Internet -Computer ((network))s have been around since the 1950s. If you put -cables between two or more computers and allow them to send data back -and forth through these cables, you can do all kinds of wonderful -things. +Computer ((network))s have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things. -And if connecting two machines in the same building allows us to do -wonderful things, connecting machines all over the planet should be -even better. The technology to start implementing this vision was -developed in the 1980s, and the resulting network is called the -_((Internet))_. It has lived up to its promise. +If connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((internet))_. It has lived up to its promise. -A computer can use this network to shoot bits at another computer. For -any effective ((communication)) to arise out of this bit-shooting, the -computers on both ends must know what the bits are supposed to -represent. The meaning of any given sequence of bits depends entirely -on the kind of thing that it is trying to express and on the -((encoding)) mechanism used. +A computer can use this network to shoot bits at another computer. For any effective ((communication)) to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the ((encoding)) mechanism used. {{index [network, protocol]}} -A _network ((protocol))_ describes a style of communication over a -((network)). There are protocols for sending email, for fetching email, -for sharing files, and even for controlling computers that happen to be -infected by malicious software. +A _network ((protocol))_ describes a style of communication over a ((network)). There are protocols for sending email, for fetching email, for sharing files, and even for controlling computers that happen to be infected by malicious software. -{{indexsee "Hypertext Transfer Protocol", HTTP}} +{{indexsee "HyperText Transfer Protocol", HTTP}} -For example, the _Hypertext Transfer Protocol_ (((HTTP))) is -a protocol for retrieving named ((resource))s (chunks of information, -such as web pages or pictures). It specifies that the side making the -request should start with a line like this, naming the resource and -the version of the protocol that it is trying to use: +The _HyperText Transfer Protocol_ (((HTTP))) is a protocol for retrieving named ((resource))s (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use: -```{lang: "text/plain"} +```{lang: http} GET /index.html HTTP/1.1 ``` -There are a lot more rules about the way the requester can include more -information in the ((request)) and the way the other side, which -returns the resource, packages up its content. We'll look at HTTP in a -little more detail in [Chapter ?](http). +There are many more rules about the way the requester can include more information in the ((request)) and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter ?](http). {{index layering, stream, ordering}} -Most protocols are built on top of other protocols. HTTP treats the -network as a streamlike device into which you can put bits and have -them arrive at the correct destination in the correct order. As we saw -in [Chapter ?](async), ensuring those things is already a rather -difficult problem. +Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Providing those guarantees on top of the primitive data-sending that the network gives you is already a rather tricky problem. {{index TCP}} {{indexsee "Transmission Control Protocol", TCP}} -The _Transmission Control Protocol_ (TCP) is a ((protocol)) that -addresses this problem. All Internet-connected devices "speak" it, and -most communication on the ((Internet)) is built on top of it. +The _Transmission Control Protocol_ (TCP) is a ((protocol)) that addresses this problem. All internet-connected devices "speak" it, and most communication on the ((internet)) is built on top of it. {{index "listening (TCP)"}} -A TCP ((connection)) works as follows: one computer must be waiting, -or _listening_, for other computers to start talking to it. To be able -to listen for different kinds of communication at the same time on a -single machine, each listener has a number (called a _((port))_) -associated with it. Most ((protocol))s specify which port should be -used by default. For example, when we want to send an email using the -((SMTP)) protocol, the machine through which we send it is expected to -be listening on port 25. - -Another computer can then establish a ((connection)) by connecting to -the target machine using the correct port number. If the target -machine can be reached and is listening on that port, the connection -is successfully created. The listening computer is called the -_((server))_, and the connecting computer is called the _((client))_. - -{{index [abtraction, "of the network"]}} - -Such a connection acts as a two-way ((pipe)) through which bits can -flow—the machines on both ends can put data into it. Once the bits are -successfully transmitted, they can be read out again by the machine on -the other side. This is a convenient model. You could say that ((TCP)) -provides an abstraction of the network. +A TCP ((connection)) works as follows: one computer must be waiting, or _listening_, for other computers to start talking to it. To be able to listen for different kinds of communication at the same time on a single machine, each listener has a number (called a _((port))_) associated with it. Most ((protocol))s specify which port should be used by default. For example, when we want to send an email using the ((SMTP)) protocol, the machine through which we send it is expected to be listening on port 25. + +Another computer can then establish a ((connection)) by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the _((server))_, and the connecting computer is called the _((client))_. + +{{index [abstraction, "of the network"]}} + +Such a connection acts as a two-way ((pipe)) through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that ((TCP)) provides an abstraction of the network. {{id web}} ## The Web -The _((World Wide Web))_ (not to be confused with the ((Internet)) as -a whole) is a set of ((protocol))s and formats that allow us to visit -web pages in a browser. The "Web" part in the name refers to the fact -that such pages can easily link to each other, thus connecting into a -huge ((mesh)) that users can move through. +The _((World Wide Web))_ (not to be confused with the ((internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. 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. +To become part of the web, all you need to do is connect a machine to the ((internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. {{index URL}} -{{indexsee "Uniform Resource Locator", URL}} +{{indexsee "uniform resource locator", URL}} -Each ((document)) on the Web is named by a _Uniform Resource Locator_ -(URL), which looks something like this: +Each ((document)) on the web is named by a _uniform resource locator_ (URL), which looks something like this: ```{lang: null} http://eloquentjavascript.net/13_browser.html @@ -147,45 +82,25 @@ Each ((document)) on the Web is named by a _Uniform Resource Locator_ {{index HTTPS}} -The first part tells us that this URL uses the HTTP ((protocol)) (as -opposed to, for example, encrypted HTTP, which would be _https://_). -Then comes the part that identifies which ((server)) we are requesting -the document from. Last is a path string that identifies the specific -document (or _((resource))_) we are interested in. - -Machines connected to the Internet get an _((IP address))_, which is a -number that can be used to send messages to that machine, and looks -something like `149.210.142.219` or `2001:4860:4860::8888`. But lists -of more or less random numbers are hard to remember and awkward to -type, so you can instead register a _((domain)) name_ for a specific -address or set of addresses. I registered _eloquentjavascript.net_ to -point at the IP address of a machine I control and can thus use that -domain name to serve web pages. +The first part tells us that this URL uses the HTTP ((protocol)) (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which ((server)) we are requesting the document from. Last is a path string that identifies the document (or _((resource))_) we are interested in. + +Machines connected to the internet get an _((IP address))_, a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. Since lists of more or less random numbers are hard to remember and awkward to type, you can instead register a _((domain)) name_ for an address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. {{index browser}} -If you type this URL into your browser's ((address bar)), the browser -will try to retrieve and display the ((document)) at that URL. First, -your browser has to find out what address _eloquentjavascript.net_ -refers to. Then, using the ((HTTP)) protocol, it will make a -connection to the server at that address and ask for the resource -_/13_browser.html_. If all goes well, the server sends back a -document, which your browser then displays on your screen. +If you type this URL into your browser's ((address bar)), the browser will try to retrieve and display the ((document)) at that URL. First, your browser has to find out what address _eloquentjavascript.net_ refers to. Then, using the ((HTTP)) protocol, it will make a connection to the server at that address and ask for the resource _/13_browser.html_. If all goes well, the server sends back a document, which your browser then displays on your screen. ## HTML {{index HTML}} -{{indexsee "Hypertext Markup Language", HTML}} +{{indexsee "HyperText Markup Language", HTML}} -HTML, which stands for _Hypertext Markup Language_, is the document -format used for web pages. An HTML document contains ((text)), as well -as _((tag))s_ that give structure to the text, describing things such -as links, paragraphs, and headings. +_HTML_, which stands for HyperText Markup Language, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. A short HTML document might look like this: -```{lang: "text/html"} +```{lang: "html"} @@ -205,77 +120,45 @@ A short HTML document might look like this: This is what such a document would look like in the browser: -{{figure {url: "img/home-page.png", alt: "My home page",width: "6.3cm"}}} +{{figure {url: "img/home-page.png", alt: "A rendered version of the home page example HTML",width: "6.3cm"}}} if}} {{index [HTML, notation]}} -The tags, wrapped in ((angle brackets)) (`<` and `>`, the symbols for -_less than_ and _greater than_), provide information about the -((structure)) of the document. The other ((text)) is just plain text. +The tags, wrapped in ((angle brackets)) (`<` and `>`, the symbols for _less than_ and _greater than_), provide information about the ((structure)) of the document. The other ((text)) is just plain text. {{index doctype, version}} -The document starts with ``, which tells the browser to -interpret the page as _modern_ HTML, as opposed to various dialects -that were in use in the past. +The document starts with ``, which tells the browser to interpret the page as _modern_ HTML, as opposed to obsolete styles used in the past. {{index "head (HTML tag)", "body (HTML tag)", "title (HTML tag)", "h1 (HTML tag)", "p (HTML tag)"}} -HTML documents have a head and a body. The head contains information -_about_ the document, and the body contains the document itself. In -this case, the head declares that the title of this document is "My -home page" and that it uses the UTF-8 encoding, which is a way to -encode Unicode text as binary data. The document's body contains a -heading (`

`, meaning "heading 1"—`

` to `

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

`). +HTML documents have a head and a body. The head contains information _about_ the document, and the body contains the document itself. In this case, the head declares that the title of this document is "My home page" and that it uses the UTF-8 encoding, which is a way to encode Unicode text as binary data. The document's body contains a heading (`

`, meaning "heading 1"—`

` to `

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

`). {{index "href attribute", "a (HTML tag)"}} -Tags come in several forms. An ((element)), such as the body, a -paragraph, or a link, is started by an _((opening tag))_ like `

` -and ended by a _((closing tag))_ like `

`. Some opening tags, such -as the one for the ((link)) (``), contain extra information in the -form of `name="value"` pairs. These are called _((attribute))s_. In -this case, the destination of the link is indicated with -`href="http://eloquentjavascript.net"`, where `href` stands for -"hypertext reference". +Tags come in several forms. An ((element)), such as the body, a paragraph, or a link, is started by an _((opening tag))_ like `

` and ended by a _((closing tag))_ like `

`. Some opening tags, such as the one for the ((link)) (`
`), contain extra information in the form of `name="value"` pairs. These are called _((attribute))s_. In this case, the destination of the link is indicated with `href="http://eloquentjavascript.net"`, where `href` stands for "hypertext reference". {{index "src attribute", "self-closing tag", "img (HTML tag)"}} -Some kinds of ((tag))s do not enclose anything and thus do not need to -be closed. The metadata tag `` is an example of -this. +Some kinds of ((tag))s do not enclose anything and thus do not need to be closed. The metadata tag `` is an example of this. {{index [escaping, "in HTML"]}} -To be able to include ((angle brackets)) in the text of a document, -even though they have a special meaning in HTML, yet another form of -special notation has to be introduced. A plain opening angle bracket -is written as `<` ("less than"), and a closing bracket is written -as `>` ("greater than"). In HTML, an ampersand (`&`) character -followed by a name or character code and a semicolon (`;`) is called an _((entity))_ -and will be replaced by the character it encodes. +To be able to include ((angle brackets)) in the text of a document even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` ("less than"), and a closing bracket is written as `>` ("greater than"). In HTML, an ampersand (`&`) character followed by a name or character code and a semicolon (`;`) is called an _((entity))_ and will be replaced by the character it encodes. {{index ["backslash character", "in strings"], "ampersand character", "double-quote character"}} -This is analogous to the way backslashes are used in JavaScript -strings. Since this mechanism gives ampersand characters a special -meaning, too, they need to be escaped as `&`. Inside attribute -values, which are wrapped in double quotes, `"` can be used to -insert an actual quote character. +This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning too, they need to be escaped as `&`. Inside attribute values, which are wrapped in double quotes, `"` can be used to insert a literal quote character. {{index "error tolerance", parsing}} -HTML is parsed in a remarkably error-tolerant way. When tags that -should be there are missing, the browser reconstructs them. The way in -which this is done has been standardized, and you can rely on all -modern browsers to do it in the same way. +HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser automatically adds them. The way this is done has been standardized, and you can rely on all modern browsers to do it in the same way. The following document will be treated just like the one shown previously: -```{lang: "text/html"} +```{lang: "html"} @@ -289,25 +172,13 @@ The following document will be treated just like the one shown previously: {{index "title (HTML tag)", "head (HTML tag)", "body (HTML tag)", "html (HTML tag)"}} -The ``, ``, and `` tags are gone completely. The -browser knows that `` and `` belong in the head and that -`<h1>` means the body has started. Furthermore, I am no longer -explicitly closing the paragraphs since opening a new paragraph or -ending the document will close them implicitly. The quotes around the -attribute values are also gone. +The `<html>`, `<head>`, and `<body>` tags are completely gone. The browser knows that `<meta>` and `<title>` belong in the head and that `<h1>` means the body has started. Furthermore, I am no longer explicitly closing the paragraphs, since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone. -This book will usually omit the `<html>`, `<head>`, and `<body>` tags -from examples to keep them short and free of clutter. But I _will_ -close tags and include quotes around attributes. +This book will usually omit the `<html>`, `<head>`, and `<body>` tags from examples to keep them short and free of clutter. I _will_ close tags and include quotes around attributes, though. {{index browser}} -I will also usually omit the ((doctype)) and `charset` declaration. -This is not to be taken as an encouragement to drop these from HTML -documents. Browsers will often do ridiculous things when you forget -them. You should consider the doctype and the `charset` metadata -to be implicitly present in examples, even when they are not actually shown -in the text. +I will also usually omit the ((doctype)) and `charset` declaration. Don't take this as encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. Consider the doctype and the `charset` metadata to be implicitly present in examples, even when they are not actually shown in the text. {{id script_tag}} @@ -315,144 +186,82 @@ in the text. {{index [JavaScript, "in HTML"], "script (HTML tag)"}} -In the context of this book, the most important HTML tag is -`<script>`. This tag allows us to include a piece of JavaScript in a -document. +In the context of this book, the most important HTML tag is `<script>`, which allows us to include a piece of JavaScript in a document. -```{lang: "text/html"} +```{lang: "html"} <h1>Testing alert</h1> <script>alert("hello!");</script> ``` {{index "alert function", timeline}} -Such a script will run as soon as its `<script>` tag is encountered -while the browser reads the HTML. This page will pop up a dialog when -opened—the `alert` function resembles `prompt`, in that it pops up a -little window, but only shows a message without asking for input. +Such a script will run as soon as its `<script>` tag is encountered while the browser reads the HTML. This page will pop up a dialog when opened—the `alert` function resembles `prompt`, in that it pops up a little window, but only shows a message without asking for input. {{index "src attribute"}} -Including large programs directly in HTML documents is often -impractical. The `<script>` tag can be given an `src` attribute to fetch a script file (a text file containing a JavaScript -program) from a URL. +Including large programs directly in HTML documents is often impractical. The `<script>` tag can be given an `src` attribute to fetch a script file (a text file containing a JavaScript program) from a URL. -```{lang: "text/html"} +```{lang: "html"} <h1>Testing alert</h1> <script src="code/hello.js"></script> ``` -The _code/hello.js_ file included here contains the same -program—`alert("hello!")`. When an HTML page references other URLs as -part of itself—for example, an image file or a script—web browsers -will retrieve them immediately and include them in the page. +The _code/hello.js_ file included here contains the same program—`alert("hello!")`. When an HTML page references other URLs as part of itself, such as an image file or a script, web browsers will retrieve them immediately and include them in the page. {{index "script (HTML tag)", "closing tag"}} -A script tag must always be closed with `</script>`, even if it refers -to a script file and doesn't contain any code. If you forget this, the -rest of the page will be interpreted as part of the script. +A script tag must always be closed with `</script>`, even if it refers to a script file and doesn't contain any code. If you forget this, the rest of the page will be interpreted as part of the script. {{index "relative path", dependency}} -You can load ((ES modules)) (see [Chapter ?](modules#es)) in the -browser by giving your script tag a `type="module"` attribute. Such -modules can depend on other modules by using ((URL))s relative to -themselves as module names in `import` declarations. +You can load ((ES modules)) (see [Chapter ?](modules#es)) in the browser by giving your script tag a `type="module"` attribute. Such modules can depend on other modules by using ((URL))s relative to themselves as module names in `import` declarations. {{index "button (HTML tag)", "onclick attribute"}} -Some attributes can also contain a JavaScript program. The `<button>` -tag shown next (which shows up as a button) has an `onclick` -attribute. The attribute's value will be run whenever the button is -clicked. +Some attributes can also contain a JavaScript program. The `<button>` tag (which shows up as a button) supports an `onclick` attribute. The attribute's value will be run whenever the button is clicked. -```{lang: "text/html"} +```{lang: "html"} <button onclick="alert('Boom!');">DO NOT PRESS</button> ``` {{index "single-quote character", [escaping, "in HTML"]}} -Note that I had to use single quotes for the string in the `onclick` -attribute because double quotes are already used to quote the whole -attribute. I could also have used `"`. +Note that I had to use single quotes for the string in the `onclick` attribute because double quotes are already used to quote the whole attribute. I could also have used `"` to escape the inner quotes. ## In the sandbox {{index "malicious script", "World Wide Web", browser, website, security}} -Running programs downloaded from the ((Internet)) is potentially -dangerous. You do not know much about the people behind most sites you -visit, and they do not necessarily mean well. Running programs by -people who do not mean well is how you get your computer infected by -((virus))es, your data stolen, and your accounts hacked. +Running programs downloaded from the ((internet)) is potentially dangerous. You don't know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by malicious actors is how you get your computer infected by ((virus))es, your data stolen, and your accounts hacked. -Yet the attraction of the Web is that you can browse it without -necessarily ((trust))ing all the pages you visit. This is why browsers -severely limit the things a JavaScript program may do: it can't look -at the files on your computer or modify anything not related to the -web page it was embedded in. +Yet the attraction of the web is that you can browse it without necessarily ((trust))ing all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can't look at the files on your computer or modify anything not related to the web page it was embedded in. {{index isolation}} -Isolating a programming environment in this way is called -_((sandbox))ing_, the idea being that the program is harmlessly -playing in a sandbox. But you should imagine this particular kind of -sandbox as having a cage of thick steel bars over it so that the -programs playing in it can't actually get out. +Isolating a programming environment in this way is called _((sandbox))ing_, the idea being that the program is harmlessly playing in a sandbox. But you should imagine this particular kind of sandbox as having a cage of thick steel bars over it so that the programs playing in it can't actually get out. -The hard part of sandboxing is allowing the programs enough room to be -useful yet at the same time restricting them from doing anything -dangerous. Lots of useful functionality, such as communicating with -other servers or reading the content of the copy-paste ((clipboard)), -can also be used to do problematic, ((privacy))-invading things. +The hard part of sandboxing is allowing programs enough room to be useful while restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste ((clipboard)), can also be used for problematic, ((privacy))-invading purposes. {{index leak, exploit, security}} -Every now and then, someone comes up with a new way to circumvent the -limitations of a ((browser)) and do something harmful, ranging from -leaking minor private information to taking over the whole machine -that the browser runs on. The browser developers respond by fixing the -hole, and all is well again—until the next problem is discovered, and -hopefully publicized, rather than secretly exploited by some -government agency or ((mafia)). +Every now and then, someone comes up with a new way to circumvent the limitations of a ((browser)) and do something harmful, ranging from leaking minor private information to taking over the whole machine on which the browser is running. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized rather than secretly exploited by some government agency or criminal organization. ## Compatibility and the browser wars {{index Microsoft, "World Wide Web"}} -In the early stages of the Web, a browser called ((Mosaic)) dominated -the market. After a few years, the balance shifted to -((Netscape)), which was then, in turn, largely supplanted by -Microsoft's ((Internet Explorer)). At any point where a single -((browser)) was dominant, that browser's vendor would feel entitled to -unilaterally invent new features for the Web. Since most users used -the most popular browser, ((website))s would simply start using those -features—never mind the other browsers. - -This was the dark age of ((compatibility)), often called the -_((browser wars))_. Web developers were left with not one unified Web -but two or three incompatible platforms. To make things worse, the -browsers in use around 2003 were all full of ((bug))s, and of course -the bugs were different for each ((browser)). Life was hard for people -writing web pages. +In the early stages of the web, a browser called ((Mosaic)) dominated the market. After a few years, the balance shifted to ((Netscape)), which was, in turn, largely supplanted by Microsoft's ((Internet Explorer)). At any point where a single ((browser)) was dominant, that browser's vendor would feel entitled to unilaterally invent new features for the web. Since most users used the most popular browser, ((website))s would simply start using those features—never mind the other browsers. + +This was the dark age of ((compatibility)), often called the _((browser wars))_. Web developers were left with not one unified web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of ((bug))s, and of course the bugs were different for each ((browser)). Life was hard for people writing web pages. {{index Apple, "Internet Explorer", Mozilla}} -Mozilla ((Firefox)), a not-for-profit offshoot of ((Netscape)), -challenged Internet Explorer's position in the late 2000s. Because -((Microsoft)) was not particularly interested in staying competitive -at the time, Firefox took a lot of market share away from it. Around -the same time, ((Google)) introduced its ((Chrome)) browser, and -Apple's ((Safari)) browser gained popularity, leading to a situation -where there were four major players, rather than one. +Mozilla ((Firefox)), a not-for-profit offshoot of ((Netscape)), challenged Internet Explorer's position in the late 2000s. Because ((Microsoft)) was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, ((Google)) introduced its ((Chrome)) browser and Apple's ((Safari)) browser gained popularity, leading to a situation where there were four major players, rather than one. {{index compatibility}} -The new players had a more serious attitude toward ((standards)) and -better ((engineering)) practices, giving us less incompatibility and -fewer ((bug))s. Microsoft, seeing its market share crumble, came -around and adopted these attitudes in its Edge browser, which replaces -Internet Explorer. If you are starting to learn web development today, -consider yourself lucky. The latest versions of the major browsers -behave quite uniformly and have relatively few bugs. +The new players had a more serious attitude toward ((standards)) and better ((engineering)) practices, giving us less incompatibility and fewer ((bug))s. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaced Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs. + +Unfortunately, with Firefox's market share getting ever smaller, and Edge becoming just a wrapper around Chrome's core in 2018, this uniformity might once again take the form of a single vendor—Google, this time—having enough control over the browser market to push its idea of what the web should look like onto the rest of the world. + +For what it is worth, this long chain of historical events and accidents has produced the web platform that we have today. In the next chapters, we are going to write programs for it. \ No newline at end of file diff --git a/14_dom.md b/14_dom.md index 50f49cde0..aa7380826 100644 --- a/14_dom.md +++ b/14_dom.md @@ -2,40 +2,27 @@ {{quote {author: "Friedrich Nietzsche", title: "Beyond Good and Evil", chapter: true} -Too bad! Same old story! Once you've finished building your house you -notice you've accidentally learned something that you really should -have known—before you started. +Too bad! Same old story! Once you've finished building your house you notice you've accidentally learned something that you really should have known—before you started. quote}} -{{figure {url: "img/chapter_picture_14.jpg", alt: "Picture of a tree with letters and scripts hanging from its branches", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_14.jpg", alt: "Illustration showing a tree with letters, pictures, and gears hanging on its branches", chapter: "framed"}}} {{index drawing, parsing}} -When you open a web page in your browser, the browser retrieves the -page's ((HTML)) text and parses it, much like the way our parser from -[Chapter ?](language#parsing) parsed programs. The browser builds up a -model of the document's ((structure)) and uses this model to draw the -page on the screen. +When you open a web page, your browser retrieves the page's ((HTML)) text and parses it, much like our parser from [Chapter ?](language#parsing) parsed programs. The browser builds up a model of the document's ((structure)) and uses this model to draw the page on the screen. {{index "live data structure"}} -This representation of the ((document)) is one of the toys that a -JavaScript program has available in its ((sandbox)). It is a ((data -structure)) that you can read or modify. It acts as a _live_ data -structure: when it's modified, the page on the screen is updated to -reflect the changes. +This representation of the ((document)) is one of the toys that a JavaScript program has available in its ((sandbox)). It is a ((data structure)) that you can read or modify. It acts as a _live_ data structure: when it's modified, the page on the screen is updated to reflect the changes. ## Document structure {{index [HTML, structure]}} -You can imagine an HTML document as a nested set of ((box))es. -Tags such as `<body>` and `</body>` enclose other ((tag))s, which in -turn contain other tags or ((text)). Here's the example document from -the [previous chapter](browser): +You can imagine an HTML document as a nested set of ((box))es. Tags such as `<body>` and `</body>` enclose other ((tag))s, which in turn contain other tags or ((text)). Here's the example document from the [previous chapter](browser): -```{lang: "text/html", sandbox: "homepage"} +```{lang: html, sandbox: "homepage"} <!doctype html> <html> <head> @@ -52,79 +39,47 @@ the [previous chapter](browser): This page has the following structure: -{{figure {url: "img/html-boxes.svg", alt: "HTML document as nested boxes", width: "7cm"}}} +{{figure {url: "img/html-boxes.svg", alt: "Diagram showing an HTML document as a set of nested boxes. The outer box is labeled 'html' and contains two boxes labeled 'head' and 'body'. Inside those are further boxes, with some of the innermost boxes containing the document's text.", width: "7cm"}}} {{indexsee "Document Object Model", DOM}} -The data structure the browser uses to represent the document follows -this shape. For each box, there is an object, which we can -interact with to find out things such as what HTML tag it represents -and which boxes and text it contains. This representation is called -the _Document Object Model_, or ((DOM)) for short. +The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the _Document Object Model_, or _((DOM))_ for short. {{index "documentElement property", "head property", "body property", "html (HTML tag)", "body (HTML tag)", "head (HTML tag)"}} -The global binding `document` gives us access to these objects. Its -`documentElement` property refers to the object representing the -`<html>` tag. Since every HTML document has a head and a body, it also -has `head` and `body` properties, pointing at those elements. +The global binding `document` gives us access to these objects. Its `documentElement` property refers to the object representing the `<html>` tag. Since every HTML document has a head and a body, it also has `head` and `body` properties pointing at those elements. ## Trees {{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]}} -We call a data structure a _((tree))_ when it has a branching -structure, has no ((cycle))s (a node may not contain itself, directly -or indirectly), and has a single, well-defined _((root))_. In the case -of the DOM, `document.documentElement` serves as the root. +We call a data structure a _((tree))_ when it has a branching structure, no ((cycle))s (a node may not contain itself, directly or indirectly), and a single, well-defined _((root))_. In the case of the DOM, `document.documentElement` serves as the root. {{index sorting, ["data structure", "tree"], "syntax tree"}} -Trees come up a lot in computer science. In addition to representing -recursive structures such as HTML documents or programs, they are -often used to maintain sorted ((set))s of data because elements can -usually be found or inserted more efficiently in a tree than in a flat -array. +Trees come up a lot in computer science. In addition to representing recursive structures such as HTML documents or programs, they are often used to maintain sorted ((set))s of data because elements can usually be found or inserted more efficiently in a tree than in a flat array. {{index "leaf node", "Egg language"}} -A typical tree has different kinds of ((node))s. The syntax tree for -[the Egg language](language) had identifiers, values, and application -nodes. Application nodes may have children, whereas identifiers and -values are _leaves_, or nodes without children. +A typical tree has different kinds of ((node))s. The syntax tree for [the Egg language](language) had identifiers, values, and application nodes. Application nodes may have children, whereas identifiers and values are _leaves_, or nodes without children. {{index "body property", [HTML, structure]}} -The same goes for the DOM. Nodes for _((element))s_, which represent -HTML tags, determine the structure of the document. These can have -((child node))s. An example of such a node is `document.body`. Some of -these children can be ((leaf node))s, such as pieces of ((text)) or -((comment)) nodes. +The same goes for the DOM. Nodes for _((element))s_, which represent HTML tags, determine the structure of the document. These can have ((child node))s. An example of such a node is `document.body`. Some of these children can be ((leaf node))s, such as pieces of ((text)) or ((comment)) nodes. {{index "text node", element, "ELEMENT_NODE code", "COMMENT_NODE code", "TEXT_NODE code", "nodeType property"}} -Each DOM node object has a `nodeType` property, which contains a code -(number) that identifies the type of node. Elements have code 1, which -is also defined as the constant property `Node.ELEMENT_NODE`. Text -nodes, representing a section of text in the document, get code 3 -(`Node.TEXT_NODE`). Comments have code 8 -(`Node.COMMENT_NODE`). +Each DOM node object has a `nodeType` property, which contains a code (number) that identifies the type of node. Elements have code 1, which is also defined as the constant property `Node.ELEMENT_NODE`. Text nodes, representing a section of text in the document, get code 3 (`Node.TEXT_NODE`). Comments have code 8 (`Node.COMMENT_NODE`). Another way to visualize our document ((tree)) is as follows: -{{figure {url: "img/html-tree.svg", alt: "HTML document as a tree",width: "8cm"}}} +{{figure {url: "img/html-tree.svg", alt: "Diagram showing the HTML document as a tree, with arrows from parent nodes to child nodes", width: "8cm"}}} -The leaves are text nodes, and the arrows indicate parent-child -relationships between nodes. +The leaves are text nodes, and the arrows indicate parent-child relationships between nodes. {{id standard}} @@ -132,89 +87,47 @@ relationships between nodes. {{index "programming language", [interface, design], [DOM, interface]}} -Using cryptic numeric codes to represent node types is not a very -JavaScript-like thing to do. Later in this chapter, we'll see that -other parts of the DOM interface also feel cumbersome and alien. -The reason for this is that the DOM wasn't designed for just -JavaScript. Rather, it tries to be a language-neutral interface -that can be used in other systems as well—not just for HTML but also -for ((XML)), which is a generic ((data format)) with an HTML-like -syntax. +Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we'll see that other parts of the DOM interface also feel cumbersome and alien. This is because the DOM interface wasn't designed for JavaScript alone. Rather, it tries to be a language-neutral interface that can be used in other systems as well—not just for HTML but also for ((XML)), which is a generic ((data format)) with an HTML-like syntax. {{index consistency, integration}} -This is unfortunate. Standards are often useful. But in this case, the -advantage (cross-language consistency) isn't all that compelling. -Having an interface that is properly integrated with the language you -are using will save you more time than having a familiar interface -across languages. +This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn't all that compelling. Having an interface that is properly integrated with the language you're using will save you more time than having a familiar interface across languages. {{index "array-like object", "NodeList type"}} -As an example of this poor integration, consider the `childNodes` -property that element nodes in the DOM have. This property holds an -array-like object, with a `length` property and properties labeled by -numbers to access the child nodes. But it is an instance of the -`NodeList` type, not a real array, so it does not have methods such as -`slice` and `map`. +As an example of this poor integration, consider the `childNodes` property that element nodes in the DOM have. This property holds an array-like object with a `length` property and properties labeled by numbers to access the child nodes. But it is an instance of the `NodeList` type, not a real array, so it does not have methods such as `slice` and `map`. {{index [interface, design], [DOM, construction], "side effect"}} -Then there are issues that are simply poor design. For example, there -is no way to create a new node and immediately add children or -((attribute))s to it. Instead, you have to first create it and then add -the children and attributes one by one, using side effects. Code that -interacts heavily with the DOM tends to get long, repetitive, and -ugly. +Then there are issues that are simply caused by poor design. For example, there is no way to create a new node and immediately add children or ((attribute))s to it. Instead, you have to first create it and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly. {{index library}} -But these flaws aren't fatal. Since JavaScript allows us to create our -own ((abstraction))s, it is possible to design improved ways to -express the operations you are performing. Many libraries intended for -browser programming come with such tools. +But these flaws aren't fatal. Since JavaScript allows us to create our own ((abstraction))s, it is possible to design improved ways to express the operations we are performing. Many libraries intended for browser programming come with such tools. ## Moving through the tree {{index pointer}} -DOM nodes contain a wealth of ((link))s to other nearby nodes. The -following diagram illustrates these: +DOM nodes contain a wealth of ((link))s to other nearby nodes. The following diagram illustrates these: -{{figure {url: "img/html-links.svg", alt: "Links between DOM nodes",width: "6cm"}}} +{{figure {url: "img/html-links.svg", alt: "Diagram that shows the links between DOM nodes. The 'body' node is shown as a box, with a 'firstChild' arrow pointing at the 'h1' node at its start, a 'lastChild' arrow pointing at the last paragraph node, and 'childNodes' arrow pointing at an array of links to all its children. The middle paragraph has a 'previousSibling' arrow pointing at the node before it, a 'nextSibling' arrow to the node after it, and a 'parentNode' arrow pointing at the 'body' node.", width: "6cm"}}} {{index "child node", "parentNode property", "childNodes property"}} -Although the diagram shows only one link of each type, every node has -a `parentNode` property that points to the node it is part of, if any. -Likewise, every element node (node type 1) has a `childNodes` property -that points to an ((array-like object)) holding its children. +Although the diagram shows only one link of each type, every node has a `parentNode` property that points to the node it is part of, if any. Likewise, every element node (node type 1) has a `childNodes` property that points to an ((array-like object)) holding its children. {{index "firstChild property", "lastChild property", "previousSibling property", "nextSibling property"}} -In theory, you could move anywhere in the tree using just these parent -and child links. But JavaScript also gives you access to a number of -additional convenience links. The `firstChild` and `lastChild` -properties point to the first and last child elements or have the -value `null` for nodes without children. Similarly, `previousSibling` -and `nextSibling` point to adjacent nodes, which are nodes with the -same parent that appear immediately before or after the node itself. -For a first child, `previousSibling` will be null, and for a last -child, `nextSibling` will be null. +In theory, you could move anywhere in the tree using just these parent and child links. But JavaScript also gives you access to a number of additional convenience links. The `firstChild` and `lastChild` properties point to the first and last child elements or have the value `null` for nodes without children. Similarly, `previousSibling` and `nextSibling` point to adjacent nodes, which are nodes with the same parent that appear immediately before or after the node itself. For a first child, `previousSibling` will be null, and for a last child, `nextSibling` will be null. {{index "children property", "text node", element}} -There's also the `children` property, which is like `childNodes` but -contains only element (type 1) children, not other types of -child nodes. This can be useful when you aren't interested in text -nodes. +There's also the `children` property, which is like `childNodes` but contains only element (type 1) children, not other types of child nodes. This can be useful when you aren't interested in text nodes. {{index "talksAbout function", recursion, [nesting, "of objects"]}} -When dealing with a nested data structure like this one, recursive -functions are often useful. The following function scans a document -for ((text node))s containing a given string and returns `true` when -it has found one: +When dealing with a nested data structure like this one, recursive functions are often useful. The following function scans a document for ((text node))s containing a given string and returns `true` when it has found one: {{id talksAbout}} @@ -238,30 +151,17 @@ console.log(talksAbout(document.body, "book")); {{index "nodeValue property"}} -The `nodeValue` property of a text node holds the string of text that -it represents. +The `nodeValue` property of a text node holds the string of text that it represents. ## Finding elements {{index [DOM, querying], "body property", "hard-coding", [whitespace, "in HTML"]}} -Navigating these ((link))s among parents, children, and siblings is -often useful. But if we want to find a specific node in the document, -reaching it by starting at `document.body` and following a fixed path -of properties is a bad idea. Doing so bakes assumptions into our -program about the precise structure of the document—a structure you -might want to change later. Another complicating factor is that text -nodes are created even for the whitespace between nodes. The -example document's `<body>` tag does not have just three children (`<h1>` -and two `<p>` elements) but actually has seven: those three, plus the -spaces before, after, and between them. +Navigating these ((link))s among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at `document.body` and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document's `<body>` tag has not just three children (`<h1>` and two `<p>` elements), but seven: those three, plus the spaces before, after, and between them. {{index "search problem", "href attribute", "getElementsByTagName method"}} -So if we want to get the `href` attribute of the link in that -document, we don't want to say something like "Get the second child of -the sixth child of the document body". It'd be better if we could say -"Get the first link in the document". And we can. +If we want to get the `href` attribute of the link in that document, we don't want to say something like "Get the second child of the sixth child of the document body". It'd be better if we could say "Get the first link in the document". And we can. ```{sandbox: "homepage"} let link = document.body.getElementsByTagName("a")[0]; @@ -270,17 +170,13 @@ console.log(link.href); {{index "child node"}} -All element nodes have a `getElementsByTagName` method, which collects -all elements with the given tag name that are descendants (direct or -indirect children) of that node and returns them as an ((array-like -object)). +All element nodes have a `getElementsByTagName` method, which collects all elements with the given tag name that are descendants (direct or indirect children) of that node and returns them as an ((array-like object)). {{index "id attribute", "getElementById method"}} -To find a specific _single_ node, you can give it an `id` attribute -and use `document.getElementById` instead. +To find a specific _single_ node, you can give it an `id` attribute and use `document.getElementById` instead. -```{lang: "text/html"} +```{lang: html} <p>My ostrich Gertrude:</p> <p><img id="gertrude" src="img/ostrich.png"></p> @@ -292,24 +188,15 @@ and use `document.getElementById` instead. {{index "getElementsByClassName method", "class attribute"}} -A third, similar method is `getElementsByClassName`, which, like -`getElementsByTagName`, searches through the contents of an element -node and retrieves all elements that have the given string in their -`class` attribute. +A third, similar method is `getElementsByClassName`, which, like `getElementsByTagName`, searches through the contents of an element node and retrieves all elements that have the given string in their `class` attribute. ## Changing the document {{index "side effect", "removeChild method", "appendChild method", "insertBefore method", [DOM, construction], [DOM, modification]}} -Almost everything about the DOM data structure can be changed. The -shape of the document tree can be modified by changing parent-child -relationships. Nodes have a `remove` method to remove them from their -current parent node. To add a child node to an element node, we can -use `appendChild`, which puts it at the end of the list of children, -or `insertBefore`, which inserts the node given as the first argument -before the node given as the second argument. +Almost everything about the DOM data structure can be changed. The shape of the document tree can be modified by changing parent-child relationships. Nodes have a `remove` method to remove them from their current parent node. To add a child node to an element node, we can use `appendChild`, which puts it at the end of the list of children, or `insertBefore`, which inserts the node given as the first argument before the node given as the second argument. -```{lang: "text/html"} +```{lang: html} <p>One</p> <p>Two</p> <p>Three</p> @@ -320,36 +207,19 @@ before the node given as the second argument. </script> ``` -A node can exist in the document in only one place. Thus, inserting -paragraph _Three_ in front of paragraph _One_ will first remove it -from the end of the document and then insert it at the front, -resulting in _Three_/_One_/_Two_. All operations that insert a node -somewhere will, as a ((side effect)), cause it to be removed from its -current position (if it has one). +A node can exist in the document in only one place. Thus, inserting paragraph _Three_ in front of paragraph _One_ will first remove it from the end of the document and then insert it at the front, resulting in _Three_/_One_/_Two_. All operations that insert a node somewhere will, as a ((side effect)), cause it to be removed from its current position (if it has one). {{index "insertBefore method", "replaceChild method"}} -The `replaceChild` method is used to replace a child node with another -one. It takes as arguments two nodes: a new node and the node to be -replaced. The replaced node must be a child of the element the method -is called on. Note that both `replaceChild` and `insertBefore` expect -the _new_ node as their first argument. +The `replaceChild` method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both `replaceChild` and `insertBefore` expect the _new_ node as their first argument. ## Creating nodes -{{index "alt attribute", "img (HTML tag)"}} +{{index "alt attribute", "img (HTML tag)", "createTextNode method"}} -Say we want to write a script that replaces all ((image))s (`<img>` -tags) in the document with the text held in their `alt` attributes, -which specifies an alternative textual representation of the image. +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. -{{index "createTextNode method"}} - -This involves not only removing the images but adding a new text node -to replace them. Text nodes are created with the -`document.createTextNode` method. - -```{lang: "text/html"} +```{lang: html} <p>The <img src="img/cat.png" alt="Cat"> in the <img src="img/hat.png" alt="Hat">.</p> @@ -371,24 +241,15 @@ to replace them. Text nodes are created with the {{index "text node"}} -Given a string, `createTextNode` gives us a text node that we can -insert into the document to make it show up on the screen. +Given a string, `createTextNode` gives us a text node that we can insert into the document to make it show up on the screen. {{index "live data structure", "getElementsByTagName method", "childNodes property"}} -The loop that goes over the images starts at the end of the list. This -is necessary because the node list returned by a method like -`getElementsByTagName` (or a property like `childNodes`) is _live_. -That is, it is updated as the document changes. If we started from the -front, removing the first image would cause the list to lose its first -element so that the second time the loop repeats, where `i` is 1, it -would stop because the length of the collection is now also 1. +The loop that goes over the images starts at the end of the list. This is necessary because the node list returned by a method like `getElementsByTagName` (or a property like `childNodes`) is _live_. That is, it is updated as the document changes. If we started from the front, removing the first image would cause the list to lose its first element so that the second time the loop repeats, where `i` is 1, it would stop because the length of the collection is now also 1. {{index "slice method"}} -If you want a _solid_ collection of nodes, as opposed to a live one, -you can convert the collection to a real array by calling -`Array.from`. +If you want a _solid_ collection of nodes, as opposed to a live one, you can convert the collection to a real array by calling `Array.from`. ``` let arrayish = {0: "one", 1: "two", length: 2}; @@ -399,19 +260,15 @@ console.log(array.map(s => s.toUpperCase())); {{index "createElement method"}} -To create ((element)) nodes, you can use the `document.createElement` -method. This method takes a tag name and returns a new empty node of -the given type. +To create ((element)) nodes, you can use the `document.createElement` method. This method takes a tag name and returns a new empty node of the given type. {{index "Popper, Karl", [DOM, construction], "elt function"}} {{id elt}} -The following example defines a utility `elt`, which creates an -element node and treats the rest of its arguments as children to that -node. This function is then used to add an attribution to a quote. +The following example defines a utility `elt`, which creates an element node and treats the rest of its arguments as children to that node. This function is then used to add an attribution to a quote. -```{lang: "text/html"} +```{lang: html} <blockquote id="quote"> No book can ever be finished. While working on it we learn just enough to find it immature the moment we turn away @@ -441,7 +298,7 @@ node. This function is then used to add an attribution to a quote. This is what the resulting document looks like: -{{figure {url: "img/blockquote.png", alt: "A blockquote with attribution",width: "8cm"}}} +{{figure {url: "img/blockquote.png", alt: "Rendered picture of the blockquote with attribution", width: "8cm"}}} if}} @@ -449,20 +306,13 @@ if}} {{index "href attribute", [DOM, attributes]}} -Some element ((attribute))s, such as `href` for links, can be accessed -through a property of the same name on the element's ((DOM)) -object. This is the case for most commonly used standard attributes. +Some element ((attribute))s, such as `href` for links, can be accessed through a property of the same name on the element's ((DOM)) object. This is the case for most commonly used standard attributes. {{index "data attribute", "getAttribute method", "setAttribute method", attribute}} -But HTML allows you to set any attribute you want on nodes. This can -be useful because it allows you to store extra information in a -document. If you make up your own attribute names, though, such -attributes will not be present as properties on the element's node. -Instead, you have to use the `getAttribute` and `setAttribute` methods -to work with them. +HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. To read or change custom attributes, which aren't available as regular object properties, you have to use the `getAttribute` and `setAttribute` methods. -```{lang: "text/html"} +```{lang: html} <p data-classified="secret">The launch code is 00000000.</p> <p data-classified="unclassified">I have two feet.</p> @@ -476,49 +326,29 @@ to work with them. </script> ``` -It is recommended to prefix the names of such made-up attributes with -`data-` to ensure they do not conflict with any other attributes. +It is recommended to prefix the names of such made-up attributes with `data-` to ensure they do not conflict with any other attributes. {{index "getAttribute method", "setAttribute method", "className property", "class attribute"}} -There is a commonly used attribute, `class`, which is a ((keyword)) in -the JavaScript language. For historical reasons—some old JavaScript -implementations could not handle property names that matched -keywords—the property used to access this attribute is called -`className`. You can also access it under its real name, `"class"`, by -using the `getAttribute` and `setAttribute` methods. +There is a commonly used attribute, `class`, which is a ((keyword)) in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called `className`. You can also access it under its real name, `"class"`, with the `getAttribute` and `setAttribute` methods. ## Layout {{index layout, "block element", "inline element", "p (HTML tag)", "h1 (HTML tag)", "a (HTML tag)", "strong (HTML tag)"}} -You may have noticed that different types of elements are laid out -differently. Some, such as paragraphs (`<p>`) or headings (`<h1>`), -take up the whole width of the document and are rendered on separate -lines. These are called _block_ elements. Others, such as links -(`<a>`) or the `<strong>` element, are rendered on the same line with -their surrounding text. Such elements are called _inline_ elements. +You may have noticed that different types of elements are laid out differently. Some, such as paragraphs (`<p>`) or headings (`<h1>`), take up the whole width of the document and are rendered on separate lines. These are called _block_ elements. Others, such as links (`<a>`) or the `<strong>` element, are rendered on the same line with their surrounding text. Such elements are called _inline_ elements. {{index drawing}} -For any given document, browsers are able to compute a layout, which -gives each element a size and position based on its type and content. -This layout is then used to actually draw the document. +For any given document, browsers are able to compute a layout, which gives each element a size and position based on its type and content. This layout is then used to actually draw the document. {{index "border (CSS)", "offsetWidth property", "offsetHeight property", "clientWidth property", "clientHeight property", dimensions}} -The size and position of an element can be accessed from JavaScript. -The `offsetWidth` and `offsetHeight` properties give you the space the -element takes up in _((pixel))s_. A pixel is the basic unit of -measurement in the browser. It traditionally corresponds to the -smallest dot that the screen can draw, but on modern displays, which -can draw _very_ small dots, that may no longer be the case, and a -browser pixel may span multiple display dots. +The size and position of an element can be accessed from JavaScript. The `offsetWidth` and `offsetHeight` properties give you the space the element takes up in _((pixel))s_. A pixel is the basic unit of measurement in the browser. It traditionally corresponds to the smallest dot that the screen can draw, but on modern displays, which can draw _very_ small dots, that may no longer be the case, and a browser pixel may span multiple display dots. -Similarly, `clientWidth` and `clientHeight` give you the size of the -space _inside_ the element, ignoring border width. +Similarly, `clientWidth` and `clientHeight` give you the size of the space _inside_ the element, ignoring border width. -```{lang: "text/html"} +```{lang: html} <p style="border: 3px solid red"> I'm boxed in </p> @@ -526,7 +356,9 @@ space _inside_ the element, ignoring border width. <script> let para = document.body.getElementsByTagName("p")[0]; console.log("clientHeight:", para.clientHeight); + // → 19 console.log("offsetHeight:", para.offsetHeight); + // → 25 </script> ``` @@ -534,7 +366,7 @@ space _inside_ the element, ignoring border width. Giving a paragraph a border causes a rectangle to be drawn around it. -{{figure {url: "img/boxed-in.png", alt: "A paragraph with a border",width: "8cm"}}} +{{figure {url: "img/boxed-in.png", alt: "Rendered picture of a paragraph with a border", width: "8cm"}}} if}} @@ -542,36 +374,17 @@ if}} {{id boundingRect}} -The most effective way to find the precise position of an element on -the screen is the `getBoundingClientRect` method. It returns an object -with `top`, `bottom`, `left`, and `right` properties, indicating the -pixel positions of the sides of the element relative to the top left -of the screen. If you want them relative to the whole document, you -must add the current scroll position, which you can find in the -`pageXOffset` and `pageYOffset` bindings. +The most effective way to find the precise position of an element on the screen is the `getBoundingClientRect` method. It returns an object with `top`, `bottom`, `left`, and `right` properties, indicating the pixel positions of the sides of the element relative to the 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}} -Laying out a document can be quite a lot of work. In the interest of -speed, browser engines do not immediately re-layout a document every -time you change it but wait as long as they can. When a JavaScript -program that changed the document finishes running, the browser will -have to compute a new layout to draw the changed document to -the screen. When a program _asks_ for the position or size of -something by reading properties such as `offsetHeight` or calling -`getBoundingClientRect`, providing correct information also requires -computing a ((layout)). +Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can before doing so. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing that information also requires computing a ((layout)). {{index "side effect", optimization, benchmark}} -A program that repeatedly alternates between reading DOM layout -information and changing the DOM forces a lot of layout computations -to happen and will consequently run very slowly. The following code is -an example of this. It contains two different programs that build up a -line of _X_ characters 2,000 pixels wide and measures the time each -one takes. +A program that repeatedly alternates between reading DOM layout information and changing the DOM forces a lot of layout computations to happen and will consequently run very slowly. The following code is an example of this. It contains two different programs that build up a line of _X_ characters 2,000 pixels wide and measures the time each one takes. -```{lang: "text/html", test: nonumbers} +```{lang: html, test: nonumbers} <p><span id="one"></span></p> <p><span id="two"></span></p> @@ -604,45 +417,34 @@ one takes. {{index "block element", "inline element", style, "strong (HTML tag)", "a (HTML tag)", underline}} -We have seen that different HTML elements are drawn differently. Some -are displayed as blocks, others inline. Some add styling—`<strong>` -makes its content ((bold)), and `<a>` makes it blue and underlines it. +We have seen that different HTML elements are drawn differently. Some are displayed as blocks, others inline. Some add styling—`<strong>` makes its content ((bold)), and `<a>` makes it blue and underlines it. {{index "img (HTML tag)", "default behavior", "style attribute"}} -The way an `<img>` tag shows an image or an `<a>` tag causes a link to -be followed when it is clicked is strongly tied to the element type. -But we can change the styling associated with an element, such -as the text color or underline. Here is an example that uses -the `style` property: +The way an `<img>` tag shows an image or an `<a>` tag causes a link to be followed when it is clicked is strongly tied to the element type. But we can change the styling associated with an element, such as the text color or underline. Here is an example that uses the `style` property: -```{lang: "text/html"} +```{lang: html} <p><a href=".">Normal link</a></p> <p><a href="." style="color: green">Green link</a></p> ``` {{if book -The second link will be green instead of the default link color. +The second link will be green instead of the default link color: -{{figure {url: "img/colored-links.png", alt: "A normal and a green link",width: "2.2cm"}}} +{{figure {url: "img/colored-links.png", alt: "Rendered picture of a normal blue link and a styled green link", width: "2.2cm"}}} if}} {{index "border (CSS)", "color (CSS)", CSS, "colon character"}} -A style attribute may contain one or more _((declaration))s_, which -are a property (such as `color`) followed by a colon and a value (such -as `green`). When there is more than one declaration, they must be -separated by ((semicolon))s, as in `"color: red; border: none"`. +A style attribute may contain one or more _((declaration))s_, which are a property (such as `color`) followed by a colon and a value (such as `green`). When there is more than one declaration, they must be separated by ((semicolon))s, as in `"color: red; border: none"`. {{index "display (CSS)", layout}} -A lot of aspects of the document can be influenced by -styling. For example, the `display` property controls whether an -element is displayed as a block or an inline element. +A lot of aspects of the document can be influenced by styling. For example, the `display` property controls whether an element is displayed as a block or an inline element. -```{lang: "text/html"} +```{lang: html} This text is displayed <strong>inline</strong>, <strong style="display: block">as a block</strong>, and <strong style="display: none">not at all</strong>. @@ -650,28 +452,19 @@ 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 -{{figure {url: "img/display.png", alt: "Different display styles",width: "4cm"}}} +{{figure {url: "img/display.png", alt: "Different display styles", width: "4cm"}}} if}} {{index "color (CSS)", "style attribute"}} -JavaScript code can directly manipulate the style of an element -through the element's `style` property. This property holds an object -that has properties for all possible style properties. The values of -these properties are strings, which we can write to in order to change -a particular aspect of the element's style. +JavaScript code can directly manipulate the style of an element through the element's `style` property. This property holds an object that has properties for all possible style properties. The values of these properties are strings, which we can write to in order to change a particular aspect of the element's style. -```{lang: "text/html"} +```{lang: html} <p id="para" style="color: purple"> Nice text </p> @@ -685,11 +478,7 @@ a particular aspect of the element's style. {{index "camel case", capitalization, "hyphen character", "font-family (CSS)"}} -Some style property names contain hyphens, such as `font-family`. -Because such property names are awkward to work with in JavaScript -(you'd have to say `style["font-family"]`), the property names in the -`style` object for such properties have their hyphens removed and the -letters after them capitalized (`style.fontFamily`). +Some style property names contain hyphens, such as `font-family`. Because such property names are awkward to work with in JavaScript (you'd have to say `style["font-family"]`), the property names in the `style` object for such properties have their hyphens removed and the letters after them capitalized (`style.fontFamily`). ## Cascading styles @@ -698,11 +487,9 @@ letters after them capitalized (`style.fontFamily`). {{indexsee "Cascading Style Sheets", CSS}} {{indexsee "style sheet", CSS}} -The styling system for HTML is called ((CSS)), for _Cascading Style -Sheets_. A _style sheet_ is a set of rules for how to style -elements in a document. It can be given inside a `<style>` tag. +The styling system for HTML is called _((CSS))_, for _Cascading Style Sheets_. A _style sheet_ is a set of rules for how to style elements in a document. It can be given inside a `<style>` tag. -```{lang: "text/html"} +```{lang: html} <style> strong { font-style: italic; @@ -714,29 +501,17 @@ elements in a document. It can be given inside a `<style>` tag. {{index "rule (CSS)", "font-weight (CSS)", overlay}} -The _((cascading))_ in the name refers to the fact that multiple such -rules are combined to produce the final style for an element. In the -example, the default styling for `<strong>` tags, which gives them -`font-weight: bold`, is overlaid by the rule in the `<style>` tag, -which adds `font-style` and `color`. +The _((cascading))_ in the name refers to the fact that multiple such rules are combined to produce the final style for an element. In the example, the default styling for `<strong>` tags, which gives them `font-weight: bold`, is overlaid by the rule in the `<style>` tag, which adds `font-style` and `color`. {{index "style (HTML tag)", "style attribute"}} -When multiple rules define a value for the same property, the most -recently read rule gets a higher ((precedence)) and wins. So if the -rule in the `<style>` tag included `font-weight: normal`, -contradicting the default `font-weight` rule, the text would be -normal, _not_ bold. Styles in a `style` attribute applied directly to -the node have the highest precedence and always win. +When multiple rules define a value for the same property, the most recently read rule gets a higher ((precedence)) and wins. For example, if the rule in the `<style>` tag included `font-weight: normal`, contradicting the default `font-weight` rule, the text would be normal, _not_ bold. Styles in a `style` attribute applied directly to the node have the highest precedence and always win. {{index uniqueness, "class attribute", "id attribute"}} -It is possible to target things other than ((tag)) names in CSS rules. -A rule for `.abc` applies to all elements with `"abc"` in their `class` -attribute. A rule for `#xyz` applies to the element with an `id` -attribute of `"xyz"` (which should be unique within the document). +It is possible to target things other than ((tag)) names in CSS rules. A rule for `.abc` applies to all elements with `"abc"` in their `class` attribute. A rule for `#xyz` applies to the element with an `id` attribute of `"xyz"` (which should be unique within the document). -```{lang: "text/css"} +```{lang: "css"} .subtle { color: gray; font-size: 80%; @@ -753,43 +528,23 @@ p#main.a.b { {{index "rule (CSS)"}} -The ((precedence)) rule favoring the most recently defined rule -applies only when the rules have the same _((specificity))_. A rule's -specificity is a measure of how precisely it describes matching -elements, determined by the number and kind (tag, class, or ID) of -element aspects it requires. For example, a rule that targets `p.a` is -more specific than rules that target `p` or just `.a` and would thus -take precedence over them. +The ((precedence)) rule favoring the most recently defined rule applies only when the rules have the same _((specificity))_. A rule's specificity is a measure of how precisely it describes matching elements, determined by the number and kind (tag, class, or ID) of element aspects it requires. For example, a rule that targets `p.a` is more specific than rules that target `p` or just `.a` and would thus take precedence over them. {{index "direct child node"}} -The notation `p > a {…}` applies the given styles to all `<a>` tags -that are direct children of `<p>` tags. Similarly, `p a {…}` applies -to all `<a>` tags inside `<p>` tags, whether they are direct or -indirect children. +The notation `p > a {…}` applies the given styles to all `<a>` tags that are direct children of `<p>` tags. Similarly, `p a {…}` applies to all `<a>` tags inside `<p>` tags, whether they are direct or indirect children. ## Query selectors -{{index complexity, CSS}} - -We won't be using style sheets all that much in this book. -Understanding them is helpful when programming in the browser, but -they are complicated enough to warrant a separate book. - -{{index "domain-specific language", [DOM, querying]}} +{{index complexity, CSS, "domain-specific language", [DOM, querying]}} -The main reason I introduced _((selector))_ syntax—the notation used -in style sheets to determine which elements a set of styles apply -to—is that we can use this same mini-language as an effective way to -find DOM elements. +We won't be using style sheets very much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book. The main reason I introduced _((selector))_ syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements. {{index "querySelectorAll method", "NodeList type"}} -The `querySelectorAll` method, which is defined both on the `document` -object and on element nodes, takes a selector string and returns a -`NodeList` containing all the elements that it matches. +The `querySelectorAll` method, which is defined both on the `document` object and on element nodes, takes a selector string and returns a `NodeList` containing all the elements that it matches. -```{lang: "text/html"} +```{lang: html} <p>And if you go chasing <span class="animal">rabbits</span></p> <p>And you know you're going to fall</p> @@ -814,17 +569,11 @@ object and on element nodes, takes a selector string and returns a {{index "live data structure"}} -Unlike methods such as `getElementsByTagName`, the object returned by -`querySelectorAll` is _not_ live. It won't change when you change the -document. It is still not a real array, though, so you still need to -call `Array.from` if you want to treat it like one. +Unlike methods such as `getElementsByTagName`, the object returned by `querySelectorAll` is _not_ live. It won't change when you change the document. It is still not a real array, though, so you need to call `Array.from` if you want to treat it like one. {{index "querySelector method"}} -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}} @@ -832,25 +581,13 @@ matches. {{index "position (CSS)", "relative positioning", "top (CSS)", "left (CSS)", "absolute positioning"}} -The `position` style property influences layout in a powerful way. By -default it has a value of `static`, meaning the element sits in its -normal place in the document. When it is set to `relative`, the -element still takes up space in the document, but now the `top` and -`left` style properties can be used to move it relative to that normal -place. When `position` is set to `absolute`, the element is removed -from the normal document flow—that is, it no longer takes up space and -may overlap with other elements. Also, its `top` and `left` properties -can be used to absolutely position it relative to the top-left corner -of the nearest enclosing element whose `position` property isn't -`static`, or relative to the document if no such enclosing element -exists. +The `position` style property influences layout in a powerful way. It has a default value of `static`, meaning the element sits in its normal place in the document. When it is set to `relative`, the element still takes up space in the document, but now the `top` and `left` style properties can be used to move it relative to that normal place. When `position` is set to `absolute`, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Its `top` and `left` properties can be used to absolutely position it relative to the 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"]}} -We can use this to create an animation. The following document -displays a picture of a cat that moves around in an ((ellipse)): +We can use this to create an animation. The following document displays a picture of a cat that moves around in an ((ellipse)): -```{lang: "text/html", startCode: true} +```{lang: html, startCode: true} <p style="text-align: center"> <img src="img/cat.png" style="position: relative"> </p> @@ -873,113 +610,59 @@ displays a picture of a cat that moves around in an ((ellipse)): The gray arrow shows the path along which the image moves. -{{figure {url: "img/cat-animation.png", alt: "A moving cat head",width: "8cm"}}} +{{figure {url: "img/cat-animation.png", alt: "A diagram showing a picture of a cat with a circular arrow indicating its motion", width: "8cm"}}} if}} {{index "top (CSS)", "left (CSS)", centering, "relative positioning"}} -Our picture is centered on the page and given a `position` of -`relative`. We'll repeatedly update that picture's `top` and `left` -styles to move it. +Our picture is centered on the page and given a `position` of `relative`. We'll repeatedly update that picture's `top` and `left` styles to move it. {{index "requestAnimationFrame function", drawing, animation}} {{id animationFrame}} -The script uses `requestAnimationFrame` to schedule the `animate` -function to run whenever the browser is ready to repaint the screen. -The `animate` function itself again calls `requestAnimationFrame` to -schedule the next update. When the browser window (or tab) is active, -this will cause updates to happen at a rate of about 60 per second, -which tends to produce a good-looking animation. +The script uses `requestAnimationFrame` to schedule the `animate` function to run whenever the browser is ready to repaint the screen. The `animate` function itself again calls `requestAnimationFrame` to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation. {{index timeline, blocking}} -If we just updated the DOM in a loop, the page would freeze, and -nothing would show up on the screen. Browsers do not update their -display while a JavaScript program is running, nor do they allow any -interaction with the page. This is why we need -`requestAnimationFrame`—it lets the browser know that we are done for -now, and it can go ahead and do the things that browsers do, such as -updating the screen and responding to user actions. +If we just updated the DOM in a loop, the page would freeze, and nothing would show up on the screen. Browsers do not update their display while a JavaScript program is running, nor do they allow any interaction with the page. This is why we need `requestAnimationFrame`—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions. {{index "smooth animation"}} -The animation function is passed the current ((time)) as an -argument. To ensure that the motion of the cat per millisecond is -stable, it bases the speed at which the angle changes on the -difference between the current time and the last time the function -ran. If it just moved the angle by a fixed amount per step, the motion -would stutter if, for example, another heavy task running on the same -computer were to prevent the function from running for a fraction of a -second. +The animation function is passed the current ((time)) as an argument. To ensure that the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter when, for example, another heavy task running on the same computer prevented the function from running for a fraction of a second. {{index "Math.cos function", "Math.sin function", cosine, sine, trigonometry}} {{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 one. Both functions -interpret their argument as the position on this circle, with zero -denoting the point on the far right of the circle, going clockwise -until 2π (about 6.28) has taken us around the whole circle. `Math.cos` -tells you the x-coordinate of the point that corresponds to the given -position, and `Math.sin` yields the y-coordinate. Positions (or -angles) greater than 2π or less than 0 are valid—the rotation repeats -so that _a_+2π refers to the same ((angle)) as _a_. +`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0, 0) with a radius of 1. Both functions interpret their argument as the position on this circle, with 0 denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position, and `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same ((angle)) as _a_. {{index "PI constant"}} -This unit for measuring angles is called ((radian))s—a full circle is -2π radians, similar to how it is 360 degrees when measuring in -degrees. The constant π is available as `Math.PI` in JavaScript. +This unit for measuring angles is called ((radian))s—a full circle is 2π radians, similar to how it is 360 degrees when measuring in degrees. The constant π is available as `Math.PI` in JavaScript. -{{figure {url: "img/cos_sin.svg", alt: "Using cosine and sine to compute coordinates",width: "6cm"}}} +{{figure {url: "img/cos_sin.svg", alt: "Diagram showing the use of cosine and sine to compute coordinates. A circle with radius 1 is shown with two points on it. The angle from the right side of the circle to the point, in radians, is used to compute the position of each point by using 'cos(angle)' for the horizontal distance from the center of the circle and sin(angle) for the vertical distance.", width: "6cm"}}} {{index "counter variable", "Math.sin function", "top (CSS)", "Math.cos function", "left (CSS)", ellipse}} -The cat animation code keeps a counter, `angle`, for the current angle -of the animation and increments it every time the `animate` function -is called. It can then use this angle to compute the current position -of the image element. The `top` style is computed with `Math.sin` and -multiplied by 20, which is the vertical radius of our ellipse. The -`left` style is based on `Math.cos` and multiplied by 200 so that the -ellipse is much wider than it is high. +The cat animation code keeps a counter, `angle`, for the current angle of the animation and increments it every time the `animate` function is called. It can then use this angle to compute the current position of the image element. The `top` style is computed with `Math.sin` and multiplied by 20, which is the vertical radius of our ellipse. The `left` style is based on `Math.cos` and multiplied by 200 so that the ellipse is much wider than it is high. {{index "unit (CSS)"}} -Note that styles usually need _units_. In this case, we have to append -`"px"` to the number to tell the browser that we are counting in ((pixel))s -(as opposed to centimeters, "ems", or other units). This is easy to -forget. Using numbers without units will result in your style being -ignored—unless the number is 0, which always means the same thing, -regardless of its unit. +Note that styles usually need _units_. In this case, we have to append `"px"` to the number to tell the browser that we are counting in ((pixel))s (as opposed to centimeters, "ems", or other units). This is easy to forget. Using numbers without units will result in your style being ignored—unless the number is 0, which always means the same thing, regardless of its unit. ## Summary -JavaScript programs may inspect and interfere with the document that -the browser is displaying through a data structure called the DOM. -This data structure represents the browser's model of the document, -and a JavaScript program can modify it to change the visible document. +JavaScript programs may inspect and interfere with the document that the browser is displaying through a data structure called the DOM. This data structure represents the browser's model of the document, and a JavaScript program can modify it to change the visible document. -The DOM is organized like a tree, in which elements are arranged -hierarchically according to the structure of the document. The objects -representing elements have properties such as `parentNode` and -`childNodes`, which can be used to navigate through this tree. +The DOM is organized like a tree, where elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as `parentNode` and `childNodes`, which can be used to navigate through this tree. -The way a document is displayed can be influenced by _styling_, both -by attaching styles to nodes directly and by defining rules that match -certain nodes. There are many different style properties, such as -`color` or `display`. JavaScript code can manipulate an element's -style directly through its `style` property. +The way a document is displayed can be influenced by _styling_, both by attaching styles to nodes directly and by defining rules that match certain nodes. There are many different style properties, such as `color` or `display`. JavaScript code can manipulate an element's style directly through its `style` property. ## Exercises @@ -991,7 +674,7 @@ style directly through its `style` property. An HTML table is built with the following tag structure: -```{lang: "text/html"} +```{lang: html} <table> <tr> <th>name</th> @@ -1008,30 +691,21 @@ An HTML table is built with the following tag structure: {{index "tr (HTML tag)", "th (HTML tag)", "td (HTML tag)"}} -For each _((row))_, the `<table>` tag contains a `<tr>` tag. Inside of -these `<tr>` tags, we can put cell elements: either heading cells -(`<th>`) or regular cells (`<td>`). +For each _((row))_, the `<table>` tag contains a `<tr>` tag. Inside of these `<tr>` tags, we can put cell elements: either heading cells (`<th>`) or regular cells (`<td>`). -Given a data set of mountains, an array of objects with `name`, -`height`, and `place` properties, generate the DOM structure for a -table that enumerates the objects. It should have one column per key -and one row per object, plus a header row with `<th>` elements at the -top, listing the column names. +Given a dataset of mountains, an array of objects with `name`, `height`, and `place` properties, generate the DOM structure for a table that enumerates the objects. It has one column per key and one row per object, plus a header row with `<th>` elements at the top, listing the column names. -Write this so that the columns are automatically derived from the -objects, by taking the property names of the first object in the data. +Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data. -Add the resulting table to the element with an `id` attribute of -`"mountains"` so that it becomes visible in the document. +Show the resulting table in the document by appending it to the element that has an `id` attribute of `"mountains"`. {{index "right-aligning", "text-align (CSS)"}} -Once you have this working, right-align cells that contain number -values by setting their `style.textAlign` property to `"right"`. +Once you have this working, right-align cells that contain number values by setting their `style.textAlign` property to `"right"`. {{if interactive -```{test: no, lang: "text/html"} +```{test: no, lang: html} <h1>Mountains</h1> <div id="mountains"></div> @@ -1057,22 +731,15 @@ if}} {{index "createElement method", "table example", "appendChild method"}} -You can use `document.createElement` to create new element nodes, -`document.createTextNode` to create text nodes, and the `appendChild` -method to put nodes into other nodes. +You can use `document.createElement` to create new element nodes, `document.createTextNode` to create text nodes, and the `appendChild` method to put nodes into other nodes. {{index "Object.keys function"}} -You'll want to loop over the key names once to fill in the top row and -then again for each object in the array to construct the data rows. To -get an array of key names from the first object, `Object.keys` will be -useful. +You'll want to loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows. To get an array of key names from the first object, `Object.keys` will be useful. {{index "getElementById method", "querySelector method"}} -To add the table to the correct parent node, you can use -`document.getElementById` or `document.querySelector` to find the node -with the proper `id` attribute. +To add the table to the correct parent node, you can use `document.getElementById` or `document.querySelector` with `"#mountains"` to find the node. hint}} @@ -1080,21 +747,15 @@ hint}} {{index "getElementsByTagName method", recursion}} -The `document.getElementsByTagName` method returns all child elements -with a given tag name. Implement your own version of this as a -function that takes a node and a string (the tag name) as arguments -and returns an array containing all descendant element nodes with the -given tag name. +The `document.getElementsByTagName` method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name. Your function should go through the document itself. It may not use a method like `querySelectorAll` to do the work. {{index "nodeName property", capitalization, "toLowerCase method", "toUpperCase method"}} -To find the tag name of an element, use its `nodeName` property. But -note that this will return the tag name in all uppercase. Use the -`toLowerCase` or `toUpperCase` string methods to compensate for this. +To find the tag name of an element, use its `nodeName` property. But note that this will return the tag name in all uppercase. Use the `toLowerCase` or `toUpperCase` string methods to compensate for this. {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <h1>Heading with a <span>span</span> element.</h1> <p>A paragraph with <span>one</span>, <span>two</span> spans.</p> @@ -1119,26 +780,15 @@ if}} {{index "getElementsByTagName method", recursion}} -The solution is most easily expressed with a recursive function, -similar to the [`talksAbout` function](dom#talksAbout) defined earlier -in this chapter. +The solution is most easily expressed with a recursive function, similar to the [`talksAbout` function](dom#talksAbout) defined earlier in this chapter. {{index concatenation, "concat method", closure}} -You could call `byTagname` itself recursively, concatenating the -resulting arrays to produce the output. Or you could create an inner -function that calls itself recursively and that has access to an array -binding defined in the outer function, to which it can add the -matching elements it finds. Don't forget to call the ((inner -function)) once from the outer function to start the process. +You could call `byTagname` itself recursively, concatenating the resulting arrays to produce the output. Or you could create an inner function that calls itself recursively and that has access to an array binding defined in the outer function, to which it can add the matching elements it finds. Don't forget to call the ((inner function)) once from the outer function to start the process. {{index "nodeType property", "ELEMENT_NODE code"}} -The recursive function must check the node type. Here we are -interested only in node type 1 (`Node.ELEMENT_NODE`). For such -nodes, we must loop over their children and, for each child, see -whether the child matches the query while also doing a recursive call -on it to inspect its own children. +The recursive function must check the node type. Here we are interested only in node type 1 (`Node.ELEMENT_NODE`). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children. hint}} @@ -1146,25 +796,17 @@ hint}} {{index "cat's hat (exercise)", [animation, "spinning cat"]}} -Extend the cat animation defined [earlier](dom#animation) so that -both the cat and his hat (`<img src="img/hat.png">`) orbit at opposite -sides of the ellipse. +Extend the cat animation defined [earlier](dom#animation) so that both the cat and his hat (`<img src="img/hat.png">`) orbit at opposite sides of the ellipse. -Or make the hat circle around the cat. Or alter the animation in some -other interesting way. +Or make the hat circle around the cat. Or alter the animation in some other interesting way. {{index "absolute positioning", "top (CSS)", "left (CSS)", "position (CSS)"}} -To make positioning multiple objects easier, it is probably a good -idea to switch to absolute positioning. This means that `top` and -`left` are counted relative to the top left of the document. To avoid -using negative coordinates, which would cause the image to move -outside of the visible page, you can add a fixed number of pixels to -the position values. +To make positioning multiple objects easier, you'll probably want to switch to absolute positioning. This means that `top` and `left` are counted relative to the 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 -```{lang: "text/html", test: no} +```{lang: html, test: no} <style>body { min-height: 200px }</style> <img src="img/cat.png" id="cat" style="position: absolute"> <img src="img/hat.png" id="hat" style="position: absolute"> @@ -1193,9 +835,6 @@ if}} {{hint -`Math.cos` and `Math.sin` measure angles in radians, where a full -circle is 2π. For a given angle, you can get the opposite angle by -adding half of this, which is `Math.PI`. This can be useful for -putting the hat on the opposite side of the orbit. +`Math.cos` and `Math.sin` measure angles in radians, where a full circle is 2π. For a given angle, you can get the opposite angle by adding half of this, which is `Math.PI`. This can be useful for putting the hat on the opposite side of the orbit. hint}} diff --git a/15_event.md b/15_event.md index ce35adb00..ffb1c832c 100644 --- a/15_event.md +++ b/15_event.md @@ -2,50 +2,33 @@ {{quote {author: "Marcus Aurelius", title: Meditations, chapter: true} -You have power over your mind—not outside events. Realize this, and -you will find strength. +You have power over your mind—not outside events. Realize this, and you will find strength. quote}} {{index stoicism, "Marcus Aurelius", input, timeline}} -{{figure {url: "img/chapter_picture_15.jpg", alt: "Picture a Rube Goldberg machine", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_15.jpg", alt: "Illustration showing a Rube Goldberg machine involving a ball, a see-saw, a pair of scissors, and a hammer, which affect each other in a chain reaction that turns on a lightbulb.", chapter: "framed"}}} -Some programs work with direct user input, such as mouse and keyboard -actions. That kind of input isn't available as a well-organized data -structure—it comes in piece by piece, in real time, and the program is -expected to respond to it as it happens. +Some programs work with direct user input, such as mouse and keyboard actions. That kind of input isn't available ahead of time, as a well-organized data structure—it comes in piece by piece, in real time, and the program must respond to it as it happens. ## Event handlers {{index polling, button, "real-time"}} -Imagine an interface where the only way to find out whether a key on -the ((keyboard)) is being pressed is to read the current state of that -key. To be able to react to keypresses, you would have to constantly -read the key's state so that you'd catch it before it's released -again. It would be dangerous to perform other time-intensive -computations since you might miss a keypress. +Imagine an interface where the only way to find out whether a key on the ((keyboard)) is being pressed 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 do handle input like that. A step up from this -would be for the hardware or operating system to notice the keypress -and put it in a queue. A program can then periodically check the queue -for new events and react to what it finds there. +Some primitive machines handle input like this. A step up from this is for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there. {{index responsiveness, "user experience"}} -Of course, it has to remember to look at the queue, and to do it -often, because any time between the key being pressed and the program -noticing the event will cause the software to feel unresponsive. This -approach is called _((polling))_. Most programmers prefer to avoid it. +Of course, the program has to remember to look at the queue, and to do it often because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _((polling))_. Most programmers prefer to avoid it. {{index "callback function", "event handling"}} -A better mechanism is for the system to actively notify our code when -an event occurs. Browsers do this by allowing us to register functions -as _handlers_ for specific events. +A better mechanism is for the system to actively notify the code when an event occurs. Browsers do this by allowing us to register functions as _handlers_ for specific events. -```{lang: "text/html"} +```{lang: html} <p>Click this document to activate the handler.</p> <script> window.addEventListener("click", () => { @@ -56,24 +39,15 @@ as _handlers_ for specific events. {{index "click event", "addEventListener method", "window object", [browser, window]}} -The `window` binding refers to a built-in object provided by the -browser. It represents the browser window that contains the -document. Calling its `addEventListener` method registers the second -argument to be called whenever the event described by its first -argument occurs. +The `window` binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its `addEventListener` method registers the second argument to be called whenever the event described by its first argument occurs. ## Events and DOM nodes {{index "addEventListener method", "event handling", "window object", browser, [DOM, events]}} -Each browser event handler is registered in a context. In the previous example we called -`addEventListener` on the `window` object to register a handler -for the whole window. Such a method can also be found on DOM -elements and some other types of objects. Event listeners are -called only when the event happens in the context of the object they are -registered on. +Each browser event handler is registered in a context. In the previous example, we called `addEventListener` on the `window` object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object on which they are registered. -```{lang: "text/html"} +```{lang: html} <button>Click me</button> <p>No handler here.</p> <script> @@ -86,27 +60,19 @@ registered on. {{index "click event", "button (HTML tag)"}} -That example attaches a handler to the button node. Clicks on the -button cause that handler to run, but clicks on the rest of the -document do not. +That example attaches a handler to the button node. Clicks on the button cause that handler to run, but clicks on the rest of the document do not. {{index "onclick attribute", encapsulation}} -Giving a node an `onclick` attribute has a similar effect. This works -for most types of events—you can attach a handler through the -attribute whose name is the event name with `on` in front of it. +Giving a node an `onclick` attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is the event name with `on` in front of it. -But a node can have only one `onclick` attribute, so you can register -only one handler per node that way. The `addEventListener` method -allows you to add any number of handlers so that it is safe to add -handlers even if there is already another handler on the element. +But a node can have only one `onclick` attribute, so you can register only one handler per node that way. The `addEventListener` method allows you to add any number of handlers meaning it's safe to add handlers even if there is already another handler on the element. {{index "removeEventListener method"}} -The `removeEventListener` method, called with arguments similar to -`addEventListener`, removes a handler. +The `removeEventListener` method, called with arguments similar to `addEventListener`, removes a handler. -```{lang: "text/html"} +```{lang: html} <button>Act-once button</button> <script> let button = document.querySelector("button"); @@ -120,22 +86,15 @@ The `removeEventListener` method, called with arguments similar to {{index [function, "as value"]}} -The function given to `removeEventListener` has to be the same -function value that was given to `addEventListener`. So, to unregister -a handler, you'll want to give the function a name (`once`, in the -example) to be able to pass the same function value to both methods. +The function given to `removeEventListener` has to be the same function value given to `addEventListener`. When you need to unregister a handler, you'll want to give the handler function a name (`once`, in the example) to be able to pass the same function value to both methods. ## Event objects {{index "button property", "event handling"}} -Though we have ignored it so far, event handler functions are passed -an argument: the _((event object))_. This object holds additional -information about the event. For example, if we want to know _which_ -((mouse button)) was pressed, we can look at the event object's -`button` property. +Though we have ignored it so far, event handler functions are passed an argument: the _((event object))_. This object holds additional information about the event. For example, if we want to know _which_ ((mouse button)) was pressed, we can look at the event object's `button` property. -```{lang: "text/html"} +```{lang: html} <button>Click me any way you want</button> <script> let button = document.querySelector("button"); @@ -153,10 +112,7 @@ information about the event. For example, if we want to know _which_ {{index "event type", "type property"}} -The information stored in an event object differs per type of event. -We'll discuss different types later in the chapter. The object's -`type` property always holds a string identifying the event (such as -`"click"` or `"mousedown"`). +The information stored in an event object differs per type of event. (We'll discuss different types later in the chapter.) The object's `type` property always holds a string identifying the event (such as `"click"` or `"mousedown"`). ## Propagation @@ -166,38 +122,21 @@ We'll discuss different types later in the chapter. The object's {{indexsee propagation, "event propagation"}} -For most event types, handlers registered on nodes with children will -also receive events that happen in the children. If a button inside a -paragraph is clicked, event handlers on the paragraph will also see -the click event. +For most event types, handlers registered on nodes with children will also receive events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also see the click event. {{index "event handling"}} -But if both the paragraph and the button have a handler, the more -specific handler—the one on the button—gets to go first. The event is -said to _propagate_ outward, from the node where it happened to that -node's parent node and on to the root of the document. Finally, after -all handlers registered on a specific node have had their turn, -handlers registered on the whole ((window)) get a chance to respond to -the event. +But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to _propagate_ outward from the node where it happened to that node's parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole ((window)) get a chance to respond to the event. {{index "stopPropagation method", "click event"}} -At any point, an event handler can call the `stopPropagation` method -on the event object to prevent handlers further up from receiving the -event. This can be useful when, for example, you have a button inside -another clickable element and you don't want clicks on the button to -activate the outer element's click behavior. +At any point, an event handler can call the `stopPropagation` method on the event object to prevent handlers further up from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don't want clicks on the button to activate the outer element's click behavior. {{index "mousedown event", "pointer event"}} -The following example registers `"mousedown"` handlers on both a -button and the paragraph around it. When clicked with the right mouse -button, the handler for the button calls `stopPropagation`, which will -prevent the handler on the paragraph from running. When the button is -clicked with another ((mouse button)), both handlers will run. +The following example registers `"mousedown"` handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls `stopPropagation`, which will prevent the handler on the paragraph from running. When the button is clicked with another ((mouse button)), both handlers will run. -```{lang: "text/html"} +```{lang: html} <p>A paragraph with a <button>button</button>.</p> <script> let para = document.querySelector("p"); @@ -214,19 +153,11 @@ clicked with another ((mouse button)), both handlers will run. {{index "event propagation", "target property"}} -Most event objects have a `target` property that refers to the node -where they originated. You can use this property to ensure that you're -not accidentally handling something that propagated up from a node you -do not want to handle. +Most event objects have a `target` property that refers to the node where they originated. You can use this property to ensure that you're not accidentally handling something that propagated up from a node you do not want to handle. -It is also possible to use the `target` property to cast a wide net -for a specific type of event. For example, if you have a node -containing a long list of buttons, it may be more convenient to -register a single click handler on the outer node and have it use the -`target` property to figure out whether a button was clicked, rather -than register individual handlers on all of the buttons. +It is also possible to use the `target` property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the `target` property to figure out whether a button was clicked, rather than registering individual handlers on all of the buttons. -```{lang: "text/html"} +```{lang: html} <button>A</button> <button>B</button> <button>C</button> @@ -243,27 +174,17 @@ than register individual handlers on all of the buttons. {{index scrolling, "default behavior", "event handling"}} -Many events have a default action associated with them. If you click a -((link)), you will be taken to the link's target. If you press the -down arrow, the browser will scroll the page down. If you right-click, -you'll get a context menu. And so on. +Many events have a default action. If you click a ((link)), you will be taken to the link's target. If you press the down arrow, the browser will scroll the page down. If you right-click, you'll get a context menu. And so on. {{index "preventDefault method"}} -For most types of events, the JavaScript event handlers are called -_before_ the default behavior takes place. If the handler doesn't want -this normal behavior to happen, typically because it has already taken -care of handling the event, it can call the `preventDefault` method on -the event object. +For most types of events, the JavaScript event handlers are called _before_ the default behavior takes place. If the handler doesn't want this normal behavior to happen, typically because it has already taken care of handling the event, it can call the `preventDefault` method on the event object. {{index expectation}} -This can be used to implement your own ((keyboard)) shortcuts or -((context menu)). It can also be used to obnoxiously interfere with -the behavior that users expect. For example, here is a link that -cannot be followed: +This can be used to implement your own ((keyboard)) shortcuts or ((context menu)). It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed: -```{lang: "text/html"} +```{lang: html} <a href="https://developer.mozilla.org/">MDN</a> <script> let link = document.querySelector("a"); @@ -276,23 +197,17 @@ cannot be followed: {{index usability}} -Try not to do such things unless you have a really good reason to. -It'll be unpleasant for people who use your page when expected -behavior is broken. +Try not to do such things without a really good reason. It'll be unpleasant for people who use your page when expected behavior is broken. -Depending on the browser, some events can't be intercepted at all. On -Chrome, for example, the ((keyboard)) shortcut to close the current -tab ([control]{keyname}-W or [command]{keyname}-W) cannot be handled by JavaScript. +Depending on the browser, some events can't be intercepted at all. On Chrome, for example, the ((keyboard)) shortcut to close the current tab ([ctrl]{keyname}-W or [command]{keyname}-W) cannot be handled by JavaScript. ## Key events {{index keyboard, "keydown event", "keyup event", "event handling"}} -When a key on the keyboard is pressed, your browser fires a -`"keydown"` event. When it is released, you get a `"keyup"` -event. +When a key on the keyboard is pressed, your browser fires a `"keydown"` event. When it is released, you get a `"keyup"` event. -```{lang: "text/html", focus: true} +```{lang: html, focus: true} <p>This page turns violet when you hold the V key.</p> <script> window.addEventListener("keydown", event => { @@ -310,34 +225,17 @@ event. {{index "repeating key"}} -Despite its name, `"keydown"` fires not only when the key is -physically pushed down. When a key is pressed and held, the event -fires again every time the key _repeats_. Sometimes you have to be -careful about this. For example, if you add a button to the DOM when a -key is pressed and remove it again when the key is released, you -might accidentally add hundreds of buttons when the key is held down -longer. +Despite its name, `"keydown"` fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key _repeats_. Sometimes you have to be careful about this. For example, if you add a button to the DOM when a key is pressed and remove it again when the key is released, you might accidentally add hundreds of buttons when the key is held down longer. {{index "key property"}} -The example looked at the `key` property of the event object to see -which key the event is about. This property holds a string that, for -most keys, corresponds to the thing that pressing that key would type. -For special keys such as [enter]{keyname}, it holds a string that names the key -(`"Enter"`, in this case). If you hold [shift]{keyname} while pressing a key, -that might also influence the name of the key—`"v"` becomes `"V"`, and -`"1"` may become `"!"`, if that is what pressing [shift]{keyname}-1 produces on -your keyboard. +The previous example looks at the `key` property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys such as [enter]{keyname}, it holds a string that names the key (`"Enter"`, in this case). If you hold [shift]{keyname} while pressing a key, that might also influence the name of the key—`"v"` becomes `"V"`, and `"1"` may become `"!"`, if that is what pressing [shift]{keyname}-1 produces on your keyboard. {{index "modifier key", "shift key", "control key", "alt key", "meta key", "command key", "ctrlKey property", "shiftKey property", "altKey property", "metaKey property"}} -Modifier keys such as [shift]{keyname}, [control]{keyname}, [alt]{keyname}, and [meta]{keyname} ([command]{keyname} on Mac) -generate key events just like normal keys. But when looking for key -combinations, you can also find out whether these keys are held down -by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` -properties of keyboard and mouse events. +Modifier keys such as [shift]{keyname}, [ctrl]{keyname}, [alt]{keyname}, and [meta]{keyname} ([command]{keyname} on Mac) generate key events just like normal keys. When looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events. -```{lang: "text/html", focus: true} +```{lang: html, focus: true} <p>Press Control-Space to continue.</p> <script> window.addEventListener("keydown", event => { @@ -350,77 +248,41 @@ properties of keyboard and mouse events. {{index "button (HTML tag)", "tabindex attribute", [DOM, events]}} -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. - -To notice when something was typed, elements that you can type into, -such as the `<input>` and `<textarea>` tags, fire `"input"` events -whenever the user changes their content. To get the actual content -that was typed, it is best to directly read it from the focused field. -[Chapter ?](http#forms) will show how. +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 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). ## Pointer events -There are currently two widely used ways to point at things on a -screen: mice (including devices that act like mice, such as touchpads -and trackballs) and touchscreens. These produce different kinds of -events. +There are currently two widely used ways to point at things on a screen: mice (including devices that act like mice, such as touchpads and trackballs) and touchscreens. These produce different kinds of events. ### Mouse clicks {{index "mousedown event", "mouseup event", "mouse cursor"}} -Pressing a ((mouse button)) causes a number of events to fire. The -`"mousedown"` and `"mouseup"` events are similar to `"keydown"` and -`"keyup"` and fire when the button is pressed and released. These -happen on the DOM nodes that are immediately below the mouse pointer -when the event occurs. +Pressing a ((mouse button)) causes a number of events to fire. The `"mousedown"` and `"mouseup"` events are similar to `"keydown"` and `"keyup"` and fire when the button is pressed and released. These happen on the DOM nodes that are immediately below the mouse pointer when the event occurs. {{index "click event"}} -After the `"mouseup"` event, a `"click"` event fires on the most -specific node that contained both the press and the release of the -button. For example, if I press down the mouse button on one paragraph -and then move the pointer to another paragraph and release the button, -the `"click"` event will happen on the element that contains both -those paragraphs. +After the `"mouseup"` event, a `"click"` event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the `"click"` event will happen on the element that contains both those paragraphs. {{index "dblclick event", "double click"}} -If two clicks happen close together, a `"dblclick"` (double-click) -event also fires, after the second click event. +If two clicks happen close together, a `"dblclick"` (double-click) event also fires, after the second click event. {{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"}} {{id mouse_drawing}} -The following implements a primitive drawing program. Every time you -click the document, it adds a dot under your mouse pointer. See -[Chapter ?](paint) for a less primitive drawing program. +The following program implements a primitive drawing application. Every time you click the document, it adds a dot under your mouse pointer. -```{lang: "text/html"} +```{lang: html} <style> body { height: 200px; @@ -429,7 +291,7 @@ click the document, it adds a dot under your mouse pointer. See .dot { height: 8px; width: 8px; border-radius: 4px; /* rounds corners */ - background: blue; + background: teal; position: absolute; } </style> @@ -444,22 +306,19 @@ click the document, it adds a dot under your mouse pointer. See </script> ``` +We'll create a less primitive drawing application in [Chapter ?](paint). + ### Mouse motion {{index "mousemove event"}} -Every time the mouse pointer moves, a `"mousemove"` event is fired. -This event can be used to track the position of the mouse. A common -situation in which this is useful is when implementing some form of -mouse-((dragging)) functionality. +Every time the mouse pointer moves, a `"mousemove"` event fires. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-((dragging)) functionality. {{index "draggable bar example"}} -As an example, the following program displays a bar and sets up event -handlers so that dragging to the left or right on this bar makes it -narrower or wider: +As an example, the following program displays a bar and sets up event handlers so that dragging to the left or right on this bar makes it narrower or wider: -```{lang: "text/html", startCode: true} +```{lang: html, startCode: true} <p>Drag the bar to change its width:</p> <div style="background: orange; width: 60px; height: 20px"> </div> @@ -491,72 +350,41 @@ narrower or wider: The resulting page looks like this: -{{figure {url: "img/drag-bar.png", alt: "A draggable bar",width: "5.3cm"}}} +{{figure {url: "img/drag-bar.png", alt: "Picture of a draggable bar", width: "5.3cm"}}} if}} {{index "mouseup event", "mousemove event"}} -Note that the `"mousemove"` handler is registered on the whole -((window)). Even if the mouse goes outside of the bar during resizing, -as long as the button is held we still want to update its size. +Note that the `"mousemove"` handler is registered on the whole ((window)). Even if the mouse goes outside of the bar during resizing, as long as the button is held, we still want to update its size. {{index "buttons property", "button property", "bitfield"}} -We must stop resizing the bar when the mouse button is released. For -that, we can use the `buttons` property (note the plural), which tells -us about the buttons that are currently held down. When this is zero, -no buttons are down. When buttons are held, its value is the sum of -the codes for those buttons—the left button has code 1, the right -button 2, and the middle one 4. With the left and right buttons held, -for example, the value of `buttons` will be 3. +We must stop resizing the bar when the mouse button is released. For that, we can use the `buttons` property (note the plural), which tells us about the buttons that are currently held down. When it is 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 really a strong point of the browser's -programming interface. +Note that the order of these codes is different from the one used by `button`, where the middle button came before the right one. As mentioned, consistency isn't a strong point of the browser's programming interface. ### Touch events {{index touch, "mousedown event", "mouseup event", "click event"}} -The style of graphical browser that we use was designed with mouse -interfaces in mind, at a time where touchscreens were rare. To -make the Web "work" on early touchscreen phones, browsers for those -devices pretended, to a certain extent, that touch events were mouse -events. If you tap your screen, you'll get `"mousedown"`, `"mouseup"`, -and `"click"` events. +The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the web "work" on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you'll get `"mousedown"`, `"mouseup"`, and `"click"` events. -But this illusion isn't very robust. A touchscreen works differently -from a mouse: it doesn't have multiple buttons, you can't track the -finger when it isn't on the screen (to simulate `"mousemove"`), and it -allows multiple fingers to be on the screen at the same time. +But this illusion isn't very robust. A touchscreen doesn't work like a mouse: it doesn't have multiple buttons, you can't track the finger when it isn't on the screen (to simulate `"mousemove"`), and it allows multiple fingers to be on the screen at the same time. -Mouse events cover touch interaction only in straightforward cases—if -you add a `"click"` handler to a button, touch users will still be -able to use it. But something like the resizeable bar in the previous -example does not work on a touchscreen. +Mouse events cover touch interaction only in straightforward cases—if you add a `"click"` handler to a button, touch users will still be able to use it. But something like the resizeable bar in the previous example does not work on a touchscreen. {{index "touchstart event", "touchmove event", "touchend event"}} -There are specific event types fired by touch interaction. When a -finger starts touching the screen, you get a `"touchstart"` event. -When it is moved while touching, `"touchmove"` events fire. -Finally, when it stops touching the screen, you'll see a `"touchend"` -event. +There are specific event types fired by touch interaction. When a finger starts touching the screen, you get a `"touchstart"` event. When it is moved while touching, `"touchmove"` events fire. Finally, when it stops touching the screen, you'll see a `"touchend"` event. {{index "touches property", "clientX property", "clientY property", "pageX property", "pageY property"}} -Because many touchscreens can detect multiple fingers at the same -time, these events don't have a single set of coordinates associated -with them. Rather, their ((event object))s have a `touches` property, -which holds an ((array-like object)) of points, each of which has its -own `clientX`, `clientY`, `pageX`, and `pageY` properties. +Because many touchscreens can detect multiple fingers at the same time, these events don't have a single set of coordinates associated with them. Rather, their ((event object))s have a `touches` property, which holds an ((array-like object)) of points, each of which has its own `clientX`, `clientY`, `pageX`, and `pageY` properties. -You could do something like this to show red circles around every -touching finger: +You could do something like this to show red circles around every touching finger: -```{lang: "text/html"} +```{lang: html} <style> dot { position: absolute; display: block; border: 2px solid red; border-radius: 50px; @@ -584,26 +412,17 @@ touching finger: {{index "preventDefault method"}} -You'll often want to call `preventDefault` in touch event handlers to -override the browser's default behavior (which may include scrolling -the page on swiping) and to prevent the mouse events from being fired, -for which you may _also_ have a handler. +You'll often want to call `preventDefault` in touch event handlers to override the browser's default behavior (which may include scrolling the page on swiping) and to prevent the mouse events from being fired, for which you may _also_ have a handler. ## Scroll events {{index scrolling, "scroll event", "event handling"}} -Whenever an element is scrolled, a `"scroll"` event is fired on it. -This has various uses, such as knowing what the user is currently -looking at (for disabling off-screen ((animation))s or sending ((spy)) -reports to your evil headquarters) or showing some indication of -progress (by highlighting part of a table of contents or showing a -page number). +Whenever an element is scrolled, a `"scroll"` event is fired on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen ((animation))s or sending ((spy)) reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number). -The following example draws a ((progress bar)) above the document and -updates it to fill up as you scroll down: +The following example draws a ((progress bar)) above the document and updates it to fill up as you scroll down: -```{lang: "text/html"} +```{lang: html} <style> #progress { border-bottom: 2px solid blue; @@ -628,47 +447,31 @@ updates it to fill up as you scroll down: {{index "unit (CSS)", scrolling, "position (CSS)", "fixed positioning", "absolute positioning", percentage, "repeat method"}} -Giving an element a `position` of `fixed` acts much like an `absolute` -position but also prevents it from scrolling along with the rest of -the document. The effect is to make our progress bar stay at the top. -Its width is changed to indicate the current progress. We use `%`, -rather than `px`, as a unit when setting the width so that the element -is sized relative to the page width. +Giving an element a `position` of `fixed` acts much like an `absolute` position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay at the top. Its width is changed to indicate the current progress. We use `%`, rather than `px`, as a unit when setting the width so that the element is sized relative to the page width. {{index "innerHeight property", "innerWidth property", "pageYOffset property"}} -The global `innerHeight` binding gives us the height of the window, -which we have to subtract from the total scrollable height—you can't -keep scrolling when you hit the bottom of the document. There's also -an `innerWidth` for the window width. By dividing `pageYOffset`, the -current scroll position, by the maximum scroll position and -multiplying by 100, we get the percentage for the progress bar. +The global `innerHeight` binding gives us the height of the window, which we must subtract from the total scrollable height—you can't keep scrolling when you hit the bottom of the document. There's also an `innerWidth` for the window width. By dividing `pageYOffset`, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar. {{index "preventDefault method"}} -Calling `preventDefault` on a scroll event does not prevent the -scrolling from happening. In fact, the event handler is called only -_after_ the scrolling takes place. +Calling `preventDefault` on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only _after_ the scrolling takes place. ## Focus events {{index "event handling", "focus event", "blur event"}} -When an element gains ((focus)), the browser fires a `"focus"` event -on it. When it loses focus, the element gets a `"blur"` event. +When an element gains ((focus)), the browser fires a `"focus"` event on it. When it loses focus, the element gets a `"blur"` event. {{index "event propagation"}} -Unlike the events discussed earlier, these two events do not -propagate. A handler on a parent element is not notified when a child -element gains or loses focus. +Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus. {{index "input (HTML tag)", "help text example"}} -The following example displays help text for the ((text field)) that -currently has focus: +The following example displays help text for the ((text field)) that currently has focus: -```{lang: "text/html"} +```{lang: html} <p>Name: <input type="text" data-help="Your full name"></p> <p>Age: <input type="text" data-help="Your age in years"></p> <p id="help"></p> @@ -690,49 +493,29 @@ currently has focus: {{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: "Providing help when a field is focused",width: "4.4cm"}}} +{{figure {url: "img/help-field.png", alt: "Screenshot of the help text below the age field", width: "4.4cm"}}} if}} {{index "focus event", "blur event"}} -The ((window)) object will receive `"focus"` and `"blur"` events when -the user moves from or to the browser tab or window in which the -document is shown. +The ((window)) object will receive `"focus"` and `"blur"` events when the user moves from or to the browser tab or window in which the document is shown. ## Load event {{index "script (HTML tag)", "load event"}} -When a page finishes loading, the `"load"` event fires on the window -and the document body objects. This is often used to schedule -((initialization)) actions that require the whole ((document)) to have -been built. Remember that the content of `<script>` tags is run -immediately when the tag is encountered. This may be too soon, for -example when the script needs to do something with parts of the -document that appear after the `<script>` tag. +When a page finishes loading, the `"load"` event fires on the window and the document body objects. This is often used to schedule ((initialization)) actions that require the whole ((document)) to have been built. Remember that the content of `<script>` tags is run immediately when the tag is encountered. This may be too soon, for example when the script needs to do something with parts of the document that appear after the `<script>` tag. {{index "event propagation", "img (HTML tag)"}} -Elements such as ((image))s and script tags that load an external file -also have a `"load"` event that indicates the files they reference -were loaded. Like the focus-related events, loading events do not -propagate. +Elements such as ((image))s and script tags that load an external file also have a `"load"` event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate. {{index "beforeunload event", "page reload", "preventDefault method"}} -When a page is closed or navigated away from (for example, by -following a link), a `"beforeunload"` event fires. The main use of -this event is to prevent the user from accidentally losing work by -closing a document. If you prevent the default behavior on this event -_and_ set the `returnValue` property on the event object to a -string, the browser will show the user a dialog asking if they really -want to leave the page. That dialog might include your string, but -because some malicious sites try to use these dialogs to confuse -people into staying on their page to look at dodgy weight loss ads, -most browsers no longer display them. +When you close page or navigate away from it (for example, by following a link), a `"beforeunload"` event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event _and_ set the `returnValue` property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight-loss ads, most browsers no longer display them. {{id timeline}} @@ -740,27 +523,13 @@ most browsers no longer display them. {{index "requestAnimationFrame function", "event handling", timeline, "script (HTML tag)"}} -In the context of the event loop, as discussed in [Chapter ?](async), -browser event handlers behave like other asynchronous notifications. -They are scheduled when the event occurs but must wait for other -scripts that are running to finish before they get a chance to run. +In the context of the event loop, as discussed in [Chapter ?](async), browser event handlers behave like other asynchronous notifications. They are scheduled when the event occurs but must wait for other scripts that are running to finish before they get a chance to run. -The fact that events can be processed only when nothing else is -running means that, if the event loop is tied up with other work, any -interaction with the page (which happens through events) will be -delayed until there's time to process it. So if you schedule too much -work, either with long-running event handlers or with lots of -short-running ones, the page will become slow and cumbersome to use. +The fact that events can be processed only when nothing else is running means that if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there's time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use. -For cases where you _really_ do want to do some time-consuming thing -in the background without freezing the page, browsers provide -something called _((web worker))s_. A worker is a JavaScript process -that runs alongside the main script, on its own timeline. +For cases where you _really_ do want to do some time-consuming thing in the background without freezing the page, browsers provide something called _((web worker))s_. A worker is a JavaScript process that runs alongside the main script, on its own timeline. -Imagine that squaring a number is a heavy, long-running computation -that we want to perform in a separate ((thread)). We could write a -file called `code/squareworker.js` that responds to messages by -computing a square and sending a message back. +Imagine that squaring a number is a heavy, long-running computation that we want to perform in a separate ((thread)). We could write a file called `code/squareworker.js` that responds to messages by computing a square and sending a message back. ``` addEventListener("message", event => { @@ -768,13 +537,9 @@ addEventListener("message", event => { }); ``` -To avoid the problems of having multiple ((thread))s touching the same -data, workers do not share their ((global scope)) or any other data -with the main script's environment. Instead, you have to communicate -with them by sending messages back and forth. +To avoid the problems of having multiple ((thread))s touching the same data, workers do not share their ((global scope)) or any other data with the main script's environment. Instead, you have to communicate with them by sending messages back and forth. -This code spawns a worker running that script, sends it a few -messages, and outputs the responses. +This code spawns a worker running that script, sends it a few messages, and outputs the responses. ```{test: no} let squareWorker = new Worker("code/squareworker.js"); @@ -787,27 +552,13 @@ squareWorker.postMessage(24); {{index "postMessage method", "message event"}} -The `postMessage` function sends a message, which will cause a -`"message"` event to fire in the receiver. The script that created the -worker sends and receives messages through the `Worker` object, -whereas the worker talks to the script that created it by sending and -listening directly on its ((global scope)). Only values that can be -represented as JSON can be sent as messages—the other side will -receive a _copy_ of them, rather than the value itself. +The `postMessage` function sends a message, which will cause a `"message"` event to fire in the receiver. The script that created the worker sends and receives messages through the `Worker` object, whereas the worker talks to the script that created it by sending and listening directly on its ((global scope)). Only values that can be represented as JSON can be sent as messages—the other side will receive a _copy_ of them, rather than the value itself. ## Timers -{{index timeout, "setTimeout function"}} +{{index timeout, "setTimeout function", "clearTimeout function"}} -We saw the `setTimeout` function in [Chapter ?](async). It schedules -another function to be called later, after a given number of -milliseconds. - -{{index "clearTimeout function"}} - -Sometimes you need to cancel a function you have scheduled. This is -done by storing the value returned by `setTimeout` and calling -`clearTimeout` on it. +The `setTimeout` function we saw in [Chapter ?](async) schedules another function to be called later, after a given number of milliseconds. Sometimes you need to cancel a function you have scheduled. You can do this by storing the value returned by `setTimeout` and calling `clearTimeout` on it. ``` let bombTimer = setTimeout(() => { @@ -822,15 +573,11 @@ if (Math.random() < 0.5) { // 50% chance {{index "cancelAnimationFrame function", "requestAnimationFrame function"}} -The `cancelAnimationFrame` function works in the same way as -`clearTimeout`—calling it on a value returned by -`requestAnimationFrame` will cancel that frame (assuming it hasn't -already been called). +The `cancelAnimationFrame` function works in the same way as `clearTimeout`. Calling it on a value returned by `requestAnimationFrame` will cancel that frame (assuming it hasn't already been called). {{index "setInterval function", "clearInterval function", repetition}} -A similar set of functions, `setInterval` and `clearInterval`, are used -to set timers that should _repeat_ every _X_ milliseconds. +A similar set of functions, `setInterval` and `clearInterval`, are used to set timers that should repeat every _X_ milliseconds. ``` let ticks = 0; @@ -847,30 +594,17 @@ let clock = setInterval(() => { {{index optimization, "mousemove event", "scroll event", blocking}} -Some types of events have the potential to fire rapidly, many times in -a row (the `"mousemove"` and `"scroll"` events, for example). When -handling such events, you must be careful not to do anything too -time-consuming or your handler will take up so much time that -interaction with the document starts to feel slow. +Some types of events have the potential to fire rapidly many times in a row, such as the `"mousemove"` and `"scroll"` events. When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow. {{index "setTimeout function"}} -If you do need to do something nontrivial in such a handler, you can -use `setTimeout` to make sure you are not doing it too often. This is -usually called _((debouncing))_ the event. There are several slightly -different approaches to this. +If you do need to do something nontrivial in such a handler, you can use `setTimeout` to make sure you are not doing it too often. This is usually called _((debouncing))_ the event. There are several slightly different approaches to this. {{index "textarea (HTML tag)", "clearTimeout function", "keydown event"}} -In the first example, we want to react when the user has typed -something, but we don't want to do it immediately for every input -event. When they are ((typing)) quickly, we just want to wait until a -pause occurs. Instead of immediately performing an action in the event -handler, we set a timeout. We also clear the previous timeout (if any) -so that when events occur close together (closer than our timeout -delay), the timeout from the previous event will be canceled. +For example, suppose we want to react when the user has typed something, but we don't want to do it immediately for every input event. When they are ((typing)) quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled. -```{lang: "text/html"} +```{lang: html} <textarea>Type something here...</textarea> <script> let textarea = document.querySelector("textarea"); @@ -884,19 +618,13 @@ delay), the timeout from the previous event will be canceled. {{index "sloppy programming"}} -Giving an undefined value to `clearTimeout` or calling it on a timeout -that has already fired has no effect. Thus, we don't have to be -careful about when to call it, and we simply do so for every event. +Giving an undefined value to `clearTimeout` or calling it on a timeout that has already fired has no effect. Thus, we don't have to be careful about when to call it, and we simply do so for every event. {{index "mousemove event"}} -We can use a slightly different pattern if we want to space responses -so that they're separated by at least a certain length of ((time)) but -want to fire them _during_ a series of events, not just afterward. For -example, we might want to respond to `"mousemove"` events by showing -the current coordinates of the mouse but only every 250 milliseconds. +We can use a slightly different pattern if we want to space responses so that they're separated by at least a certain length of ((time)) but want to fire them _during_ a series of events, not just afterward. For example, we might want to respond to `"mousemove"` events by showing the current coordinates of the mouse, but only every 250 milliseconds. -```{lang: "text/html"} +```{lang: html} <script> let scheduled = null; window.addEventListener("mousemove", event => { @@ -914,29 +642,15 @@ the current coordinates of the mouse but only every 250 milliseconds. ## Summary -Event handlers make it possible to detect and react to events -happening in our web page. The `addEventListener` method is used to -register such a handler. +Event handlers make it possible to detect and react to events happening in our web page. The `addEventListener` method is used to register such a handler. -Each event has a type (`"keydown"`, `"focus"`, and so on) that -identifies it. Most events are called on a specific DOM element and -then _propagate_ to that element's ancestors, allowing handlers -associated with those elements to handle them. +Each event has a type (`"keydown"`, `"focus"`, and so on) that identifies it. Most events are called on a specific DOM element and then propagate to that element's ancestors, allowing handlers associated with those elements to handle them. -When an event handler is called, it is passed an event object with -additional information about the event. This object also has methods -that allow us to stop further propagation (`stopPropagation`) and -prevent the browser's default handling of the event -(`preventDefault`). +When an event handler is called, it's passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (`stopPropagation`) and prevent the browser's default handling of the event (`preventDefault`). -Pressing a key fires `"keydown"` and `"keyup"` events. Pressing a -mouse button fires `"mousedown"`, `"mouseup"`, and `"click"` events. -Moving the mouse fires `"mousemove"` events. Touchscreen interaction -will result in `"touchstart"`, `"touchmove"`, and `"touchend"` events. +Pressing a key fires `"keydown"` and `"keyup"` events. Pressing a mouse button fires `"mousedown"`, `"mouseup"`, and `"click"` events. Moving the mouse fires `"mousemove"` events. Touchscreen interaction will result in `"touchstart"`, `"touchmove"`, and `"touchend"` events. -Scrolling can be detected with the `"scroll"` event, and focus changes -can be detected with the `"focus"` and `"blur"` events. When the -document finishes loading, a `"load"` event fires on the window. +Scrolling can be detected with the `"scroll"` event, and focus changes can be detected with the `"focus"` and `"blur"` events. When the document finishes loading, a `"load"` event fires on the window. ## Exercises @@ -944,28 +658,19 @@ document finishes loading, a `"load"` event fires on the window. {{index "balloon (exercise)", "arrow key"}} -Write a page that displays a ((balloon)) (using the balloon ((emoji)), -🎈). When you press the up arrow, it should inflate (grow) 10 percent, -and when you press the down arrow, it should deflate (shrink) 10 percent. +Write a page that displays a ((balloon)) (using the balloon ((emoji)), 🎈). When you press the up arrow, it should inflate (grow) 10 percent. When you press the down arrow, it should deflate (shrink) 10 percent. {{index "font-size (CSS)"}} -You can control the size of text (emoji are text) by setting the -`font-size` CSS property (`style.fontSize`) on its parent element. -Remember to include a unit in the value—for example, pixels (`10px`). +You can control the size of text (emoji are text) by setting the `font-size` CSS property (`style.fontSize`) on its parent element. Remember to include a unit in the value—for example, pixels (`10px`). -The key names of the arrow keys are `"ArrowUp"` and `"ArrowDown"`. -Make sure the keys change only the balloon, without scrolling the -page. +The key names of the arrow keys are `"ArrowUp"` and `"ArrowDown"`. Make sure the keys change only the balloon, without scrolling the page. -When that works, add a feature where, if you blow up the balloon past -a certain size, it explodes. In this case, exploding means that it is -replaced with an 💥 emoji, and the event handler is removed (so that -you can't inflate or deflate the explosion). +Once you have that working, add a feature where if you blow up the balloon past a certain size, it “explodes”. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can't inflate or deflate the explosion). {{if interactive -```{test: no, lang: "text/html", focus: yes} +```{test: no, lang: html, focus: yes} <p>🎈</p> <script> @@ -979,21 +684,13 @@ if}} {{index "keydown event", "key property", "balloon (exercise)"}} -You'll want to register a handler for the `"keydown"` event and look -at `event.key` to figure out whether the up or down arrow key was -pressed. +You'll want to register a handler for the `"keydown"` event and look at `event.key` to figure out whether the up or down arrow key was pressed. -The current size can be kept in a binding so that you can base the -new size on it. It'll be helpful to define a function that updates the -size—both the binding and the style of the balloon in the DOM—so that -you can call it from your event handler, and possibly also once when -starting, to set the initial size. +The current size can be kept in a binding so that you can base the new size on it. It'll be helpful to define a function that updates the size—both the binding and the style of the balloon in the DOM—so that you can call it from your event handler, and possibly also once when starting, to set the initial size. {{index "replaceChild method", "textContent property"}} -You can change the balloon to an explosion by replacing the text node -with another one (using `replaceChild`) or by setting the -`textContent` property of its parent node to a new string. +You can change the balloon to an explosion by replacing the text node with another one (using `replaceChild`) or by setting the `textContent` property of its parent node to a new string. hint}} @@ -1001,33 +698,19 @@ hint}} {{index animation, "mouse trail (exercise)"}} -In JavaScript's early days, which was the high time of ((gaudy home -pages)) with lots of animated images, people came up with some truly -inspiring ways to use the language. - -One of these was the _mouse trail_—a series of elements that would -follow the mouse pointer as you moved it across the page. +In JavaScript's early days, which was the high time of ((gaudy home pages)) with lots of animated images, people came up with some truly inspiring ways to use the language. One of these was the _mouse trail_—a series of elements that would follow the mouse pointer as you moved it across the page. {{index "absolute positioning", "background (CSS)"}} -In this exercise, I want you to implement a mouse trail. Use -absolutely positioned `<div>` elements with a fixed size and -background color (refer to the [code](event#mouse_drawing) in the -"Mouse Clicks" section for an example). Create a bunch of such -elements and, when the mouse moves, display them in the wake of the -mouse pointer. +In this exercise, I want you to implement a mouse trail. Use absolutely positioned `<div>` elements with a fixed size and background color (refer to the [code](event#mouse_drawing) in the "Mouse Clicks" section for an example). Create a bunch of these elements and, when the mouse moves, display them in the wake of the mouse pointer. {{index "mousemove event"}} -There are various possible approaches here. You can make your solution -as simple or as complex as you want. A simple solution to start with -is to keep a fixed number of trail elements and cycle through them, -moving the next one to the mouse's current position every time a -`"mousemove"` event occurs. +There are various possible approaches here. You can make your trail as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse's current position every time a `"mousemove"` event occurs. {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <style> .trail { /* className for the trail elements */ position: absolute; @@ -1051,27 +734,15 @@ if}} {{index "mouse trail (exercise)"}} -Creating the elements is best done with a loop. Append them to the -document to make them show up. To be able to access them later to change their position, you'll want to store the elements in -an array. +Creating the elements is best done with a loop. Append them to the document to make them show up. To be able to access them later to change their position, you'll want to store the elements in an array. {{index "mousemove event", [array, indexing], "remainder operator", "% operator"}} -Cycling through them can be done by keeping a ((counter variable)) and -adding 1 to it every time the `"mousemove"` event fires. The remainder -operator (`% elements.length`) can then be used to get a valid array -index to pick the element you want to position during a given event. +Cycling through them can be done by keeping a ((counter variable)) and adding 1 to it every time the `"mousemove"` event fires. The remainder operator (`% elements.length`) can then be used to get a valid array index to pick the element you want to position during a given event. {{index simulation, "requestAnimationFrame function"}} -Another interesting effect can be achieved by modeling a simple -((physics)) system. Use the `"mousemove"` event only to update a pair -of bindings that track the mouse position. Then use -`requestAnimationFrame` to simulate the trailing elements being -attracted to the position of the mouse pointer. At every animation -step, update their position based on their position relative to the -pointer (and, optionally, a speed that is stored for each element). -Figuring out a good way to do this is up to you. +Another interesting effect can be achieved by modeling a simple ((physics)) system. Use the `"mousemove"` event only to update a pair of bindings that track the mouse position. Then use `requestAnimationFrame` to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and, optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you. hint}} @@ -1079,27 +750,17 @@ hint}} {{index "tabbed interface (exercise)"}} -Tabbed panels are widely used in user interfaces. They allow you to -select an interface panel by choosing from a number of tabs "sticking -out" above an element. +Tabbed panels are common in user interfaces. They allow you to select an interface panel by choosing from a number of tabs "sticking out" above an element. {{index "button (HTML tag)", "display (CSS)", "hidden element", "data attribute"}} -In this exercise you must implement a simple tabbed interface. Write a -function, `asTabs`, that takes a DOM node and creates a tabbed -interface showing the child elements of that node. It should insert a -list of `<button>` elements at the top of the node, one for each child -element, containing text retrieved from the `data-tabname` attribute -of the child. All but one of the original children should be hidden -(given a `display` style of `none`). The currently visible node can be -selected by clicking the buttons. +Implement a simple tabbed interface. Write a function, `asTabs`, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of `<button>` elements at the top of the node, one for each child element, containing text retrieved from the `data-tabname` attribute of the child. All but one of the original children should be hidden (given a `display` style of `none`). The currently visible node can be selected by clicking the buttons. -When that works, extend it to style the button for the currently -selected tab differently so that it is obvious which tab is selected. +When that works, extend it to style the button for the currently selected tab differently so that it is obvious which tab is selected. {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <tab-panel> <div data-tabname="one">Tab one</div> <div data-tabname="two">Tab two</div> @@ -1119,26 +780,14 @@ if}} {{index "text node", "childNodes property", "live data structure", "tabbed interface (exercise)", [whitespace, "in HTML"]}} -One pitfall you might run into is that you can't directly use the -node's `childNodes` property as a collection of tab nodes. For one -thing, when you add the buttons, they will also become child nodes and -end up in this object because it is a live data structure. For -another, the text nodes created for the whitespace between the -nodes are also in `childNodes` but should not get their own tabs. You -can use `children` instead of `childNodes` to ignore text nodes. +One pitfall you might run into is that you can't directly use the node's `childNodes` property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is a live data structure. For another, the text nodes created for the whitespace between the nodes are also in `childNodes` but should not get their own tabs. You can use `children` instead of `childNodes` to ignore text nodes. {{index "TEXT_NODE code", "nodeType property"}} -You could start by building up an array of tabs so that you have easy -access to them. To implement the styling of the buttons, you could -store objects that contain both the tab panel and its button. +You could start by building up an array of tabs so that you have easy access to them. To implement the styling of the buttons, you could store objects that contain both the tab panel and its button. -I recommend writing a separate function for changing tabs. You can -either store the previously selected tab and change only the styles -needed to hide that and show the new one, or you can just update the -style of all tabs every time a new tab is selected. +I recommend writing a separate function for changing tabs. You can either store the previously selected tab and change only the styles needed to hide that and show the new one, or you can just update the style of all tabs every time a new tab is selected. -You might want to call this function immediately to make the -interface start with the first tab visible. +You might want to call this function immediately to make the interface start with the first tab visible. hint}} diff --git a/16_game.md b/16_game.md index 00f34d05e..c41e970fb 100644 --- a/16_game.md +++ b/16_game.md @@ -1,4 +1,4 @@ -{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js"], zip: "html include=[\"css/game.css\"]"}}} +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/_stop_keys.js"], zip: "html include=[\"css/game.css\"]"}}} # Project: A Platform Game @@ -8,113 +8,65 @@ All reality is a game. quote}} -{{index "Banks, Ian", "project chapter", simulation}} +{{index "Banks, Iain", "project chapter", simulation}} -{{figure {url: "img/chapter_picture_16.jpg", alt: "Picture of a game character jumping over lava", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_16.jpg", alt: "Illustration showing a computer game character jumping over lava in a two dimensional world", chapter: "framed"}}} -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. +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}} -This chapter will walk through the implementation of a small -((platform game)). Platform games (or "jump and run" games) are games -that expect the ((player)) to move a figure through a ((world)), which -is usually two-dimensional and viewed from the side, while jumping over and -onto things. +This chapter will walk through the implementation of a small ((platform game)). Platform games (or "jump and run" games) are games that expect the ((player)) to move a figure through a ((world)), which is usually two-dimensional and viewed from the side, while jumping over and onto things. ## The game {{index minimalism, "Palef, Thomas", "Dark Blue (game)"}} -Our ((game)) will be roughly based on [Dark -Blue](http://www.lessmilk.com/games/10)[ -(_www.lessmilk.com/games/10_)]{if book} by Thomas Palef. I chose that -game because it is both entertaining and minimalist and because it -can be built without too much ((code)). It looks like this: +Our ((game)) will be roughly based on [Dark Blue](http://www.lessmilk.com/games/10)[ (_www.lessmilk.com/games/10_)]{if book} by Thomas Palef. I chose that game because it is both entertaining and minimalist and because it can be built without too much ((code)). It looks like this: -{{figure {url: "img/darkblue.png", alt: "The game Dark Blue"}}} +{{figure {url: "img/darkblue.png", alt: "Screenshot of the 'Dark Blue' game, showing a world made out of colored boxes. There's a black box representing the player, standing on lines of white against a blue background. Small yellow coins float in the air, and some parts of the background are red, representing lava."}}} {{index coin, lava}} -The dark ((box)) represents the ((player)), whose task is to collect -the yellow boxes (coins) while avoiding the red stuff (lava). A -((level)) is completed when all coins have been collected. +The dark ((box)) represents the ((player)), whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava). A ((level)) is completed when all coins have been collected. {{index keyboard, jumping}} -The player can walk around with the left and right arrow keys and can -jump with the up arrow. Jumping is a specialty of this game character. -It can reach several times its own height and can change -direction in midair. This may not be entirely realistic, but it helps -give the player the feeling of being in direct control of the on-screen -((avatar)). +The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is this game character's specialty. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen ((avatar)). {{index "fractional number", discretization, "artificial life", "electronic life"}} -The ((game)) consists of a static ((background)), laid out like a -((grid)), with the moving elements overlaid on that background. Each -field on the grid is either empty, solid, or ((lava)). The moving -elements are the player, coins, and certain pieces of lava. The -positions of these elements are not constrained to the grid—their -coordinates may be fractional, allowing smooth ((motion)). +The ((game)) consists of a static ((background)), laid out like a ((grid)), with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or ((lava)). The moving elements are the player, coins, and certain pieces of lava. The positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth ((motion)). ## The technology {{index "event handling", keyboard, [DOM, graphics]}} -We will use the ((browser)) DOM to display the game, and we'll -read user input by handling key events. +We will use the ((browser)) DOM to display the game, and we'll read user input by handling key events. {{index rectangle, "background (CSS)", "position (CSS)", graphics}} -The screen- and keyboard-related code is only a small part of the work -we need to do to build this ((game)). Since everything looks like -colored ((box))es, drawing is uncomplicated: we create DOM elements -and use styling to give them a background color, size, and position. +The screen- and keyboard-related code is only a small part of the work we need to do to build this ((game)). Since everything looks like colored ((box))es, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position. {{index "table (HTML tag)"}} -We can represent the background as a table since it is an unchanging -((grid)) of squares. The free-moving elements can be overlaid using -absolutely positioned elements. +We can represent the background as a table, since it is an unchanging ((grid)) of squares. The free-moving elements can be overlaid using absolutely positioned elements. {{index performance, [DOM, graphics]}} -In games and other programs that should animate ((graphics)) and -respond to user ((input)) without noticeable delay, ((efficiency)) is -important. Although the DOM was not originally designed for -high-performance graphics, it is actually better at this than you -would expect. You saw some ((animation))s in [Chapter -?](dom#animation). On a modern machine, a simple game like this -performs well, even if we don't worry about ((optimization)) very -much. +In games and other programs that should animate ((graphics)) and respond to user ((input)) without noticeable delay, ((efficiency)) is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. You saw some ((animation))s in [Chapter ?](dom#animation). On a modern machine, a simple game like this performs well, even if we don't worry about ((optimization)) very much. {{index canvas, [DOM, graphics]}} -In the [next chapter](canvas), we will explore another ((browser)) -technology, the `<canvas>` tag, which provides a more traditional way -to draw graphics, working in terms of shapes and ((pixel))s rather -than DOM elements. +In the [next chapter](canvas), we will explore another ((browser)) technology, the `<canvas>` tag, which provides a more traditional way to draw graphics, working in terms of shapes and ((pixel))s rather than DOM elements. ## Levels {{index dimensions}} -We'll want a human-readable, human-editable way to specify levels. -Since it is okay for everything to start out on a grid, we could use -big strings in which each character represents an element—either a -part of the background grid or a moving element. +We'll want a human-readable, human-editable way to specify levels. Since it is okay for everything to start out on a grid, we could use big strings in which each character represents an element—either a part of the background grid or a moving element. The plan for a small level might look like this: @@ -133,23 +85,13 @@ let simpleLevelPlan = ` {{index level}} -Periods are empty space, hash (`#`) characters are walls, and plus -signs are lava. The ((player))'s starting position is the ((at sign)) -(`@`). Every O character is a coin, and the equal sign (`=`) at the -top is a block of lava that moves back and forth horizontally. +Periods are empty space, hash (`#`) characters are walls, and plus signs are lava. The ((player))'s starting position is the ((at sign)) (`@`). Every O character is a coin, and the equal sign (`=`) at the top is a block of lava that moves back and forth horizontally. {{index bouncing}} -We'll support two additional kinds of moving ((lava)): the pipe -character (`|`) creates vertically moving blobs, and `v` indicates -_dripping_ lava—vertically moving lava that doesn't bounce back and -forth but only moves down, jumping back to its start position when it -hits the floor. +We'll support two additional kinds of moving ((lava)): the pipe character (`|`) creates vertically moving blobs, and `v` indicates _dripping_ lava—vertically moving lava that doesn't bounce back and forth but only moves down, jumping back to its start position when it hits the floor. -A whole ((game)) consists of multiple ((level))s that the ((player)) -must complete. A level is completed when all ((coin))s have been -collected. If the player touches ((lava)), the current level is -restored to its starting position, and the player may try again. +A whole ((game)) consists of multiple ((level))s that the ((player)) must complete. A level is completed when all ((coin))s have been collected. If the player touches ((lava)), the current level is restored to its starting position, and the player may try again. {{id level}} @@ -157,8 +99,7 @@ restored to its starting position, and the player may try again. {{index "Level class"}} -The following ((class)) stores a ((level)) object. Its argument should -be the string that defines the level. +The following ((class)) stores a ((level)) object. Its argument should be the string that defines the level. ```{includeCode: true} class Level { @@ -171,10 +112,12 @@ class Level { this.rows = rows.map((row, y) => { return row.map((ch, x) => { let type = levelChars[ch]; - if (typeof type == "string") return type; - this.startActors.push( - type.create(new Vec(x, y), ch)); - return "empty"; + if (typeof type != "string") { + let pos = new Vec(x, y); + this.startActors.push(type.create(pos, ch)); + type = "empty"; + } + return type; }); }); } @@ -183,50 +126,27 @@ class Level { {{index "trim method", "split method", [whitespace, trimming]}} -The `trim` method is used to remove whitespace at the start and -end of the plan string. This allows our example plan to start with a -newline so that all the lines are directly below each other. The -remaining string is split on ((newline character))s, and each line is -spread into an array, producing arrays of characters. +The `trim` method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all lines are directly below each other. The remaining string is split on ((newline character))s, and each line is spread into an array, producing arrays of characters. {{index [array, "as matrix"]}} -So `rows` holds an array of arrays of characters, the rows of the -plan. We can derive the level's width and height from these. But we -must still separate the moving elements from the background grid. -We'll call moving elements _actors_. They'll be stored in an array of -objects. The background will be an array of arrays of strings, holding -field types such as `"empty"`, `"wall"`, or `"lava"`. +So `rows` holds an array of arrays of characters, the rows of the plan. We can derive the level's width and height from these. But we must still separate the moving elements from the background grid. We'll call moving elements _actors_. They'll be stored in an array of objects. The background will be an array of arrays of strings, holding field types such as `"empty"`, `"wall"`, or `"lava"`. {{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"}} -To interpret the characters in the plan, the `Level` constructor uses -the `levelChars` object, which maps background elements to strings and -actor characters to classes. When `type` is an actor class, its static -`create` method is used to create an object, which is added to -`startActors`, and the mapping function returns `"empty"` for this -background square. +To interpret the characters in the plan, the `Level` constructor uses the `levelChars` object, which, for each character used in the level descriptions, holds a string if it is a background type, and a class if it produces an actor. When `type` is an actor class, its static `create` method is used to create an object, which is added to `startActors`, and the mapping function returns `"empty"` for this background square. {{index "Vec class"}} -The position of the actor is stored as a `Vec` object. This is a -two-dimensional vector, an object with `x` and `y` properties, as seen -in the exercises of [Chapter ?](object#exercise_vector). +The position of the actor is stored as a `Vec` object. This is a two-dimensional vector, an object with `x` and `y` properties, as seen in the exercises of [Chapter ?](object#exercise_vector). {{index [state, in objects]}} -As the game runs, actors will end up in different places or even -disappear entirely (as coins do when collected). We'll use a `State` -class to track the state of a running game. +As the game runs, actors will end up in different places or even disappear entirely (as coins do when collected). We'll use a `State` class to track the state of a running game. ```{includeCode: true} class State { @@ -246,42 +166,25 @@ class State { } ``` -The `status` property will switch to `"lost"` or `"won"` when the game -has ended. +The `status` property will switch to `"lost"` or `"won"` when the game has ended. -This is again a persistent data structure—updating the game state -creates a new state and leaves the old one intact. +This is again a persistent data structure—updating the game state creates a new state and leaves the old one intact. ## Actors {{index actor, "Vec class", [interface, object]}} -Actor objects represent the current position and state of a given -moving element in our game. All actor objects conform to the same -interface. Their `pos` property holds the coordinates of the -element's top-left corner, and their `size` property holds its size. +Actor objects represent the current position and state of a given moving element (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. -Then they have an `update` method, which is used to compute their -new state and position after a given time step. It simulates the thing -the actor does—moving in response to the arrow keys for the player and -bouncing back and forth for the lava—and returns a new, updated actor -object. +This `update` method is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object. -A `type` property contains a string that identifies the type of the -actor—`"player"`, `"coin"`, or `"lava"`. This is useful when drawing -the game—the look of the rectangle drawn for an actor is based on its -type. +A `type` property contains a string that identifies the type of the actor—`"player"`, `"coin"`, or `"lava"`. This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type. -Actor classes have a static `create` method that is used by the -`Level` constructor to create an actor from a character in the level -plan. It is given the coordinates of the character and the character -itself, which is needed because the `Lava` class handles several -different characters. +Actor classes have a static `create` method that is used by the `Level` constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is necessary because the `Lava` class handles several different characters. {{id vector}} -This is the `Vec` class that we'll use for our two-dimensional values, -such as the position and size of actors. +This is the `Vec` class that we'll use for our two-dimensional values, such as the position and size of actors. ```{includeCode: true} class Vec { @@ -299,18 +202,13 @@ class Vec { {{index "times method", multiplication}} -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 `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"}} -The player class has a property `speed` that stores its current speed -to simulate momentum and gravity. +The player class has a `speed` property that stores its current speed to simulate momentum and gravity. ```{includeCode: true} class Player { @@ -330,29 +228,15 @@ class Player { Player.prototype.size = new Vec(0.8, 1.5); ``` -Because a player is one-and-a-half squares high, its initial position -is set to be half a square above the position where the `@` character -appeared. This way, its bottom aligns with the bottom of the square it -appeared in. +Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the `@` character appeared. This way, its bottom aligns with the bottom of the square where it appeared. -The `size` property is the same for all instances of `Player`, so we -store it on the prototype rather than on the instances themselves. We -could have used a ((getter)) like `type`, but that would create and -return a new `Vec` object every time the property is read, which would -be wasteful. (Strings, being ((immutable)), don't have to be re-created -every time they are evaluated.) +The `size` property is the same for all instances of `Player`, so we store it on the prototype rather than on the instances themselves. We could have used a ((getter)) like `type`, but that would create and return a new `Vec` object every time the property is read, which would be wasteful. (Strings, being ((immutable)), don't have to be re-created every time they are evaluated.) {{index "Lava class", bouncing}} -When constructing a `Lava` actor, we need to initialize the object -differently depending on the character it is based on. Dynamic lava -moves along at its current speed until it hits an obstacle. At that -point, if it has a `reset` property, it will jump back to its start -position (dripping). If it does not, it will invert its speed and -continue in the other direction (bouncing). +When constructing a `Lava` actor, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its current speed until it hits an obstacle. At that point, if it has a `reset` property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing). -The `create` method looks at the character that the `Level` -constructor passes and creates the appropriate lava actor. +The `create` method looks at the character that the `Level` constructor passes and creates the appropriate lava actor. ```{includeCode: true} class Lava { @@ -380,12 +264,7 @@ Lava.prototype.size = new Vec(1, 1); {{index "Coin class", animation}} -`Coin` actors are relatively simple. They mostly just sit in their -place. But to liven up the game a little, they are given a "wobble", a -slight vertical back-and-forth motion. To track this, a coin object -stores a base position as well as a `wobble` property that tracks the -((phase)) of the bouncing motion. Together, these determine the coin's -actual position (stored in the `pos` property). +`Coin` actors are relatively simple. They mostly just sit in their place. But to liven up the game a little, they are given a "wobble", a slight vertical back-and-forth motion. To track this, a coin object stores a base position as well as a `wobble` property that tracks the ((phase)) of the bouncing motion. Together, these determine the coin's actual position (stored in the `pos` property). ```{includeCode: true} class Coin { @@ -409,23 +288,15 @@ Coin.prototype.size = new Vec(0.6, 0.6); {{index "Math.random function", "random number", "Math.sin function", sine, wave}} -In [Chapter ?](dom#sin_cos), we saw that `Math.sin` gives us the -y-coordinate of a point on a circle. That coordinate goes back and -forth in a smooth waveform as we move along the circle, which makes -the sine function useful for modeling a wavy motion. +In [Chapter ?](dom#sin_cos), we saw that `Math.sin` gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth waveform as we move along the circle, which makes the sine function useful for modeling a wavy motion. {{index pi}} -To avoid a situation where all coins move up and down synchronously, -the starting phase of each coin is randomized. The period of -`Math.sin`'s wave, the width of a wave it produces, is 2π. We multiply -the value returned by `Math.random` by that number to give the coin a -random starting position on the wave. +To avoid a situation where all coins move up and down synchronously, the starting phase of each coin is randomized. The period of `Math.sin`'s wave, the width of a wave it produces, is 2π. We multiply the value returned by `Math.random` by that number to give the coin a random starting position on the wave. {{index map, [object, "as map"]}} -We can now define the `levelChars` object that maps plan characters to -either background grid types or actor classes. +We can now define the `levelChars` object that maps plan characters to either background grid types or actor classes. ```{includeCode: true} const levelChars = { @@ -443,70 +314,25 @@ console.log(`${simpleLevel.width} by ${simpleLevel.height}`); // → 22 by 9 ``` -The task ahead is to display such levels on the screen and to model -time and motion inside them. - -## Encapsulation as a burden - -{{index "programming style", "program size", complexity}} - -Most of the code in this chapter does not worry about -((encapsulation)) very much for two reasons. First, encapsulation -takes extra effort. It makes programs bigger and requires additional -concepts and interfaces to be introduced. Since there is only so much -code you can throw at a reader before their eyes glaze over, I've made -an effort to keep the program small. - -{{index [interface, design]}} - -Second, the various elements in this game are so closely tied together -that if the behavior of one of them changed, it is unlikely that any -of the others would be able to stay the same. Interfaces between the -elements would end up encoding a lot of assumptions about the way the -game works. This makes them a lot less effective—whenever you change -one part of the system, you still have to worry about the way it -impacts the other parts because their interfaces wouldn't cover the -new situation. - -Some _((cutting point))s_ in a system lend themselves well to -separation through rigorous interfaces, but others don't. Trying to -encapsulate something that isn't a suitable boundary is a sure way to -waste a lot of energy. When you are making this mistake, you'll -usually notice that your interfaces are getting awkwardly large and -detailed and that they need to be changed often, as the program -evolves. - -{{index graphics, encapsulation, graphics}} - -There is one thing that we _will_ encapsulate, and that is the -((drawing)) subsystem. The reason for this is that we'll ((display)) -the same game in a different way in the [next -chapter](canvas#canvasdisplay). By putting the drawing behind an -interface, we can load the same game program there and plug in a new -display ((module)). +The task ahead is to display such levels on the screen and to model time and motion inside them. {{id domdisplay}} ## Drawing -{{index "DOMDisplay class", [DOM, graphics]}} +{{index graphics, encapsulation, "DOMDisplay class", [DOM, graphics]}} + +In the [next chapter](canvas#canvasdisplay), we'll ((display)) the same game in a different way. To make that possible, we put the drawing logic behind an interface and pass it to the game as an argument. That way, we can use the same game program with different new display ((module))s. -The encapsulation of the ((drawing)) code is done by defining a -_((display))_ object, which displays a given ((level)) and state. The -display type we define in this chapter is called `DOMDisplay` because -it uses DOM elements to show the level. +A game display object draws a given ((level)) and state. We pass its constructor to the game to allow it to be replaced. The display class we define in this chapter is called `DOMDisplay` because it uses DOM elements to show the level. {{index "style attribute", CSS}} -We'll be using a style sheet to set the actual colors and other -fixed properties of the elements that make up the game. It would also -be possible to directly assign to the elements' `style` property when -we create them, but that would produce more verbose programs. +We'll be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements' `style` property when we create them, but that would produce more verbose programs. {{index "class attribute"}} -The following helper function provides a succinct way to create an -element and give it some attributes and child nodes: +The following helper function provides a succinct way to create an element and give it some attributes and child nodes: ```{includeCode: true} function elt(name, attrs, ...children) { @@ -521,8 +347,7 @@ function elt(name, attrs, ...children) { } ``` -A display is created by giving it a parent element to which it should -append itself and a ((level)) object. +A display is created by giving it a parent element to which it should append itself and a ((level)) object. ```{includeCode: true} class DOMDisplay { @@ -538,19 +363,11 @@ class DOMDisplay { {{index level}} -The level's ((background)) grid, which never changes, is drawn once. -Actors are redrawn every time the display is updated with a given -state. The `actorLayer` property will be used to track the element -that holds the actors so that they can be easily removed and replaced. +The level's ((background)) grid, which never changes, is drawn once. Actors are redrawn every time the display is updated with a given state. The `actorLayer` property will be used to track the element that holds the actors so that they can be easily removed and replaced. {{index scaling, "DOMDisplay class"}} -Our ((coordinates)) and sizes are tracked in ((grid)) units, where a -size or distance of 1 means one grid block. When setting ((pixel)) -sizes, we will have to scale these coordinates up—everything in the -game would be ridiculously small at a single pixel per square. The -`scale` constant gives the number of pixels that a single unit takes -up on the screen. +Our ((coordinates)) and sizes are tracked in ((grid)) units, where a size or distance of 1 means one grid block. When setting ((pixel)) sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The `scale` constant gives the number of pixels that a single unit takes up on the screen. ```{includeCode: true} const scale = 20; @@ -568,19 +385,13 @@ function drawGrid(level) { {{index "table (HTML tag)", "tr (HTML tag)", "td (HTML tag)", "spread operator"}} -As mentioned, the background is drawn as a `<table>` element. -This nicely corresponds to the structure of the `rows` property of the -level—each row of the grid is turned into a table row (`<tr>` -element). The strings in the grid are used as class names for the -table cell (`<td>`) elements. The spread (triple dot) operator is used -to pass arrays of child nodes to `elt` as separate arguments. +The `<table>` element's form nicely corresponds to the structure of the `rows` property of the level—each row of the grid is turned into a table row (`<tr>` element). The strings in the grid are used as class names for the table cell (`<td>`) elements. The code uses the spread (triple dot) operator to pass arrays of child nodes to `elt` as separate arguments. {{id game_css}} -The following ((CSS)) makes the table look like the background we -want: +The following ((CSS)) makes the table look like the background we want: -```{lang: "text/css"} +```{lang: "css"} .background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } @@ -591,28 +402,15 @@ want: {{index "padding (CSS)"}} -Some of these (`table-layout`, `border-spacing`, and `padding`) are -used to suppress unwanted default behavior. We don't want the layout -of the ((table)) to depend upon the contents of its cells, and we -don't want space between the ((table)) cells or padding inside them. +Some of these (`table-layout`, `border-spacing`, and `padding`) are used to suppress unwanted default behavior. We don't want the layout of the ((table)) to depend upon the contents of its cells, and we don't want space between the ((table)) cells or padding inside them. {{index "background (CSS)", "rgb (CSS)", CSS}} -The `background` rule sets the background color. CSS allows colors to -be specified both as words (`white`) or with a format such as -`rgb(R, G, B)`, where the red, green, and blue components of the color -are separated into three numbers from 0 to 255. So, in `rgb(52, 166, -251)`, the red component is 52, green is 166, and blue is 251. Since -the blue component is the largest, the resulting color will be bluish. -You can see that in the `.lava` rule, the first number (red) is the -largest. +The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) or with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255. In `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. In the `.lava` rule, the first number (red) is the largest. {{index [DOM, graphics]}} -We draw each ((actor)) by creating a DOM element for it and -setting that element's position and size based on the actor's -properties. The values have to be multiplied by `scale` to go from -game units to pixels. +We draw each ((actor)) by creating a DOM element for it and setting that element's position and size based on the actor's properties. The values must be multiplied by `scale` to go from game units to pixels. ```{includeCode: true} function drawActors(actors) { @@ -629,14 +427,9 @@ function drawActors(actors) { {{index "position (CSS)", "class attribute"}} -To give an element more than one class, we separate the class names by -spaces. In the ((CSS)) code shown next, the `actor` class gives the -actors their absolute position. Their type name is used as an extra -class to give them a color. We don't have to define the `lava` class -again because we're reusing the class for the lava grid squares we -defined earlier. +To give an element more than one class, we separate the class names by spaces. In the following ((CSS)) code, the `actor` class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don't have to define the `lava` class again because we're reusing the class for the lava grid squares we defined earlier. -```{lang: "text/css"} +```{lang: "css"} .actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); } @@ -644,14 +437,7 @@ defined earlier. {{index graphics, optimization, efficiency, [state, "of application"], [DOM, graphics]}} -The `syncState` method is used to make the display show a given state. -It first removes the old actor graphics, if any, and then redraws the -actors in their new positions. It may be tempting to try to reuse the -DOM elements for actors, but to make that work, we would need a -lot of additional bookkeeping to associate actors with DOM elements -and to make sure we remove elements when their actors vanish. Since -there will typically be only a handful of actors in the game, -redrawing all of them is not expensive. +The `syncState` method is used to make the display show a given state. It first removes the old actor graphics, if any, and then redraws the actors in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional bookkeeping to associate actors with DOM elements and to make sure we remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive. ```{includeCode: true} DOMDisplay.prototype.syncState = function(state) { @@ -665,12 +451,9 @@ DOMDisplay.prototype.syncState = function(state) { {{index level, "class attribute"}} -By adding the level's current status as a class name to the wrapper, -we can style the player actor slightly differently when the game is -won or lost by adding a ((CSS)) rule that takes effect only when the -player has an ((ancestor element)) with a given class. +By adding the level's current status as a class name to the wrapper, we can style the player actor slightly differently when the game is won or lost by adding a ((CSS)) rule that takes effect only when the player has an ((ancestor element)) with a given class. -```{lang: "text/css"} +```{lang: "css"} .lost .player { background: rgb(160, 64, 64); } @@ -681,26 +464,15 @@ player has an ((ancestor element)) with a given class. {{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 the -`scrollPlayerIntoView` call is needed. It ensures that if the level is -protruding outside the viewport, we scroll that viewport to make sure -the player is near its center. The following ((CSS)) gives the game's -wrapping DOM element a maximum size and ensures that anything that -sticks out of the element's box is not visible. We also give it -a relative position so that the actors inside it are -positioned relative to the level's top-left corner. - -```{lang: "text/css"} +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 { overflow: hidden; max-width: 600px; @@ -711,10 +483,7 @@ positioned relative to the level's top-left corner. {{index scrolling}} -In the `scrollPlayerIntoView` method, we find the player's position -and update the wrapping element's scroll position. We change the -scroll position by manipulating that element's `scrollLeft` and -`scrollTop` properties when the player is too close to the edge. +In the `scrollPlayerIntoView` method, we find the player's position and update the wrapping element's scroll position. We change the scroll position by manipulating that element's `scrollLeft` and `scrollTop` properties when the player is too close to the edge. ```{includeCode: true} DOMDisplay.prototype.scrollPlayerIntoView = function(state) { @@ -745,32 +514,19 @@ 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}} -Next, a series of checks verifies that the player position isn't outside -of the allowed range. Note that sometimes this will set nonsense -scroll coordinates that are below zero or beyond the element's scrollable -area. This is okay—the DOM will constrain them to acceptable values. -Setting `scrollLeft` to -10 will cause it to become 0. +Next, a series of checks verifies that the player position isn't outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element's scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting `scrollLeft` to `-10` will cause it to become `0`. -It would have been slightly simpler to always try to scroll the player -to the center of the ((viewport)). But this creates a rather jarring -effect. As you are jumping, the view will constantly shift up and -down. It is more pleasant to have a "neutral" area in the middle of -the screen where you can move around without causing any scrolling. +While it would have been slightly simpler to always try to scroll the player to the center of the ((viewport)), this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It's more pleasant to have a "neutral" area in the middle of the screen where you can move around without causing any scrolling. {{index [game, screenshot]}} We are now able to display our tiny level. -```{lang: "text/html"} +```{lang: html} <link rel="stylesheet" href="css/game.css"> <script> @@ -782,68 +538,39 @@ We are now able to display our tiny level. {{if book -{{figure {url: "img/game_simpleLevel.png", alt: "Our level rendered",width: "7cm"}}} +{{figure {url: "img/game_simpleLevel.png", alt: "Screenshot of the rendered level", width: "7cm"}}} if}} {{index "link (HTML tag)", CSS}} -The `<link>` tag, when used with `rel="stylesheet"`, is a way to load -a CSS file into a page. The file `game.css` contains the styles -necessary for our game. +The `<link>` tag, when used with `rel="stylesheet"`, is a way to load a CSS file into a page. The file `game.css` contains the styles necessary for our game. ## Motion and collision {{index physics, [animation, "platform game"]}} -Now we're at the point where we can start adding motion—the most -interesting aspect of the game. The basic approach, taken by most -games like this, is to split ((time)) into small steps and, for each -step, move the actors by a distance corresponding to their speed -multiplied by the size of the time step. We'll measure time in -seconds, so speeds are expressed in units per second. +Now we're at the point where we can start adding motion. The basic approach taken by most games like this is to split ((time)) into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We'll measure time in seconds, so speeds are expressed in units per second. {{index obstacle, "collision detection"}} -Moving things is easy. The difficult part is dealing with the -interactions between the elements. When the player hits a wall or -floor, they should not simply move through it. The game must notice -when a given motion causes an object to hit another object and respond -accordingly. For walls, the motion must be stopped. When hitting a -coin, it must be collected. When touching lava, the game should be lost. +Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, that coin must be collected. When touching lava, the game should be lost. -Solving this for the general case is a big task. You can find -libraries, usually called _((physics engine))s_, that simulate -interaction between physical objects in two or three ((dimensions)). -We'll take a more modest approach in this chapter, handling only -collisions between rectangular objects and handling them in a rather -simplistic way. +Solving this for the general case is a major task. You can find libraries, usually called _((physics engine))s_, that simulate interaction between physical objects in two or three ((dimensions)). We'll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way. {{index bouncing, "collision detection", [animation, "platform game"]}} -Before moving the ((player)) or a block of ((lava)), we test whether -the motion would take it inside of a wall. If it does, we simply -cancel the motion altogether. The response to such a collision depends -on the type of actor—the player will stop, whereas a lava block will -bounce back. +Before moving the ((player)) or a block of ((lava)), we test whether the motion would take it inside of a wall. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back. {{index discretization}} -This approach requires our ((time)) steps to be rather small since it -will cause motion to stop before the objects actually touch. If the -time steps (and thus the motion steps) are too big, the player would -end up hovering a noticeable distance above the ground. Another -approach, arguably better but more complicated, would be to find the -exact collision spot and move there. We will take the simple approach -and hide its problems by ensuring the animation proceeds in small -steps. +This approach requires our ((time)) steps to be rather small, since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps. {{index obstacle, "touches method", "collision detection"}} {{id touches}} -This method tells us whether a ((rectangle)) (specified by a position -and a size) touches a grid element of the given type. +This method tells us whether a ((rectangle)) (specified by a position and a size) touches a grid element of the given type. ```{includeCode: true} Level.prototype.touches = function(pos, size, type) { @@ -866,22 +593,13 @@ Level.prototype.touches = function(pos, size, type) { {{index "Math.floor function", "Math.ceil function"}} -The method computes the set of grid squares that the body ((overlap))s -with by using `Math.floor` and `Math.ceil` on its ((coordinates)). -Remember that ((grid)) squares are 1 by 1 units in size. By -((rounding)) the sides of a box up and down, we get the range of -((background)) squares that the box touches. +The method computes the set of grid squares that the body ((overlap))s with by using `Math.floor` and `Math.ceil` on its ((coordinates)). Remember that ((grid)) squares are 1 by 1 units in size. By ((rounding)) the sides of a box up and down, we get the range of ((background)) squares that the box touches. -{{figure {url: "img/game-grid.svg", alt: "Finding collisions on a grid",width: "3cm"}}} +{{figure {url: "img/game-grid.svg", alt: "Diagram showing a grid with a black box overlaid on it. All of the grid squares that are partially covered by the block are marked.", width: "3cm"}}} -We loop over the block of ((grid)) squares found by ((rounding)) the -((coordinates)) and return `true` when a matching square is found. -Squares outside of the level are always treated as `"wall"` to ensure -that the player can't leave the world and that we won't accidentally -try to read outside of the bounds of our `rows` array. +We loop over the block of ((grid)) squares found by ((rounding)) the ((coordinates)) and return `true` when a matching square is found. Squares outside of the level are always treated as `"wall"` to ensure that the player can't leave the world and that we won't accidentally try to read outside of the bounds of our `rows` array. -The state `update` method uses `touches` to figure out whether the player -is touching lava. +The state `update` method uses `touches` to figure out whether the player is touching lava. ```{includeCode: true} State.prototype.update = function(time, keys) { @@ -905,22 +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. +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) { @@ -931,10 +638,7 @@ function overlap(actor1, actor2) { } ``` -If any actor does overlap, its `collide` method gets a chance to -update the state. Touching a lava actor sets the game status to -`"lost"`. Coins vanish when you touch them and set the status to -`"won"` when they are the last coin of the level. +If any actor does overlap, its `collide` method gets a chance to update the state. Touching a lava actor sets the game status to `"lost"`. Coins vanish when you touch them and set the status to `"won"` when they are the last coin of the level. ```{includeCode: true} Lava.prototype.collide = function(state) { @@ -955,9 +659,7 @@ Coin.prototype.collide = function(state) { {{index actor, "Lava class", lava}} -Actor objects' `update` methods take as arguments the time step, the -state object, and a `keys` object. The one for the `Lava` actor type -ignores the `keys` object. +Actor objects' `update` methods take as arguments the time step, the state object, and a `keys` object. The one for the `Lava` actor type ignores the `keys` object. ```{includeCode: true} Lava.prototype.update = function(time, state) { @@ -974,19 +676,11 @@ Lava.prototype.update = function(time, state) { {{index bouncing, multiplication, "Vec class", "collision detection"}} -This `update` method computes a new position by adding the product of the ((time)) step -and the current speed to its old position. If no obstacle blocks that -new position, it moves there. If there is an obstacle, the behavior -depends on the type of the ((lava)) block—dripping lava has a `reset` -position, to which it jumps back when it hits something. Bouncing lava -inverts its speed by multiplying it by -1 so that it starts moving in -the opposite direction. +This `update` method computes a new position by adding the product of the ((time)) step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the ((lava)) block—dripping lava has a `reset` position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by `-1` so that it starts moving in the opposite direction. {{index "Coin class", coin, wave}} -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; @@ -1001,16 +695,11 @@ Coin.prototype.update = function(time) { {{index "Math.sin function", sine, phase}} -The `wobble` property is incremented to track time and then used as an -argument to `Math.sin` to find the new position on the ((wave)). The -coin's current position is then computed from its base position and an -offset based on this wave. +The `wobble` property is incremented to track time and then used as an argument to `Math.sin` to find the new position on the ((wave)). The coin's current position is then computed from its base position and an offset based on this wave. {{index "collision detection", "Player class"}} -That leaves the ((player)) itself. Player motion is handled separately -per ((axis)) because hitting the floor should not prevent horizontal -motion, and hitting a wall should not stop falling or jumping motion. +That leaves the ((player)) itself. Player motion is handled separately per ((axis)) because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion. ```{includeCode: true} const playerXSpeed = 7; @@ -1042,52 +731,31 @@ Player.prototype.update = function(time, state, keys) { {{index [animation, "platform game"], keyboard}} -The horizontal motion is computed based on the state of the left and -right arrow keys. When there's no wall blocking the new position -created by this motion, it is used. Otherwise, the old position is -kept. +The horizontal motion is computed based on the state of the left and right arrow keys. When there's no wall blocking the new position created by this motion, it is used. Otherwise, the old position is kept. {{index acceleration, physics}} -Vertical motion works in a similar way but has to simulate ((jumping)) -and ((gravity)). The player's vertical speed (`ySpeed`) is first -accelerated to account for ((gravity)). +Vertical motion works in a similar way but has to simulate ((jumping)) and ((gravity)). The player's vertical speed (`ySpeed`) is first accelerated to account for ((gravity)). {{index "collision detection", keyboard, jumping}} -We check for walls again. If we don't hit any, the new position is -used. If there _is_ a wall, there are two possible outcomes. When the -up arrow is pressed _and_ we are moving down (meaning the thing we hit -is below us), the speed is set to a relatively large, negative value. -This causes the player to jump. If that is not the case, the player -simply bumped into something, and the speed is set to zero. +We check for walls again. If we don't hit any, the new position is used. If there _is_ a wall, there are two possible outcomes. When the up arrow is pressed _and_ we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero. -The gravity strength, ((jumping)) speed, and pretty much all other -((constant))s in this game have been set by ((trial and error)). I -tested values until I found a combination I liked. +The gravity strength, ((jumping)) speed, and other ((constant))s in the game were determined by simply trying out some numbers and seeing which ones felt right. You can try experimenting with them. ## Tracking keys {{index keyboard}} -For a ((game)) like this, we do not want keys to take effect once per -keypress. Rather, we want their effect (moving the player figure) to -stay active as long as they are held. +For a ((game)) like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to stay active as long as they are held. {{index "preventDefault method"}} -We need to set up a key handler that stores the current state of the -left, right, and up arrow keys. We will also want to call -`preventDefault` for those keys so that they don't end up -((scrolling)) the page. +We need to set up a key handler that stores the current state of the left, right, and up arrow keys. We will also want to call `preventDefault` for those keys so that they don't end up ((scrolling)) the page. {{index "trackKeys function", "key code", "event handling", "addEventListener method"}} -The following function, when given an array of key names, will return -an object that tracks the current position of those keys. It registers -event handlers for `"keydown"` and `"keyup"` events and, when the key -code in the event is present in the set of codes that it is tracking, -updates the object. +The following function, when given an array of key names, will return an object that tracks the current position of those keys. It registers event handlers for `"keydown"` and `"keyup"` events and, when the key code in the event is present in the set of codes that it is tracking, updates the object. ```{includeCode: true} function trackKeys(keys) { @@ -1109,9 +777,7 @@ const arrowKeys = {{index "keydown event", "keyup event"}} -The same handler function is used for both event types. It looks at -the event object's `type` property to determine whether the key state -should be updated to true (`"keydown"`) or false (`"keyup"`). +The same handler function is used for both event types. It looks at the event object's `type` property to determine whether the key state should be updated to true (`"keydown"`) or false (`"keyup"`). {{id runAnimation}} @@ -1119,19 +785,11 @@ should be updated to true (`"keydown"`) or false (`"keyup"`). {{index "requestAnimationFrame function", [animation, "platform game"]}} -The `requestAnimationFrame` function, which we saw in [Chapter -?](dom#animationFrame), provides a good way to animate a game. But its -interface is quite primitive—using it requires us to track the time at -which our function was called the last time around and call -`requestAnimationFrame` again after every frame. +The `requestAnimationFrame` function, which we saw in [Chapter ?](dom#animationFrame), provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call `requestAnimationFrame` again after every frame. {{index "runAnimation function", "callback function", [function, "as value"], [function, "higher-order"], [animation, "platform game"]}} -Let's define a helper function that wraps those boring parts in a -convenient interface and allows us to simply call `runAnimation`, -giving it a function that expects a time difference as an argument and -draws a single frame. When the frame function returns the value -`false`, the animation stops. +Let's define a helper function that wraps all that in a convenient interface and allows us to simply call `runAnimation`, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value `false`, the animation stops. ```{includeCode: true} function runAnimation(frameFunc) { @@ -1150,26 +808,13 @@ function runAnimation(frameFunc) { {{index time, discretization}} -I have set a maximum frame step of 100 milliseconds (one-tenth of a -second). When the browser tab or window with our page is hidden, -`requestAnimationFrame` calls will be suspended until the tab or -window is shown again. In this case, the difference between `lastTime` -and `time` will be the entire time in which the page was hidden. -Advancing the game by that much in a single step would look silly and -might cause weird side effects, such as the player falling through the -floor. +I have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, `requestAnimationFrame` calls will be suspended until the tab or window is shown again. In this case, the difference between `lastTime` and `time` will be the entire time in which the page was hidden. Advancing the game by that much in a single step would look silly and might cause weird side effects, such as the player falling through the floor. -The function also converts the time steps to seconds, which are an -easier quantity to think about than milliseconds. +The function also converts the time steps to seconds, which are an easier quantity to think about than milliseconds. {{index "callback function", "runLevel function", [animation, "platform game"]}} -The `runLevel` function takes a `Level` object and a ((display)) -constructor and returns a promise. It displays the level (in -`document.body`) and lets the user play through it. When the level is -finished (lost or won), `runLevel` waits one more second (to let the -user see what happens) and then clears the display, stops the -animation, and resolves the promise to the game's end status. +The `runLevel` function takes a `Level` object and a ((display)) constructor and returns a promise. It displays the level (in `document.body`) and lets the user play through it. When the level is finished (lost or won), `runLevel` waits one more second (to let the user see what happens) and then clears the display, stops the animation, and resolves the promise to the game's end status. ```{includeCode: true} function runLevel(level, Display) { @@ -1197,10 +842,7 @@ function runLevel(level, Display) { {{index "runGame function"}} -A game is a sequence of ((level))s. Whenever the ((player)) dies, the -current level is restarted. When a level is completed, we move on to -the next level. This can be expressed by the following function, which -takes an array of level plans (strings) and a ((display)) constructor: +A game is a sequence of ((level))s. Whenever the ((player)) dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (strings) and a ((display)) constructor: ```{includeCode: true} async function runGame(plans, Display) { @@ -1215,19 +857,13 @@ async function runGame(plans, Display) { {{index "asynchronous programming", "event handling"}} -Because we made `runLevel` return a promise, `runGame` can be written -using an `async` function, as shown in [Chapter ?](async). It returns -another promise, which resolves when the player finishes the game. +Because we made `runLevel` return a promise, `runGame` can be written using an `async` function, as shown in [Chapter ?](async). It returns another promise, which resolves when the player finishes the game. -{{index game, "GAME_LEVELS data set"}} +{{index game, "GAME_LEVELS dataset"}} -There is a set of ((level)) plans available in the `GAME_LEVELS` -binding in [this chapter's -sandbox](https://eloquentjavascript.net/code#16)[ -([_https://eloquentjavascript.net/code#16_](https://eloquentjavascript.net/code#16))]{if -book}. This page feeds them to `runGame`, starting an actual game. +There is a set of ((level)) plans available in the `GAME_LEVELS` binding in [this chapter's sandbox](https://eloquentjavascript.net/code#16)[ ([_https://eloquentjavascript.net/code#16_](https://eloquentjavascript.net/code#16))]{if book}. This page feeds them to `runGame`, starting an actual game. -```{sandbox: null, focus: yes, lang: "text/html", startCode: true} +```{sandbox: null, focus: yes, lang: html, startCode: true} <link rel="stylesheet" href="css/game.css"> <body> @@ -1239,7 +875,7 @@ book}. This page feeds them to `runGame`, starting an actual game. {{if interactive -See if you can beat those. I had quite a lot of fun building them. +See if you can beat those. I had fun building them. if}} @@ -1249,19 +885,15 @@ if}} {{index "lives (exercise)", game}} -It's traditional for ((platform game))s to have the player start with -a limited number of _lives_ and subtract one life each time they die. -When the player is out of lives, the game restarts from the beginning. +It's traditional for ((platform game))s to have the player start with a limited number of _lives_ and subtract one life each time they die. When the player is out of lives, the game restarts from the beginning. {{index "runGame function"}} -Adjust `runGame` to implement lives. Have the player start with three. -Output the current number of lives (using `console.log`) every time a -level starts. +Adjust `runGame` to implement lives. Have the player start with three. Output the current number of lives (using `console.log`) every time a level starts. {{if interactive -```{lang: "text/html", test: no, focus: yes} +```{lang: html, test: no, focus: yes} <link rel="stylesheet" href="css/game.css"> <body> @@ -1284,37 +916,21 @@ if}} ### Pausing the game -{{index "pausing (exercise)", "escape key", keyboard}} - -Make it possible to pause (suspend) and unpause the game by pressing -the Esc key. - -{{index "runLevel function", "event handling"}} +{{index "pausing (exercise)", "escape key", keyboard, "runLevel function", "event handling"}} -This can be done by changing the `runLevel` function to use another -keyboard event handler and interrupting or resuming the animation -whenever the Esc key is hit. +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"}} -The `runAnimation` interface may not look like it is suitable for this -at first glance, but it is if you rearrange the way `runLevel` calls -it. +The `runAnimation` interface may not look like it is suitable for this at first glance, but it is if you rearrange the way `runLevel` calls it. {{index [binding, global], "trackKeys function"}} -When you have that working, there is something else you could try. The -way we have been registering keyboard event handlers is somewhat -problematic. The `arrowKeys` object is currently a global binding, and -its event handlers are kept around even when no game is running. You -could say they _((leak))_ out of our system. Extend `trackKeys` to -provide a way to unregister its handlers and then change `runLevel` -to register its handlers when it starts and unregister them again when -it is finished. +When you have that working, there's something else you can try. The way we've been registering keyboard event handlers is somewhat problematic. The `arrowKeys` object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they _((leak))_ out of our system. Extend `trackKeys` to provide a way to unregister its handlers, then change `runLevel` to register its handlers when it starts and unregister them again when it is finished. {{if interactive -```{lang: "text/html", focus: yes, test: no} +```{lang: html, focus: yes, test: no} <link rel="stylesheet" href="css/game.css"> <body> @@ -1352,28 +968,17 @@ if}} {{index "pausing (exercise)", [animation, "platform game"]}} -An animation can be interrupted by returning `false` from the -function given to `runAnimation`. It can be continued by calling -`runAnimation` again. +An animation can be interrupted by returning `false` from the function given to `runAnimation`. It can be continued by calling `runAnimation` again. {{index closure}} -So we need to communicate the fact that we are pausing the game to the -function given to `runAnimation`. For that, you can use a binding that -both the event handler and that function have access to. +So we need to communicate the fact that we are pausing the game to the function given to `runAnimation`. For that, you can use a binding that both the event handler and that function have access to. {{index "event handling", "removeEventListener method", [function, "as value"]}} -When finding a way to unregister the handlers registered by -`trackKeys`, remember that the _exact_ same function value that was -passed to `addEventListener` must be passed to `removeEventListener` -to successfully remove a handler. Thus, the `handler` function value -created in `trackKeys` must be available to the code that unregisters -the handlers. +When finding a way to unregister the handlers registered by `trackKeys`, remember that the _exact_ same function value that was passed to `addEventListener` must be passed to `removeEventListener` to successfully remove a handler. Thus, the `handler` function value created in `trackKeys` must be available to the code that unregisters the handlers. -You can add a property to the object returned by `trackKeys`, -containing either that function value or a method that handles the -unregistering directly. +You can add a property to the object returned by `trackKeys`, containing either that function value or a method that handles the unregistering directly. hint}} @@ -1381,24 +986,15 @@ hint}} {{index "monster (exercise)"}} -It is traditional for platform games to have enemies that you can jump -on top of to defeat. This exercise asks you to add such an actor type -to the game. +It is traditional for platform games to have enemies that you can defeat by jumping on top of them. This exercise asks you to add such an actor type to the game. -We'll call it a monster. Monsters move only horizontally. You can make -them move in the direction of the player, bounce back and forth -like horizontal lava, or have any movement pattern you want. The class -doesn't have to handle falling, but it should make sure the monster -doesn't walk through walls. +We'll call this actor a monster. Monsters move only horizontally. You can make them move in the direction of the player, bounce back and forth like horizontal lava, or have any other movement pattern you want. The class doesn't have to handle falling, but it should make sure the monster doesn't walk through walls. -When a monster touches the player, the effect depends on whether the -player is jumping on top of them or not. You can approximate this by -checking whether the player's bottom is near the monster's top. If -this is the case, the monster disappears. If not, the game is lost. +When a monster touches the player, the effect depends on whether the player is jumping on top of them or not. You can approximate this by checking whether the player's bottom is near the monster's top. If this is the case, the monster disappears. If not, the game is lost. {{if interactive -```{test: no, lang: "text/html", focus: yes} +```{test: no, lang: html, focus: yes} <link rel="stylesheet" href="css/game.css"> <style>.monster { background: purple }</style> @@ -1447,20 +1043,12 @@ 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"}} -When handling collision, find the player in `state.actors` and -compare its position to the monster's position. To get the _bottom_ of -the player, you have to add its vertical size to its vertical -position. The creation of an updated state will resemble either -`Coin`'s `collide` method (removing the actor) or `Lava`'s (changing -the status to `"lost"`), depending on the player position. +When handling collision, find the player in `state.actors` and compare its position to the monster's position. To get the _bottom_ of the player, you have to add its vertical size to its vertical position. The creation of an updated state will resemble either `Coin`'s `collide` method (removing the actor) or `Lava`'s (changing the status to `"lost"`), depending on the player position. hint}} diff --git a/17_canvas.md b/17_canvas.md index 090687188..096bac537 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1,4 +1,4 @@ -{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/_stop_keys.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} # Drawing on Canvas @@ -10,56 +10,29 @@ quote}} {{index "Escher, M.C."}} -{{figure {url: "img/chapter_picture_17.jpg", alt: "Picture of a robot arm drawing on paper", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_17.jpg", alt: "Illustration showing an industrial-looking robot arm drawing a city on a piece of paper", chapter: "framed"}}} {{index CSS, "transform (CSS)", [DOM, graphics]}} -Browsers give us several ways to display ((graphics)). The simplest -way is to use styles to position and color regular DOM elements. -This can get you quite far, as the game in the [previous -chapter](game) showed. By adding partially transparent background -((image))s to the nodes, we can make them look exactly the way we -want. It is even possible to rotate or skew nodes with the `transform` -style. +Browsers give us several ways to display ((graphics)). The simplest way is to use styles to position and color regular DOM elements. This can get us quite far, as the game in the [previous chapter](game) showed. By adding partially transparent background ((image))s to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the `transform` style. -But we'd be using the DOM for something that it wasn't originally -designed for. Some tasks, such as drawing a ((line)) between -arbitrary points, are extremely awkward to do with regular -HTML elements. +But we'd be using the DOM for something that it wasn't originally designed for. Some tasks, such as drawing a ((line)) between arbitrary points, are extremely awkward to do with regular HTML elements. {{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]}} -The second alternative is called a _((canvas))_. A canvas is a single -DOM element that encapsulates a ((picture)). It provides a -programming interface for drawing ((shape))s onto the space taken -up by the node. The main difference between a canvas and an SVG -picture is that in SVG the original description of the shapes is -preserved so that they can be moved or resized at any time. A canvas, -on the other hand, converts the shapes to ((pixel))s (colored dots on -a raster) as soon as they are drawn and does not remember what these -pixels represent. The only way to move a shape on a canvas is to clear -the canvas (or the part of the canvas around the shape) and redraw it -with the shape in a new position. +The second alternative is called a _((canvas))_. A canvas is a single DOM element that encapsulates a ((picture)). It provides a programming interface for drawing ((shape))s onto the space taken up by the node. The main difference between a canvas and an SVG picture is that in SVG the original description of the shapes is preserved so that they can be moved or resized at any time. A canvas, on the other hand, converts the shapes to ((pixel))s (colored dots on a raster) as soon as they are drawn and does not remember what these pixels represent. The only way to move a shape on a canvas is to clear the canvas (or the part of the canvas around the shape) and redraw it with the shape in a new position. ## SVG -This book will not go into ((SVG)) in detail, but I will briefly -explain how it works. At the [end of the -chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs -that you must consider when deciding which ((drawing)) mechanism is -appropriate for a given application. +This book won't go into ((SVG)) in detail, but I'll briefly explain how it works. At the [end of the chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs that you must consider when deciding which ((drawing)) mechanism is appropriate for a given application. This is an HTML document with a simple SVG ((picture)) in it: -```{lang: "text/html", sandbox: "svg"} +```{lang: html, sandbox: "svg"} <p>Normal HTML here.</p> <svg xmlns="http://www.w3.org/2000/svg"> <circle r="50" cx="50" cy="50" fill="red"/> @@ -70,26 +43,19 @@ This is an HTML document with a simple SVG ((picture)) in it: {{index "circle (SVG tag)", "rect (SVG tag)", "XML namespace", XML, "xmlns attribute"}} -The `xmlns` attribute changes an element (and its children) to a -different _XML namespace_. This namespace, identified by a ((URL)), -specifies the dialect that we are currently speaking. The `<circle>` -and `<rect>` tags, which do not exist in HTML, do have a meaning in -SVG—they draw shapes using the style and position specified by their -attributes. +The `xmlns` attribute changes an element (and its children) to a different _XML namespace_. This namespace, identified by a ((URL)), specifies the dialect that we are currently speaking. The `<circle>` and `<rect>` tags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the style and position specified by their attributes. {{if book The document is displayed like this: -{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} +{{figure {url: "img/svg-demo.png", alt: "Screenshot showing an SVG image embedded in an HTML document", width: "4.5cm"}}} if}} {{index [DOM, graphics]}} -These tags create DOM elements, just like HTML tags, that -scripts can interact with. For example, this changes the `<circle>` -element to be ((color))ed cyan instead: +These tags create DOM elements, just like HTML tags, that scripts can interact with. For example, this changes the `<circle>` element to be ((color))ed cyan instead: ```{sandbox: "svg"} let circle = document.querySelector("circle"); @@ -100,36 +66,23 @@ circle.setAttribute("fill", "cyan"); {{index [canvas, size], "canvas (HTML tag)"}} -Canvas ((graphics)) can be drawn onto a `<canvas>` element. You can -give such an element `width` and `height` attributes to determine its -size in ((pixel))s. +Canvas ((graphics)) can be drawn onto a `<canvas>` element. You can give such an element `width` and `height` attributes to determine its size in ((pixel))s. -A new canvas is empty, meaning it is entirely ((transparent)) and thus -shows up as empty space in the document. +A new canvas is empty, meaning it is entirely ((transparent)) and thus shows up as empty space in the document. {{index "2d (canvas context)", "webgl (canvas context)", OpenGL, [canvas, context], dimensions, [interface, canvas]}} -The `<canvas>` tag is intended to allow different styles of -((drawing)). To get access to an actual drawing interface, we -first need to create a _((context))_, an object whose methods provide -the drawing interface. There are currently two widely supported -drawing styles: `"2d"` for two-dimensional graphics and `"webgl"` for -three-dimensional graphics through the OpenGL interface. +The `<canvas>` tag is intended to allow different styles of ((drawing)). To get access to an actual drawing interface, we first need to create a _((context))_, an object whose methods provide the drawing interface. There are currently three widely supported drawing styles: `"2d"` for two-dimensional graphics, `"webgl"` for three-dimensional graphics through the OpenGL interface, and `"webgpu"`, a more modern and flexible alternative to WebGL. {{index rendering, graphics, efficiency}} -This book won't discuss WebGL—we'll stick to two dimensions. But if -you are interested in three-dimensional graphics, I do encourage you -to look into WebGL. It provides a direct interface to graphics -hardware and allows you to render even complicated scenes efficiently, -using JavaScript. +This book won't discuss WebGL or WebGPU—we'll stick to two dimensions. But if you are interested in three-dimensional graphics, I do encourage you to look into WebGPU. It provides a direct interface to graphics hardware and allows you to render even complicated scenes efficiently, using JavaScript. {{index "getContext method", [canvas, context]}} -You create a ((context)) with the `getContext` method on the -`<canvas>` DOM element. +You create a ((context)) with the `getContext` method on the `<canvas>` DOM element. -```{lang: "text/html"} +```{lang: html} <p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> @@ -141,22 +94,17 @@ You create a ((context)) with the `getContext` method on the </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 -{{figure {url: "img/canvas_fill.png", alt: "A canvas with a rectangle",width: "2.5cm"}}} +{{figure {url: "img/canvas_fill.png", alt: "Screenshot of a canvas with a rectangle on it", width: "2.5cm"}}} if}} {{index SVG, coordinates}} -Just like in HTML (and SVG), the coordinate system that the canvas -uses puts (0,0) at the top-left corner, and the positive y-((axis)) -goes down from there. So (10,10) is 10 pixels below and to the right -of the top-left corner. +Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0, 0) at the 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}} @@ -164,38 +112,25 @@ of the top-left corner. {{index filling, stroking, drawing, SVG}} -In the ((canvas)) interface, a shape can be _filled_, meaning its area -is given a certain color or pattern, or it can be _stroked_, which -means a ((line)) is drawn along its edge. The same terminology is used -by SVG. +In the ((canvas)) interface, a shape can be _filled_, meaning its area is given a certain color or pattern, or it can be _stroked_, which means a ((line)) is drawn along its edge. SVG uses the same terminology. {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and -y-((coordinates)) of the rectangle's top-left corner, then its width, -and then its height. A similar method, `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"]}} -Neither method takes any further parameters. The color of the fill, -thickness of the stroke, and so on, are not determined by an argument -to the method (as you might reasonably expect) but rather by -properties of the context object. +Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method, as you might reasonably expect, but rather by properties of the context object. {{index filling, "fillStyle property"}} -The `fillStyle` property controls the way shapes are filled. It can be -set to a string that specifies a ((color)), using the color notation -used by ((CSS)). +The `fillStyle` property controls the way shapes are filled. It can be set to a string that specifies a ((color)), using the color notation used by ((CSS)). {{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} -The `strokeStyle` property works similarly but determines the color -used for a stroked line. The width of that line is determined by the -`lineWidth` property, which may contain any positive number. +The `strokeStyle` property works similarly but determines the color used for a stroked line. The width of that line is determined by the `lineWidth` property, which may contain any positive number. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -208,30 +143,23 @@ used for a stroked line. The width of that line is determined by the {{if book -This code draws two blue squares, using a thicker line for the second -one. +This code draws two blue squares, using a thicker line for the second one. -{{figure {url: "img/canvas_stroke.png", alt: "Two stroked squares",width: "5cm"}}} +{{figure {url: "img/canvas_stroke.png", alt: "Screenshot showing two outlined squares", width: "5cm"}}} if}} {{index "default value", [canvas, size]}} -When no `width` or `height` attribute is specified, as in the example, -a canvas element gets a default width of 300 pixels and height of 150 -pixels. +When no `width` or `height` attribute is specified, as in the example, a canvas element gets a default width of 300 pixels and height of 150 pixels. ## Paths {{index [path, canvas], [interface, design], [canvas, path]}} -A path is a sequence of ((line))s. The 2D canvas interface takes a -peculiar approach to describing such a path. It is done entirely -through ((side effect))s. Paths are not values that can be stored and -passed around. Instead, if you want to do something with a path, you -make a sequence of method calls to describe its shape. +A path is a sequence of ((line))s. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through ((side effect))s. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -246,31 +174,21 @@ make a sequence of method calls to describe its shape. {{index canvas, "stroke method", "lineTo method", "moveTo method", shape}} -This example creates a path with a number of horizontal ((line)) -segments and then strokes it using the `stroke` method. Each segment -created with `lineTo` starts at the path's _current_ position. That -position is usually the end of the last segment, unless `moveTo` was -called. In that case, the next segment would start at the position -passed to `moveTo`. +This example creates a path with a number of horizontal ((line)) segments and then strokes it using the `stroke` method. Each segment created with `lineTo` starts at the path's _current_ position. That position is usually the end of the last segment, unless `moveTo` was called. In that case, the next segment would start at the position passed to `moveTo`. {{if book The path described by the previous program looks like this: -{{figure {url: "img/canvas_path.png", alt: "Stroking a number of lines",width: "2.1cm"}}} +{{figure {url: "img/canvas_path.png", alt: "Screenshot showing a number of vertical lines", width: "2.1cm"}}} if}} {{index [path, canvas], filling, [path, closing], "fill method"}} -When filling a path (using the `fill` method), each ((shape)) is -filled separately. A path can contain multiple shapes—each `moveTo` -motion starts a new one. But the path needs to be _closed_ (meaning -its start and end are in the same position) before it can be filled. -If the path is not already closed, a line is added from its end to its -start, and the shape enclosed by the completed path is filled. +When filling a path (using the `fill` method), each ((shape)) is filled separately. A path can contain multiple shapes—each `moveTo` motion starts a new one. But the path needs to be _closed_ (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -282,47 +200,35 @@ start, and the shape enclosed by the completed path is filled. </script> ``` -This example draws a filled triangle. Note that only two of the -triangle's sides are explicitly drawn. The third, from the -bottom-right corner back to the top, is implied and wouldn't be there -when you stroke the path. +This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the lower-right corner back to the top, is implied and wouldn't be there if you stroked the path. {{if book -{{figure {url: "img/canvas_triangle.png", alt: "Filling a path",width: "2.2cm"}}} +{{figure {url: "img/canvas_triangle.png", alt: "Screenshot showing a filled path", width: "2.2cm"}}} if}} {{index "stroke method", "closePath method", [path, closing], canvas}} -You could also use the `closePath` method to explicitly close a path -by adding an actual ((line)) segment back to the path's start. This -segment _is_ drawn when stroking the path. +You could also use the `closePath` method to explicitly close a path by adding an actual ((line)) segment back to the path's start. This segment _is_ drawn when stroking the path. ## Curves {{index [path, canvas], canvas, drawing}} -A path may also contain ((curve))d ((line))s. These are unfortunately -a bit more involved to draw. +A path may also contain ((curve))d ((line))s. These are unfortunately a bit more involved to draw. {{index "quadraticCurveTo method"}} -The `quadraticCurveTo` method draws a curve to a given point. To -determine the curvature of the line, the method is given a ((control -point)) as well as a destination point. Imagine this control point as -_attracting_ the line, giving it its curve. The line won't go through -the control point, but its direction at the start and end points will -be such that a straight line in that direction would point toward the -control point. The following example illustrates this: +The `quadraticCurveTo` method draws a curve to a given point. To determine the curvature of the line, the method is given a ((control point)) as well as a destination point. Imagine this control point as _attracting_ the line, giving it its curve. The line won't go through the control point, but its direction at the start and end points will be such that a straight line in that direction would point toward the control point. The following example illustrates this: -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> 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(); @@ -334,34 +240,25 @@ control point. The following example illustrates this: It produces a path that looks like this: -{{figure {url: "img/canvas_quadraticcurve.png", alt: "A quadratic curve",width: "2.3cm"}}} +{{figure {url: "img/canvas_quadraticcurve.png", alt: "Screenshot of a quadratic curve", width: "2.3cm"}}} 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 one has two—one for each of the -((line))'s endpoints. Here is a similar sketch to illustrate the -behavior of such a curve: +The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this method has two—one for each of the ((line))'s end points. Here is a similar sketch to illustrate the behavior of such a curve: -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> 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); @@ -370,47 +267,34 @@ behavior of such a curve: </script> ``` -The two control points specify the direction at both ends of the -curve. The farther they are away from their corresponding point, the -more the curve will "bulge" in that direction. +The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more the curve will "bulge" in that direction. {{if book -{{figure {url: "img/canvas_beziercurve.png", alt: "A bezier curve",width: "2.2cm"}}} +{{figure {url: "img/canvas_beziercurve.png", alt: "Screenshot of a bezier curve", width: "2.2cm"}}} if}} {{index "trial and error"}} -Such ((curve))s can be hard to work with—it's not always clear how to -find the ((control point))s that provide the ((shape)) you are looking -for. Sometimes you can compute them, and sometimes you'll just have to -find a suitable value by trial and error. +Such ((curve))s can be hard to work with—it's not always clear how to find the ((control point))s that provide the ((shape)) you are looking for. Sometimes you can compute them, and sometimes you'll just have to find a suitable value by trial and error. {{index "arc method", arc}} -The `arc` method is a way to draw a line that curves along the edge of -a circle. It takes a pair of ((coordinates)) for the arc's center, a -radius, and then a start angle and end angle. +The `arc` method is a way to draw a line that curves along the edge of a circle. It takes a pair of ((coordinates)) for the arc's center, a radius, and then a start angle and end angle. {{index pi, "Math.PI constant"}} -Those last two parameters make it possible to draw only part of the -circle. The ((angle))s are measured in ((radian))s, not ((degree))s. -This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, -which is about 6.28. The angle starts counting at the point to the -right of the circle's center and goes clockwise from there. You can -use a start of 0 and an end bigger than 2π (say, 7) to draw a full -circle. +Those last two parameters make it possible to draw only part of the circle. The ((angle))s are measured in ((radian))s, not ((degree))s. This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, which is about 6.28. The angle starts counting at the point to the right of the circle's center and goes clockwise from there. You can use a start of 0 and an end bigger than 2π (say, 7) to draw a full circle. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <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> @@ -418,30 +302,25 @@ circle. {{index "moveTo method", "arc method", [path, " canvas"]}} -The resulting picture contains a ((line)) from the right of the full -circle (first call to `arc`) to the right of the quarter-((circle)) -(second call). Like other path-drawing methods, a line drawn with -`arc` is connected to the previous path segment. You can call `moveTo` -or start a new path to avoid this. +The resulting picture contains a ((line)) from the right of the full circle (first call to `arc`) to the right of the quarter-((circle)) (second call). {{if book -{{figure {url: "img/canvas_circle.png", alt: "Drawing a circle",width: "4.9cm"}}} +{{figure {url: "img/canvas_circle.png", alt: "Screenshot of a circle", width: "4.9cm"}}} if}} +Like other path-drawing methods, a line drawn with `arc` is connected to the previous path segment.You can call `moveTo` or start a new path to avoid this. + {{id pie_chart}} ## Drawing a pie chart {{index "pie chart example"}} -Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your -first assignment is to draw a pie chart of its customer satisfaction -((survey)) results. +Imagine 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. +The `results` binding contains an array of objects that represent the survey responses. ```{sandbox: "pie", includeCode: true} const results = [ @@ -454,14 +333,9 @@ const results = [ {{index "pie chart example"}} -To draw a pie chart, we draw a number of pie slices, each made up of -an ((arc)) and a pair of ((line))s to the center of that arc. We can -compute the ((angle)) taken up by each arc by dividing a full circle -(2π) by the total number of responses and then multiplying that number -(the angle per response) by the number of people who picked a given -choice. +To draw a pie chart, we draw a number of pie slices, each made up of an ((arc)) and a pair of ((line))s to the center of that arc. We can compute the ((angle)) taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice. -```{lang: "text/html", sandbox: "pie"} +```{lang: html, sandbox: "pie"} <canvas width="200" height="200"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -488,23 +362,19 @@ choice. This draws the following chart: -{{figure {url: "img/canvas_pie_chart.png", alt: "A pie chart",width: "5cm"}}} +{{figure {url: "img/canvas_pie_chart.png", alt: "Screenshot showing a pie chart", width: "5cm"}}} if}} -But a chart that doesn't tell us what the slices mean isn't very -helpful. We need a way to draw text to the ((canvas)). +But a chart that doesn't tell us what the slices mean isn't very helpful. We need a way to draw text to the ((canvas)). ## Text {{index stroking, filling, "fillStyle property", "fillText method", "strokeText method"}} -A 2D canvas drawing context provides the methods `fillText` and -`strokeText`. The latter can be useful for outlining letters, but -usually `fillText` is what you need. It will fill the outline of the -given ((text)) with the current `fillStyle`. +A 2D canvas drawing context provides the methods `fillText` and `strokeText`. The latter can be useful for outlining letters, but usually `fillText` is what you need. It will fill the outline of the given ((text)) with the current `fillStyle`. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -514,50 +384,27 @@ given ((text)) with the current `fillStyle`. </script> ``` -You can specify the size, style, and ((font)) of the text with the -`font` property. This example just gives a font size and family name. -It is also possible to add `italic` or `bold` to the start of the -string to select a style. +You can specify the size, style, and ((font)) of the text with the `font` property. This example just gives a font size and family name. It is also possible to add `italic` or `bold` to the start of the string to select a style. {{index "fillText method", "strokeText method", "textAlign property", "textBaseline property"}} -The last two arguments to `fillText` and `strokeText` provide the -position at which the font is drawn. By default, they indicate the -position of the start of the text's alphabetic baseline, which is the -line that letters "stand" on, not counting hanging parts in letters -such as _j_ or _p_. You can change the horizontal position by setting the -`textAlign` property to `"end"` or `"center"` and the vertical -position by setting `textBaseline` to `"top"`, `"middle"`, or -`"bottom"`. +The last two arguments to `fillText` and `strokeText` provide the position at which the font is drawn. By default, they indicate the position of the start of the text's alphabetic baseline, which is the line that letters "stand" on, not counting hanging parts in letters such as _j_ or _p_. You can change the horizontal position by setting the `textAlign` property to `"end"` or `"center"` and the vertical position by setting `textBaseline` to `"top"`, `"middle"`, or `"bottom"`. {{index "pie chart example"}} -We'll come back to our pie chart, and the problem of ((label))ing the -slices, in the [exercises](canvas#exercise_pie_chart) at the end of -the chapter. +We'll come back to our pie chart, and the problem of ((label))ing the slices, in the [exercises](canvas#exercise_pie_chart) at the end of the chapter. ## Images {{index "vector graphics", "bitmap graphics"}} -In computer ((graphics)), a distinction is often made between _vector_ -graphics and _bitmap_ graphics. The first is what we have been doing -so far in this chapter—specifying a picture by giving a logical -description of ((shape))s. Bitmap graphics, on the other hand, don't -specify actual shapes but rather work with ((pixel)) data (rasters of -colored dots). +In computer ((graphics)), a distinction is often made between _vector_ graphics and _bitmap_ graphics. The first is what we have been doing so far in this chapter—specifying a picture by giving a logical description of ((shape))s. Bitmap graphics, on the other hand, don't specify actual shapes but rather work with ((pixel)) data (rasters of colored dots). {{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} -The `drawImage` method allows us to draw ((pixel)) data onto a -((canvas)). This pixel data can originate from an `<img>` element or -from another canvas. The following example creates a detached `<img>` -element and loads an image file into it. But it cannot immediately -start drawing from this picture because the browser may not have -loaded it yet. To deal with this, we register a `"load"` event handler -and do the drawing after the image has loaded. +The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This pixel data can originate from an `<img>` element or from another canvas. The following example creates a detached `<img>` element and loads an image file into it. But the method cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a `"load"` event handler and do the drawing after the image has loaded. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -573,44 +420,29 @@ and do the drawing after the image has loaded. {{index "drawImage method", scaling}} -By default, `drawImage` will draw the image at its original size. You -can also give it two additional arguments to set a different width -and height. +By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to 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. +When `drawImage` is given _nine_ arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied. {{index "player", "pixel art"}} -This can be used to pack multiple _((sprite))s_ (image elements) into -a single image file and then draw only the part you need. For example, -we have this picture containing a game character in multiple -((pose))s: +This can be used to pack multiple _((sprite))s_ (image elements) into a single image file and then draw only the part you need. For example, this picture contains a game character in multiple ((pose))s: -{{figure {url: "img/player_big.png", alt: "Various poses of a game character",width: "6cm"}}} +{{figure {url: "img/player_big.png", alt: "Pixel art showing a computer game character in 10 different poses. The first 8 form its running animation cycle, the 9th has the character standing still, and the 10th shows him jumping.", width: "6cm"}}} {{index [animation, "platform game"]}} -By alternating which pose we draw, we can show an animation that -looks like a walking character. +By alternating which pose we draw, we can show an animation that looks like a walking character. {{index "fillRect method", "clearRect method", clearing}} -To animate a ((picture)) on a ((canvas)), the `clearRect` method is -useful. It resembles `fillRect`, but instead of coloring the -rectangle, it makes it ((transparent)), removing the previously drawn -pixels. +To animate a ((picture)) on a ((canvas)), the `clearRect` method is useful. It resembles `fillRect`, but instead of coloring the rectangle, it makes it ((transparent)), removing the previously drawn pixels. {{index "setInterval function", "img (HTML tag)"}} -We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide -and 30 pixels high. The following code loads the image and then sets -up an interval (repeated timer) to draw the next ((frame)): +We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide and 30 pixels high. The following code loads the image and then sets up an interval (repeated timer) to draw the next ((frame)): -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -634,11 +466,7 @@ up an interval (repeated timer) to draw the next ((frame)): {{index "remainder operator", "% operator", [animation, "platform game"]}} -The `cycle` binding tracks our position in the animation. For each -((frame)), it is incremented and then clipped back to the 0 to 7 range -by using the remainder operator. This binding is then used to compute -the x-coordinate that the sprite for the current pose has in the -picture. +The `cycle` binding tracks our position in the animation. For each ((frame)), it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the picture. ## Transformation @@ -646,17 +474,13 @@ picture. {{indexsee flipping, mirroring}} -But what if we want our character to walk to the left instead of to -the right? We could draw another set of sprites, of course. But we can -also instruct the ((canvas)) to draw the picture the other way round. +What if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we could also instruct the ((canvas)) to draw the picture the other way round. {{index "scale method", scaling}} -Calling the `scale` method will cause anything drawn after it to be -scaled. This method takes two parameters, one to set a horizontal -scale and one to set a vertical scale. +Calling the `scale` method will cause anything drawn after it to be scaled. This method takes two parameters, one to set a horizontal scale and one to set a vertical scale. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -670,63 +494,37 @@ scale and one to set a vertical scale. {{if book -Because of the call to `scale`, the circle is drawn three times as wide -and half as high. +Because of the call to `scale`, the circle is drawn three times as wide and half as high. -{{figure {url: "img/canvas_scale.png", alt: "A scaled circle",width: "6.6cm"}}} +{{figure {url: "img/canvas_scale.png", alt: "Screenshot of a scaled circle", width: "6.6cm"}}} if}} {{index mirroring}} -Scaling will cause everything about the drawn image, including the -((line width)), to be stretched out or squeezed together as specified. -Scaling by a negative amount will flip the picture around. The -flipping happens around point (0,0), which means it will also flip the -direction of the coordinate system. When a horizontal scaling of -1 is -applied, a shape drawn at x position 100 will end up at what used to -be position -100. +Scaling will cause everything about the drawn image, including the ((line width)), to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0, 0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at _x_ position 100 will end up at what used to be position -100. {{index "drawImage method"}} -So to turn a picture around, we can't simply add `cx.scale(-1, 1)` -before the call to `drawImage` because that would move our picture -outside of the ((canvas)), where it won't be visible. You could adjust -the ((coordinates)) given to `drawImage` to compensate for this by -drawing the image at x position -50 instead of 0. Another solution, -which doesn't require the code that does the drawing to know about the -scale change, is to adjust the ((axis)) around which the scaling -happens. +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}} -There are several other methods besides `scale` that influence the -coordinate system for a ((canvas)). You can rotate subsequently drawn -shapes with the `rotate` method and move them with the `translate` -method. The interesting—and confusing—thing is that these -transformations _stack_, meaning that each one happens relative to the -previous transformations. +There are several other methods besides `scale` that influence the coordinate system for a ((canvas)). You can rotate subsequently drawn shapes with the `rotate` method and move them with the `translate` method. The interesting—and confusing—thing is that these transformations _stack_, meaning that each one happens relative to the previous transformations. {{index "rotate method", "translate method"}} -So if we translate by 10 horizontal pixels twice, everything will be -drawn 20 pixels to the right. If we first move the center of the -coordinate system to (50,50) and then rotate by 20 ((degree))s (about -0.1π ((radian))s), that rotation will happen _around_ point (50,50). +If we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50, 50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50, 50). -{{figure {url: "img/transform.svg", alt: "Stacking transformations",width: "9cm"}}} +{{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) { @@ -738,28 +536,17 @@ function flipHorizontally(context, around) { {{index "flipHorizontally method"}} -We move the y-((axis)) to where we want our ((mirror)) to be, apply -the mirroring, and finally move the y-axis back to its proper place in -the mirrored universe. The following picture explains why this works: +We move the y-((axis)) to where we want our ((mirror)) to be, apply the mirroring, and finally move the y-axis back to its proper place in the mirrored universe. The following picture explains why this works: -{{figure {url: "img/mirror.svg", alt: "Mirroring around a vertical line",width: "8cm"}}} +{{figure {url: "img/mirror.svg", alt: "Diagram showing the effect of translating and mirroring a triangle", width: "8cm"}}} {{index "translate method", "scale method", transformation, canvas}} -This shows the coordinate systems before and after mirroring across -the central line. The triangles are numbered to illustrate each step. -If we draw a triangle at a positive x position, it would, by default, -be in the place where triangle 1 is. A call to `flipHorizontally` -first does a translation to the right, which gets us to triangle 2. It -then scales, flipping the triangle over to position 3. This is not -where it should be, if it were mirrored in the given line. The second -`translate` call fixes this—it "cancels" the initial translation and -makes triangle 4 appear exactly where it should. +This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive _x_ position, it would, by default, be in the place where triangle 1 is. A call to `flipHorizontally` first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second `translate` call fixes this—it "cancels" the initial translation and makes triangle 4 appear exactly where it should. -We can now draw a mirrored character at position (100,0) by flipping -the world around the character's vertical center. +We can now draw a mirrored character at position (100, 0) by flipping the world around the character's vertical center. -```{lang: "text/html"} +```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -778,43 +565,21 @@ the world around the character's vertical center. {{index "side effect", canvas, transformation}} -Transformations stick around. Everything else we draw after -((drawing)) that mirrored character would also be mirrored. That might -be inconvenient. +Transformations stick around. Everything else we draw after ((drawing)) that mirrored character would also be mirrored. That might be inconvenient. -It is possible to save the current transformation, do some drawing and -transforming, and then restore the old transformation. This is usually -the proper thing to do for a function that needs to temporarily -transform the coordinate system. First, we save whatever -transformation the code that called the function was using. Then the -function does its thing, adding more transformations on top of the -current transformation. Finally, we revert to the -transformation we started with. +It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with. {{index "save method", "restore method", [state, "of canvas"]}} -The `save` and `restore` methods on the 2D ((canvas)) context do this -((transformation)) management. They conceptually keep a stack of -transformation states. When you call `save`, the current state is -pushed onto the stack, and when you call `restore`, the state on top -of the stack is taken off and used as the context's current -transformation. You can also call `resetTransform` to fully reset the -transformation. +The `save` and `restore` methods on the 2D ((canvas)) context do this ((transformation)) management. They conceptually keep a stack of transformation states. When you call `save`, the current state is pushed onto the stack, and when you call `restore`, the state on top of the stack is taken off and used as the context's current transformation. You can also call `resetTransform` to fully reset the transformation. {{index "branching recursion", "fractal example", recursion}} -The `branch` function in the following example illustrates what you -can do with a function that changes the transformation and then calls -a function (in this case itself), which continues drawing with -the given transformation. +The `branch` function in the following example illustrates what you can do with a function that changes the transformation and then calls a function (in this case itself), which continues drawing with the given transformation. -This function draws a treelike shape by drawing a line, moving the -center of the coordinate system to the end of the line, and calling -itself twice—first rotated to the left and then rotated to the right. -Every call reduces the length of the branch drawn, and the recursion -stops when the length drops below 8. +This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the end of the line, and calling itself twice—first rotated to the left and then rotated to the right. Every call reduces the length of the branch drawn, and the recursion stops when the length drops below 8. -```{lang: "text/html"} +```{lang: html} <canvas width="600" height="300"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -838,18 +603,13 @@ stops when the length drops below 8. The result is a simple fractal. -{{figure {url: "img/canvas_tree.png", alt: "A recursive picture",width: "5cm"}}} +{{figure {url: "img/canvas_tree.png", alt: "Screenshot of a fractal", width: "5cm"}}} 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}} @@ -857,26 +617,15 @@ definitely not a tree. {{index "drawImage method"}} -We now know enough about ((canvas)) drawing to start working on a -((canvas))-based ((display)) system for the ((game)) from the -[previous chapter](game). The new display will no longer be showing -just colored boxes. Instead, we'll use `drawImage` to draw pictures -that represent the game's elements. +We now know enough about ((canvas)) drawing to start working on a ((canvas))-based ((display)) system for the ((game)) from the [previous chapter](game). The new display will no longer be showing just colored boxes. Instead, we'll use `drawImage` to draw pictures that represent the game's elements. {{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} -We define another display object type called `CanvasDisplay`, -supporting the same interface as `DOMDisplay` from [Chapter -?](game#domdisplay), namely, the methods `syncState` and `clear`. +We define another display object type called `CanvasDisplay`, supporting the same interface as `DOMDisplay` from [Chapter ?](game#domdisplay)—namely, the methods `syncState` and `clear`. {{index [state, "in objects"]}} -This object keeps a little more information than `DOMDisplay`. Rather -than using the scroll position of its DOM element, it tracks its own -((viewport)), which tells us what part of the level we are currently -looking at. Finally, it keeps a `flipPlayer` property so that even -when the player is standing still, it keeps facing the direction it -last moved in. +This object keeps a little more information than `DOMDisplay`. Rather than using the scroll position of its DOM element, it tracks its own ((viewport)), which tells us which part of the level we are currently looking at. Finally, it keeps a `flipPlayer` property so that even when the player is standing still, it keeps facing the direction in which it last moved. ```{sandbox: "game", includeCode: true} class CanvasDisplay { @@ -903,8 +652,7 @@ class CanvasDisplay { } ``` -The `syncState` method first computes a new viewport and then draws -the game scene at the appropriate position. +The `syncState` method first computes a new viewport and then draws the game scene at the appropriate position. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.syncState = function(state) { @@ -917,19 +665,11 @@ CanvasDisplay.prototype.syncState = function(state) { {{index scrolling, clearing}} -Contrary to `DOMDisplay`, this display style _does_ have to redraw the -background on every update. Because shapes on a canvas are just -((pixel))s, after we draw them there is no good way to move them (or -remove them). The only way to update the canvas display is to clear it -and redraw the scene. We may also have scrolled, which requires the -background to be in a different position. +Contrary to `DOMDisplay`, this display style _does_ have to redraw the background on every update. Because shapes on a canvas are just ((pixel))s, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position. {{index "CanvasDisplay class"}} -The `updateViewport` method is similar to `DOMDisplay`'s -`scrollPlayerIntoView` method. It checks whether the player is too -close to the edge of the screen and moves the ((viewport)) when this -is the case. +The `updateViewport` method is similar to `DOMDisplay`'s `scrollPlayerIntoView` method. It checks whether the player is too close to the edge of the screen and moves the ((viewport)) when this is the case. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.updateViewport = function(state) { @@ -954,14 +694,9 @@ CanvasDisplay.prototype.updateViewport = function(state) { {{index boundary, "Math.max function", "Math.min function", clipping}} -The calls to `Math.max` and `Math.min` ensure that the viewport does -not end up showing space outside of the level. `Math.max(x, 0)` makes -sure the resulting number is not less than zero. `Math.min` -similarly guarantees that a value stays below a given bound. +The calls to `Math.max` and `Math.min` ensure that the viewport does not end up showing space outside of the level. `Math.max(x, 0)` makes sure the resulting number is not less than zero. `Math.min` similarly guarantees that a value stays below a given bound. -When ((clearing)) the display, we'll use a slightly different -((color)) depending on whether the game is won (brighter) or lost -(darker). +When ((clearing)) the display, we'll use a slightly different ((color)) depending on whether the game is won (brighter) or lost (darker). ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.clearDisplay = function(status) { @@ -979,9 +714,7 @@ CanvasDisplay.prototype.clearDisplay = function(status) { {{index "Math.floor function", "Math.ceil function", rounding}} -To draw the background, we run through the tiles that are visible in -the current viewport, using the same trick used in the `touches` -method from the [previous chapter](game#touches). +To draw the background, we run through the tiles that are visible in the current viewport, using the same trick used in the `touches` method from the [previous chapter](game#touches). ```{sandbox: "game", includeCode: true} let otherSprites = document.createElement("img"); @@ -1011,46 +744,25 @@ CanvasDisplay.prototype.drawBackground = function(level) { {{index "drawImage method", sprite, tile}} -Tiles that are not empty are drawn with `drawImage`. The -`otherSprites` image contains the pictures used for elements other -than the player. It contains, from left to right, the wall tile, the -lava tile, and the sprite for a coin. +Tiles that are not empty are drawn with `drawImage`. The `otherSprites` image contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin. -{{figure {url: "img/sprites_big.png", alt: "Sprites for our game",width: "1.4cm"}}} +{{figure {url: "img/sprites_big.png", alt: "Pixel art showing three sprites: a piece of wall, made out of small white stones, a square of orange lava, and a round coin.", width: "1.4cm"}}} {{index scaling}} -Background tiles are 20 by 20 pixels since we will use the same scale -that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 -(the value of the `scale` binding), and the offset for walls is 0. +Background tiles are 20 by 20 pixels, since we'll use the same scale as in `DOMDisplay`. Thus, the offset for lava tiles is 20 (the value of the `scale` binding), and the offset for walls is 0. {{index drawing, "load event", "drawImage method"}} -We don't bother waiting for the sprite image to load. Calling -`drawImage` with an image that hasn't been loaded yet will simply do -nothing. Thus, we might fail to draw the game properly for the first -few ((frame))s, while the image is still loading, but that is not a -serious problem. Since we keep updating the screen, the correct scene -will appear as soon as the loading finishes. +We don't bother waiting for the sprite image to load. Calling `drawImage` with an image that hasn't been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few ((frame))s while the image is still loading, but that isn't a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes. {{index "player", [animation, "platform game"], drawing}} -The ((walking)) character shown earlier will be used to represent the -player. The code that draws it needs to pick the right ((sprite)) and -direction based on the player's current motion. The first eight -sprites contain a walking animation. When the player is moving along a -floor, we cycle through them based on the current time. We want to -switch frames every 60 milliseconds, so the ((time)) is divided by 60 -first. When the player is standing still, we draw the ninth sprite. -During jumps, which are recognized by the fact that the vertical speed -is not zero, we use the tenth, rightmost sprite. +The ((walking)) character shown earlier will be used to represent the player. The code that draws it needs to pick the right ((sprite)) and direction based on the player's current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current time. We want to switch frames every 60 milliseconds, so the ((time)) is divided by 60 first. When the player is standing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite. {{index "flipHorizontally function", "CanvasDisplay class"}} -Because the ((sprite))s are slightly wider than the player object—24 -instead of 16 pixels to allow some space for feet and arms—the method -has to adjust the x-coordinate and width by a given amount -(`playerXOverlap`). +Because the ((sprite))s are slightly wider than the player object—24 instead of 16 pixels to allow some space for feet and arms—the method has to adjust the x-coordinate and width by a given amount (`playerXOverlap`). ```{sandbox: "game", includeCode: true} let playerSprites = document.createElement("img"); @@ -1083,8 +795,7 @@ CanvasDisplay.prototype.drawPlayer = function(player, x, y, }; ``` -The `drawPlayer` method is called by `drawActors`, which is -responsible for drawing all the actors in the game. +The `drawPlayer` method is called by `drawActors`, which is responsible for drawing all the actors in the game. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.drawActors = function(actors) { @@ -1105,23 +816,17 @@ CanvasDisplay.prototype.drawActors = function(actors) { }; ``` -When ((drawing)) something that is not the ((player)), we look at its -type to find the offset of the correct sprite. The ((lava)) tile is -found at offset 20, and the ((coin)) sprite is found at 40 (two times -`scale`). +When ((drawing)) something that is not the ((player)), we look at its type to find the offset of the correct sprite. The ((lava)) tile is found at offset 20, and the ((coin)) sprite is found at 40 (two times `scale`). {{index viewport}} -We have to subtract the viewport's position when computing the actor's -position since (0,0) on our ((canvas)) corresponds to the top left of -the viewport, not the top left of the level. We could also have used -`translate` for this. Either way works. +We have to subtract the viewport's position when computing the actor's position, since (0, 0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works. {{if interactive This document plugs the new display into `runGame`: -```{lang: "text/html", sandbox: game, focus: yes, startCode: true} +```{lang: html, sandbox: game, focus: yes, startCode: true} <body> <script> runGame(GAME_LEVELS, CanvasDisplay); @@ -1135,10 +840,9 @@ if}} {{index [game, screenshot], [game, "with canvas"]}} -That concludes the new ((display)) system. The resulting game looks -something like this: +That concludes the new ((display)) system. The resulting game looks something like this: -{{figure {url: "img/canvas_game.png", alt: "The game as shown on canvas",width: "8cm"}}} +{{figure {url: "img/canvas_game.png", alt: "Screenshot of the game as shown on canvas", width: "8cm"}}} if}} @@ -1146,105 +850,49 @@ if}} ## Choosing a graphics interface -So when you need to generate graphics in the browser, you can choose -between plain HTML, ((SVG)), and ((canvas)). There is no single -_best_ approach that works in all situations. Each option has -strengths and weaknesses. +When you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses. {{index "text wrapping"}} -Plain HTML has the advantage of being simple. It also integrates well -with ((text)). Both SVG and canvas allow you to draw text, but they -won't help you position that text or wrap it when it takes up more -than one line. In an HTML-based picture, it is much easier to include -blocks of text. +Plain HTML has the advantage of being simple. It also integrates well with ((text)). Both SVG and canvas allow you to draw text, but they won't help you position that text or wrap it when it takes up more than one line. In an HTML-based picture, it is much easier to include blocks of text. {{index zooming, SVG}} -SVG can be used to produce ((crisp)) ((graphics)) that look good at -any zoom level. Unlike HTML, it is designed for drawing -and is thus more suitable for that purpose. +SVG can be used to produce ((crisp)) ((graphics)) that look good at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose. {{index [DOM, graphics], SVG, "event handling", ["data structure", tree]}} -Both SVG and HTML build up a data structure (the DOM) that -represents your picture. This makes it possible to modify elements -after they are drawn. If you need to repeatedly change a small part of -a big ((picture)) in response to what the user is doing or as part of -an ((animation)), doing it in a canvas can be needlessly expensive. -The DOM also allows us to register mouse event handlers on every -element in the picture (even on shapes drawn with SVG). You can't do -that with canvas. - -{{index performance, optimization}} - -But ((canvas))'s ((pixel))-oriented approach can be an advantage when -drawing a huge number of tiny elements. The fact that it does not -build up a data structure but only repeatedly draws onto the same -pixel surface gives canvas a lower cost per shape. +Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big ((picture)) in response to what the user is doing or as part of an ((animation)), doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can't do that with canvas. -{{index "ray tracer"}} +{{index performance, optimization, "ray tracer"}} -There are also effects, such as rendering a scene one pixel at a time -(for example, using a ray tracer) or postprocessing an image with -JavaScript (blurring or distorting it), that can be realistically -handled only by a ((pixel))-based approach. +But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape. There are also effects that are only practical with a canvas element, such as rendering a scene one ((pixel)) at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it). -In some cases, you may want to combine several of these techniques. -For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but -show ((text))ual information by positioning an HTML element on top -of the picture. +In some cases, you may want to combine several of these techniques. For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but show ((text))ual information by positioning an HTML element on top of the picture. {{index display}} -For nondemanding applications, it really doesn't matter much which -interface you choose. The display we built for our game in this -chapter could have been implemented using any of these three -((graphics)) technologies since it does not need to draw text, handle -mouse interaction, or work with an extraordinarily large number of -elements. +For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three ((graphics)) technologies, since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements. ## Summary -In this chapter we discussed techniques for drawing graphics in the -browser, focusing on the `<canvas>` element. +In this chapter we discussed techniques for drawing graphics in the browser, focusing on the `<canvas>` element. -A canvas node represents an area in a document that our program may -draw on. This drawing is done through a drawing context object, -created with the `getContext` method. +A canvas node represents an area in a document that our program may draw on. This drawing is done through a drawing context object, created with the `getContext` method. -The 2D drawing interface allows us to fill and stroke various shapes. -The context's `fillStyle` property determines how shapes are filled. -The `strokeStyle` and `lineWidth` properties control the way lines are -drawn. +The 2D drawing interface allows us to fill and stroke various shapes. The context's `fillStyle` property determines how shapes are filled. The `strokeStyle` and `lineWidth` properties control the way lines are drawn. -Rectangles and pieces of text can be drawn with a single method call. -The `fillRect` and `strokeRect` methods draw rectangles, and the -`fillText` and `strokeText` methods draw text. To create custom -shapes, we must first build up a path. +Rectangles and pieces of text can be drawn with a single method call. The `fillRect` and `strokeRect` methods draw rectangles, and the `fillText` and `strokeText` methods draw text. To create custom shapes, we must first build up a path. {{index stroking, filling}} -Calling `beginPath` starts a new path. A number of other methods add -lines and curves to the current path. For example, `lineTo` can add a -straight line. When a path is finished, it can be filled with the -`fill` method or stroked with the `stroke` method. +Calling `beginPath` starts a new path. A number of other methods add lines and curves to the current path. For example, `lineTo` can add a straight line. When a path is finished, it can be filled with the `fill` method or stroked with the `stroke` method. -Moving pixels from an image or another canvas onto our canvas is done -with the `drawImage` method. By default, this method draws the whole -source image, but by giving it more parameters, you can copy a -specific area of the image. We used this for our game by copying -individual poses of the game character out of an image that contained -many such poses. +Moving pixels from an image or another canvas onto our canvas is done with the `drawImage` method. By default, this method draws the whole source image, but by giving it more parameters, you can copy a specific area of the image. We used this for our game by copying individual poses of the game character out of an image that contained many such poses. -Transformations allow you to draw a shape in multiple orientations. A -2D drawing context has a current transformation that can be changed -with the `translate`, `scale`, and `rotate` methods. These will affect -all subsequent drawing operations. A transformation state can be saved -with the `save` method and restored with the `restore` method. +Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a current transformation that can be changed with the `translate`, `scale`, and `rotate` methods. These will affect all subsequent drawing operations. A transformation state can be saved with the `save` method and restored with the `restore` method. -When showing an animation on a canvas, the `clearRect` method can be -used to clear part of the canvas before redrawing it. +When showing an animation on a canvas, the `clearRect` method can be used to clear part of the canvas before redrawing it. ## Exercises @@ -1266,22 +914,17 @@ Write a program that draws the following ((shape))s on a ((canvas)): 5. A yellow ((star)) -{{figure {url: "img/exercise_shapes.png", alt: "The shapes to draw",width: "8cm"}}} +{{figure {url: "img/exercise_shapes.png", alt: "Picture showing the shapes you are asked to draw", width: "8cm"}}} -When drawing the last two, you may want to refer to the explanation of -`Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes -how to get coordinates on a circle using these functions. +When drawing the last two shapes, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions. -{{index readability, "hard-coding"}} +{{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 -```{lang: "text/html", test: no} +```{lang: html, test: no} <canvas width="600" height="200"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -1296,43 +939,23 @@ if}} {{index [path, canvas], "shapes (exercise)"}} -The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable -center coordinates and add each of the four corners around the center. +The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable center coordinates and add each of the four corners around the center. {{index "flipHorizontally function", rotation}} -The ((diamond)) (2) can be drawn the straightforward way, with a path, -or the interesting way, with a `rotate` ((transformation)). To use -rotation, you will have to apply a trick similar to what we did in the -`flipHorizontally` function. Because you want to rotate around the -center of your rectangle and not around the point (0,0), you must -first `translate` to there, then rotate, and then translate back. +The ((diamond)) (2) can be drawn the straightforward way, with a path, or the interesting way, with a `rotate` ((transformation)). To use rotation, you will have to apply a trick similar to what we did in the `flipHorizontally` function. Because you want to rotate around the center of your rectangle and not around the point (0, 0), you must first `translate` to there, then rotate, and then translate back. -Make sure you reset the transformation after drawing any shape that -creates one. +Make sure you reset the transformation after drawing any shape that creates one. {{index "remainder operator", "% operator"}} -For the ((zigzag)) (3) it becomes impractical to write a new call to -`lineTo` for each line segment. Instead, you should use a ((loop)). -You can have each iteration draw either two ((line)) segments (right -and then left again) or one, in which case you must use the evenness -(`% 2`) of the loop index to determine whether to go left or right. +For the ((zigzag)) (3) it becomes impractical to write a new call to `lineTo` for each line segment. Instead, you should use a ((loop)). You can have each iteration draw either two ((line)) segments (right and then left again) or one, in which case you must use the evenness (`% 2`) of the loop index to determine whether to go left or right. -You'll also need a loop for the ((spiral)) (4). If you draw a series -of points, with each point moving further along a circle around the -spiral's center, you get a circle. If, during the loop, you vary the -radius of the circle on which you are putting the current point and go -around more than once, the result is a spiral. +You'll also need a loop for the ((spiral)) (4). If you draw a series of points, with each point moving 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"}} -The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. -You could also draw one with straight lines. Divide a circle into -eight pieces for a star with eight points, or however many pieces you -want. Draw lines between these points, making them curve toward the -center of the star. With `quadraticCurveTo`, you can use the center as -the control point. +The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. You could also draw one with straight lines. Divide a circle into eight pieces for a star with eight points, or however many pieces you want. Draw lines between these points, making them curve toward the center of the star. With `quadraticCurveTo`, you can use the center as the control point. hint}} @@ -1342,19 +965,13 @@ hint}} {{index label, text, "pie chart example"}} -[Earlier](canvas#pie_chart) in the chapter, we saw an example program -that drew a pie chart. Modify this program so that the name of each -category is shown next to the slice that represents it. Try to find a -pleasing-looking way to automatically position this text that would -work for other data sets as well. You may assume that categories are -big enough to leave ample room for their labels. +[Earlier](canvas#pie_chart) in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other datasets as well. You may assume that categories are big enough to leave enough room for their labels. -You might need `Math.sin` and `Math.cos` again, which are described in -[Chapter ?](dom#sin_cos). +You might need `Math.sin` and `Math.cos` again, which are described in [Chapter ?](dom#sin_cos). {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <canvas width="600" height="300"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -1383,19 +1000,11 @@ if}} {{index "fillText method", "textAlign property", "textBaseline property", "pie chart example"}} -You will need to call `fillText` and set the context's `textAlign` and -`textBaseline` properties in such a way that the text ends up where -you want it. +You will need to call `fillText` and set the context's `textAlign` and `textBaseline` properties in such a way that the text ends up where you want it. -A sensible way to position the labels would be to put the text on the -line going from the center of the pie through the middle of the slice. -You don't want to put the text directly against the side of the pie -but rather move the text out to the side of the pie by a given number -of pixels. +A sensible way to position the labels would be to put the text on the line going from the center of the pie through the middle of the slice. You don't want to put the text directly against the side of the pie but rather move the text out to the side of the pie by a given number of pixels. -The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The -following code finds a position on this line 120 pixels from the -center: +The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The following code finds a position on this line 120 pixels from the center: ```{test: no} let middleAngle = currentAngle + 0.5 * sliceAngle; @@ -1403,19 +1012,11 @@ let textX = Math.cos(middleAngle) * 120 + centerX; let textY = Math.sin(middleAngle) * 120 + centerY; ``` -For `textBaseline`, the value `"middle"` is probably appropriate when -using this approach. What to use for `textAlign` depends on which side -of the circle we are on. On the left, it should be `"right"`, and on -the right, it should be `"left"`, so that the text is positioned away -from the pie. +For `textBaseline`, the value `"middle"` is probably appropriate when using this approach. What to use for `textAlign` depends on which side of the circle we are on. On the left, it should be `"right"`, and on the right, it should be `"left"`, so that the text is positioned away from the pie. {{index "Math.cos function"}} -If you are not sure how to find out which side of the circle a given -angle is on, look to the explanation of `Math.cos` in [Chapter -?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it -corresponds to, which in turn tells us exactly which side of the -circle we are on. +If you are not sure how to find out which side of the circle a given angle is on, look to the explanation of `Math.cos` in [Chapter ?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circle we are on. hint}} @@ -1423,14 +1024,11 @@ hint}} {{index [animation, "bouncing ball"], "requestAnimationFrame function", bouncing}} -Use the `requestAnimationFrame` technique that we saw in [Chapter -?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a -((box)) with a bouncing ((ball)) in it. The ball moves at a constant -((speed)) and bounces off the box's sides when it hits them. +Use the `requestAnimationFrame` technique that we saw in [Chapter ?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a ((box)) with a bouncing ((ball)) in it. The ball moves at a constant ((speed)) and bounces off the box's sides when it hits them. {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <canvas width="400" height="400"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); @@ -1457,26 +1055,15 @@ if}} {{index "strokeRect method", animation, "arc method"}} -A ((box)) is easy to draw with `strokeRect`. Define a binding that -holds its size or define two bindings if your box's width and height -differ. To create a round ((ball)), start a path and call `arc(x, y, -radius, 0, 7)`, which creates an arc going from zero to more than a -whole circle. Then fill the path. +A ((box)) is easy to draw with `strokeRect`. Define a binding that holds its size, or define two bindings if your box's width and height differ. To create a round ((ball)), start a path and call `arc(x, y, radius, 0, 7)`, which creates an arc going from zero to more than a whole circle. Then fill the path. {{index "collision detection", "Vec class"}} -To model the ball's position and ((speed)), you can use the `Vec` -class from [Chapter ?](game#vector)[ (which is available on this -page)]{if interactive}. Give it a starting speed, preferably one that -is not purely vertical or horizontal, and for every ((frame)) multiply -that speed by the amount of time that elapsed. When the ball gets -too close to a vertical wall, invert the x component in its speed. -Likewise, invert the y component when it hits a horizontal wall. +To model the ball's position and ((speed)), you can use the `Vec` class from [Chapter ?](game#vector)[ (which is available on this page)]{if interactive}. Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every ((frame)) multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the _x_ component in its speed. Likewise, invert the _y_ component when it hits a horizontal wall. {{index "clearRect method", clearing}} -After finding the ball's new position and speed, use `clearRect` to -delete the scene and redraw it using the new position. +After finding the ball's new position and speed, use `clearRect` to delete the scene and redraw it using the new position. hint}} @@ -1484,38 +1071,21 @@ hint}} {{index optimization, "bitmap graphics", mirror}} -One unfortunate thing about ((transformation))s is that they slow down -the drawing of bitmaps. The position and size of each ((pixel)) has to be -transformed, and though it is possible that ((browser))s will get -cleverer about transformation in the ((future)), they currently cause a -measurable increase in the time it takes to draw a bitmap. +One unfortunate thing about ((transformation))s is that they slow down the drawing of bitmaps. The position and size of each ((pixel)) 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. +In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion. -Think of a way to allow us to draw an inverted character without -loading additional image files and without having to make transformed -`drawImage` calls every frame. +Think of a way to draw an inverted character without loading additional image files and without having to make transformed `drawImage` calls every frame. {{hint {{index mirror, scaling, "drawImage method"}} -The key to the solution is the fact that we can use a ((canvas)) -element as a source image when using `drawImage`. It is possible to -create an extra `<canvas>` element, without adding it to the document, -and draw our inverted sprites to it, once. When drawing an actual -frame, we just copy the already inverted sprites to the main canvas. +The key to the solution is the fact that we can use a ((canvas)) element as a source image when using `drawImage`. It is possible to create an extra `<canvas>` element, without adding it to the document, and draw our inverted sprites to it, once. When drawing an actual frame, we just copy the already inverted sprites to the main canvas. {{index "load event"}} -Some care would be required because images do not load instantly. We -do the inverted drawing only once, and if we do it before the image -loads, it won't draw anything. A `"load"` handler on the image can be -used to draw the inverted images to the extra canvas. This canvas can -be used as a drawing source immediately (it'll simply be blank until -we draw the character onto it). +Some care would be required because images do not load instantly. We do the inverted drawing only once, and if we do it before the image loads, it won't draw anything. A `"load"` handler on the image can be used to draw the inverted images to the extra canvas. This canvas can be used as a drawing source immediately (it'll simply be blank until we draw the character onto it). hint}} diff --git a/18_http.md b/18_http.md index 3f0713ca4..69da0a36e 100644 --- a/18_http.md +++ b/18_http.md @@ -1,38 +1,26 @@ -{{meta {load_files: ["code/chapter/18_http.js"]}}} +{{meta {}}} # HTTP and Forms -{{quote {author: "Roy Fielding", title: "Architectural Styles and the Design of Network-based Software Architectures", chapter: true} +{{quote {author: "Tim Berners-Lee", chapter: true} -Communication must be stateless in nature [...] such that each request -from client to server must contain all of the information necessary to -understand the request, and cannot take advantage of any stored -context on the server. +What was often difficult for people to understand about the design was that there was nothing else beyond URLs, HTTP and HTML. There was no central computer 'controlling' the web, no single network on which these protocols worked, not even an organisation anywhere that 'ran' the Web. The Web was not a physical 'thing' that existed in a certain 'place'. It was a 'space' in which information could exist. quote}} {{index "Fielding, Roy"}} -{{figure {url: "img/chapter_picture_18.jpg", alt: "Picture of a web form on a medieval scroll", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_18.jpg", alt: "Illustration showing a web sign-up form on a parchment scroll", chapter: "framed"}}} {{index [browser, environment]}} -The _Hypertext Transfer Protocol_, already mentioned in [Chapter -?](browser#web), is the mechanism through which data is requested and -provided on the ((World Wide Web)). This chapter describes the -((protocol)) in more detail and explains the way browser -JavaScript has access to it. +The Hypertext Transfer Protocol, introduced in [Chapter ?](browser#web), is the mechanism through which data is requested and provided on the ((World Wide Web)). This chapter describes the ((protocol)) in more detail and explains the way browser JavaScript has access to it. ## The protocol {{index "IP address"}} -If you type _eloquentjavascript.net/18_http.html_ into your browser's -((address bar)), the ((browser)) first looks up the ((address)) of the -server associated with _eloquentjavascript.net_ and tries to open a -((TCP)) ((connection)) to it on ((port)) 80, the default port for -((HTTP)) traffic. If the ((server)) exists and accepts the connection, -the browser might send something like this: +If you type _eloquentjavascript.net/18_http.html_ in your browser's ((address bar)), the ((browser)) first looks up the ((address)) of the server associated with _eloquentjavascript.net_ and tries to open a ((TCP)) ((connection)) to it on ((port)) 80, the default port for ((HTTP)) traffic. If the ((server)) exists and accepts the connection, the browser might send something like this: ```{lang: http} GET /18_http.html HTTP/1.1 @@ -44,22 +32,19 @@ Then the server responds, through that same connection. ```{lang: http} HTTP/1.1 200 OK -Content-Length: 65585 +Content-Length: 87320 Content-Type: text/html -Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT +Last-Modified: Fri, 13 Oct 2023 10:05:41 GMT <!doctype html> ... the rest of the document ``` -The browser takes the part of the ((response)) after the blank line, -its _body_ (not to be confused with the HTML `<body>` tag), and -displays it as an ((HTML)) document. +The browser takes the part of the ((response)) after the blank line, its _body_ (not to be confused with the HTML `<body>` tag), and displays it as an ((HTML)) document. {{index HTTP}} -The information sent by the client is called the _((request))_. It -starts with this line: +The information sent by the client is called the _((request))_. It starts with this line: ```{lang: http} GET /18_http.html HTTP/1.1 @@ -67,42 +52,19 @@ GET /18_http.html HTTP/1.1 {{index "DELETE method", "PUT method", "GET method", [method, HTTP]}} -The first word is the _method_ of the ((request)). `GET` means -that we want to _get_ the specified resource. Other common methods are -`DELETE` to delete a resource, `PUT` to create or replace it, and `POST` to send -information to it. Note that the ((server)) is not obliged to carry -out every request it gets. If you walk up to a random website and tell -it to `DELETE` its main page, it'll probably refuse. +The first word is the _method_ of the ((request)). `GET` means that we want to _get_ the specified resource. Other common methods are `DELETE` to delete a resource, `PUT` to create or replace it, and `POST` to send information to it. Note that the ((server)) is not obliged to carry out every request it gets. If you walk up to a random website and tell it to `DELETE` its main page, it'll probably refuse. {{index [path, URL], GitHub, [file, resource]}} -The part after the method name is the path of the _((resource))_ the -request applies to. In the simplest case, a resource is simply a -file on the ((server)), but the protocol doesn't require it to be. -A resource may be anything that can be transferred _as if_ it is a -file. Many servers generate the responses they produce on the fly. For -example, if you open -[_https://github.com/marijnh_](https://github.com/marijnh), the server looks -in its database for a user named "marijnh", and if it finds one, it -will generate a profile page for that user. - -After the resource path, the first line of the request mentions -`HTTP/1.1` to indicate the ((version)) of the ((HTTP)) ((protocol)) it -is using. - -In practice, many sites use HTTP version 2, which supports the same -concepts as version 1.1 but is a lot more complicated so that it can -be faster. Browsers will automatically switch to the appropriate -protocol version when talking to a given server, and the outcome of a -request is the same regardless of which version is used. Because version -1.1 is more straightforward and easier to play around with, we'll -focus on that. +The part after the method name is the path of the _((resource))_ the request applies to. In the simplest case, a resource is simply a file on the ((server)), but the protocol doesn't require it to be. A resource may be anything that can be transferred _as if_ it is a file. Many servers generate the responses they produce on the fly. For example, if you open [_https://github.com/marijnh_](https://github.com/marijnh), the server looks in its database for a user named "marijnh", and if it finds one, it will generate a profile page for that user. + +After the resource path, the first line of the request mentions `HTTP/1.1` to indicate the ((version)) of the ((HTTP)) ((protocol)) it is using. + +In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we'll use that to illustrate the protocol. {{index "status code"}} -The server's ((response)) will start with a version as well, followed -by the status of the response, first as a three-digit status code and -then as a human-readable string. +The server's ((response)) will start with a version as well, followed by the status of the response, first as a three-digit status code and then as a human-readable string. ```{lang: http} HTTP/1.1 200 OK @@ -110,71 +72,43 @@ HTTP/1.1 200 OK {{index "200 (HTTP status code)", "error response", "404 (HTTP status code)"}} -Status codes starting with a 2 indicate that the request succeeded. -Codes starting with 4 mean there was something wrong with the -((request)). 404 is probably the most famous HTTP status code—it means -that the resource could not be found. Codes that start with 5 mean an -error happened on the ((server)) and the request is not to blame. +Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the ((request)). The most famous HTTP status code is probably 404, which means that the resource could not be found. Codes that start with 5 mean an error happened on the ((server)) and the request is not to blame. {{index HTTP}} {{id headers}} -The first line of a request or response may be followed by any number -of _((header))s_. These are lines in the form `name: value` that -specify extra information about the request or response. These headers -were part of the example ((response)): +The first line of a request or response may be followed by any number of _((header))s_. These are lines in the form `name: value` that specify extra information about the request or response. These headers were part of the example ((response)): ```{lang: null} -Content-Length: 65585 +Content-Length: 87320 Content-Type: text/html -Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT +Last-Modified: Fri, 13 Oct 2023 10:05:41 GMT ``` {{index "Content-Length header", "Content-Type header", "Last-Modified header"}} -This tells us the size and type of the response document. In this -case, it is an HTML document of 65,585 bytes. It also tells us when -that document was last modified. +This tells us the size and type of the response document. In this case, it is an HTML document of 87,320 bytes. It also tells us when that document was last modified. -{{index "Host header", domain}} - -For most ((header))s, the client and server are free to decide whether -to include them in a ((request)) or ((response)). But a few are -required. For example, the `Host` header, which specifies the -hostname, should be included in a request because a ((server)) might -be serving multiple hostnames on a single ((IP address)), and without -that header, the server won't know which hostname the client is trying -to talk to. +The client and server are free to decide what ((header))s to include in their ((request))s or ((response))s. But some of them are necessary for things to work. For example, without a `Content-Type` header in the response, the browser won't know how to display the document. {{index "GET method", "DELETE method", "PUT method", "POST method", "body (HTTP)"}} -After the headers, both requests and responses may include a blank -line followed by a body, which contains the data being sent. `GET` and -`DELETE` requests don't send along any data, but `PUT` and `POST` -requests do. Similarly, some response types, such as error responses, -do not require a body. +After the headers, both requests and responses may include a blank line followed by a body, which contains the actual document being sent. `GET` and `DELETE` requests don't send along any data, but `PUT` and `POST` requests do. Some response types, such as error responses, also don't require a body. ## Browsers and HTTP {{index HTTP, [file, resource]}} -As we saw in the example, a ((browser)) will make a request when we -enter a ((URL)) in its ((address bar)). When the resulting HTML page -references other files, such as ((image))s and JavaScript files, -those are also retrieved. +As we saw, a ((browser)) will make a request when we enter a ((URL)) in its ((address bar)). When the resulting HTML page references other files, such as ((image))s and JavaScript files, it will retrieve those as well. {{index parallelism, "GET method"}} -A moderately complicated ((website)) can easily include anywhere from -10 to 200 ((resource))s. To be able to fetch those quickly, browsers -will make several `GET` requests simultaneously, rather than waiting -for the responses one at a time. +A moderately complicated ((website)) can easily include anywhere from 10 to 200 ((resource))s. To be able to fetch those quickly, browsers will make several `GET` requests simultaneously, rather than waiting for the responses one at a time. -HTML pages may include _((form))s_, which allow the user to fill out -information and send it to the server. This is an example of a form: +HTML pages may include _((form))s_, which allow the user to fill out information and send it to the server. This is an example of a form: -```{lang: "text/html"} +```{lang: html} <form method="GET" action="example/message.html"> <p>Name: <input type="text" name="name"></p> <p>Message:<br><textarea name="message"></textarea></p> @@ -184,16 +118,9 @@ information and send it to the server. This is an example of a form: {{index form, "method attribute", "GET method"}} -This code describes a form with two ((field))s: a small one asking for -a name and a larger one to write a message in. When you click the Send -((button)), the form is _submitted_, meaning that the content of its -field is packed into an HTTP request and the browser navigates to the -result of that request. +This code describes a form with two ((field))s: a small one asking for a name and a larger one to write a message in. When you click the Send ((button)), the form is _submitted_, meaning that the content of its field is packed into an HTTP request and the browser navigates to the result of that request. -When the `<form>` element's `method` attribute is `GET` (or is -omitted), the information in the form is added to the end of the -`action` URL as a _((query string))_. The browser might make a request -to this URL: +When the `<form>` element's `method` attribute is `GET` (or is omitted), the information in the form is added to the end of the `action` URL as a _((query string))_. The browser might make a request to this URL: ```{lang: null} GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1 @@ -201,24 +128,11 @@ GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1 {{index "ampersand character"}} -The ((question mark)) indicates the end of the path part of the URL -and the start of the query. It is followed by pairs of names and -values, corresponding to the `name` attribute on the form field -elements and the content of those elements, respectively. An ampersand -character (`&`) is used to separate the pairs. +The ((question mark)) indicates the end of the path part of the URL and the start of the query. It is followed by pairs of names and values, corresponding to the `name` attribute on the form field elements and the content of those elements, respectively. An ampersand character (`&`) is used to separate the pairs. {{index [escaping, "in URLs"], "hexadecimal number", "encodeURIComponent function", "decodeURIComponent function"}} -The actual message encoded in the URL is "Yes?", but the question mark -is replaced by a strange code. Some characters in query strings must -be escaped. The question mark, represented as `%3F`, is one of those. -There seems to be an unwritten rule that every format needs its own -way of escaping characters. This one, called _((URL encoding))_, uses -a ((percent sign)) followed by two hexadecimal (base 16) digits that -encode the character code. In this case, 3F, which is 63 in decimal -notation, is the code of a question mark character. JavaScript -provides the `encodeURIComponent` and `decodeURIComponent` functions -to encode and decode this format. +The actual message encoded in the URL is "Yes?" but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as `%3F`, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called _((URL encoding))_, uses a ((percent sign)) followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the `encodeURIComponent` and `decodeURIComponent` functions to encode and decode this format. ``` console.log(encodeURIComponent("Yes?")); @@ -229,10 +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 @@ -242,16 +153,9 @@ Content-type: application/x-www-form-urlencoded name=Jean&message=Yes%3F ``` -`GET` requests should be used for requests that do not have ((side -effect))s but simply ask for information. Requests that change -something on the server, for example creating a new account or posting -a message, should be expressed with other methods, such as `POST`. -Client-side software such as a browser knows that it shouldn't blindly -make `POST` requests but will often implicitly make `GET` requests—for -example to prefetch a resource it believes the user will soon need. +`GET` requests should be used for requests that do not have ((side effect))s but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as `POST`. Client-side software such as a browser knows that it shouldn't blindly make `POST` requests but will often implicitly make `GET` requests—to prefetch a resource it believes the user will soon need, for example. -We'll come back to forms and how to interact with them from JavaScript -[later in the chapter](http#forms). +We'll come back to forms and how to interact with them from JavaScript [later in the chapter](http#forms). {{id fetch}} @@ -259,9 +163,7 @@ We'll come back to forms and how to interact with them from JavaScript {{index "fetch function", "Promise class", [interface, module]}} -The interface through which browser JavaScript can make HTTP -requests is called `fetch`. Since it is relatively new, it -conveniently uses promises (which is rare for browser interfaces). +The interface through which browser JavaScript can make HTTP requests is called `fetch`. ```{test: no} fetch("example/data.txt").then(response => { @@ -274,35 +176,17 @@ fetch("example/data.txt").then(response => { {{index "Response class", "status property", "headers property"}} -Calling `fetch` returns a promise that resolves to a `Response` object -holding information about the server's response, such as its status -code and its headers. The headers are wrapped in a `Map`-like object -that treats its keys (the header names) as case insensitive because -header names are not supposed to be case sensitive. This means -`headers.get("Content-Type")` and `headers.get("content-TYPE")` will -return the same value. +Calling `fetch` returns a promise that resolves to a `Response` object holding information about the server's response, such as its status code and its headers. The headers are wrapped in a `Map`-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means `headers.get("Content-Type")` and `headers.get("content-TYPE")` will return the same value. -Note that the promise returned by `fetch` resolves successfully even -if the server responded with an error code. It _might_ also be -rejected if there is a network error or if the ((server)) that the -request is addressed to can't be found. +Note that the promise returned by `fetch` resolves successfully even if the server responded with an error code. It can also be rejected if there is a network error or if the ((server)) to which that the request is addressed can't be found. {{index [path, URL], "relative URL"}} -The first argument to `fetch` is the URL that should be requested. -When that ((URL)) doesn't start with a protocol name (such as _http:_), -it is treated as _relative_, which means it is interpreted relative -to the current document. When it starts with a slash (/), it replaces -the current path, which is the part after the server name. When it -does not, the part of the current path up to and including its last -((slash character)) is put in front of the relative URL. +The first argument to `fetch` is the URL that should be requested. When that ((URL)) doesn't start with a protocol name (such as _http:_), it is treated as _relative_, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last ((slash character)) is put in front of the relative URL. {{index "text method", "body (HTTP)", "Promise class"}} -To get at the actual content of a response, you can use its `text` -method. Because the initial promise is resolved as soon as the -response's headers have been received and because reading the response body -might take a while longer, this again returns a promise. +To get at the actual content of a response, you can use its `text` method. Because the initial promise is resolved as soon as the response's headers have been received and because reading the response body might take a while longer, this again returns a promise. ```{test: no} fetch("example/data.txt") @@ -313,16 +197,11 @@ fetch("example/data.txt") {{index "json method"}} -A similar method, called `json`, returns a promise that -resolves to the value you get when parsing the body as ((JSON)) or -rejects if it's not valid JSON. +A similar method, called `json`, returns a promise that resolves to the value you get when parsing the body as ((JSON)) or rejects if it's not valid JSON. {{index "GET method", "body (HTTP)", "DELETE method", "method property"}} -By default, `fetch` uses the `GET` method to make its request and -does not include a request body. You can configure it differently by -passing an object with extra options as a second argument. For -example, this request tries to delete `example/data.txt`: +By default, `fetch` uses the `GET` method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete `example/data.txt`: ```{test: no} fetch("example/data.txt", {method: "DELETE"}).then(resp => { @@ -333,15 +212,11 @@ fetch("example/data.txt", {method: "DELETE"}).then(resp => { {{index "405 (HTTP status code)"}} -The 405 status code means "method not allowed", an HTTP server's way -of saying "I can't do that". +The 405 status code means "method not allowed", an HTTP server's way of saying "I'm afraid I can't do that". {{index "Range header", "body property", "headers property"}} -To add a request body, you can include a `body` option. To set -headers, there's the `headers` option. For example, this request -includes a `Range` header, which instructs the server to return only -part of a response. +To add a request body for a `PUT` or `POST` request, you can include a `body` option. To set headers, there's the `headers` option. For example, this request includes a `Range` header, which instructs the server to return only part of a document. ```{test: no} fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) @@ -350,11 +225,7 @@ fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) // → the content ``` -The browser will automatically add some request ((header))s, such as -"Host" and those needed for the server to figure out the size of the -body. But adding your own headers is often useful to include things -such as authentication information or to tell the server which file -format you'd like to receive. +The browser will automatically add some request ((header))s, such as "Host" and those needed for the server to figure out the size of the body. But adding your own headers is often useful to include things such as authentication information or to tell the server which file format you'd like to receive. {{id http_sandbox}} @@ -362,25 +233,13 @@ format you'd like to receive. {{index sandbox, [browser, security]}} -Making ((HTTP)) requests in web page scripts once again raises -concerns about ((security)). The person who controls the script might -not have the same interests as the person on whose computer it is -running. More specifically, if I visit _themafia.org_, I do not want -its scripts to be able to make a request to _mybank.com_, using -identifying information from my browser, with instructions to -transfer all my money to some random account. +Making ((HTTP)) requests in web page scripts once again raises concerns about ((security)). The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if I visit _themafia.org_, I do not want its scripts to be able to make a request to _mybank.com_, using identifying information from my browser, with instructions to transfer away all my money. -For this reason, browsers protect us by disallowing scripts to make -HTTP requests to other ((domain))s (names such as _themafia.org_ and -_mybank.com_). +For this reason, browsers protect us by disallowing scripts to make HTTP requests to other ((domain))s (names such as _themafia.org_ and _mybank.com_). {{index "Access-Control-Allow-Origin header", "cross-domain request"}} -This can be an annoying problem when building systems that want to -access several domains for legitimate reasons. Fortunately, -((server))s can include a ((header)) like this in their ((response)) -to explicitly indicate to the browser that it is okay for the request -to come from another domain: +This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, ((server))s can include a ((header)) like this in their ((response)) to explicitly indicate to the browser that it is okay for the request to come from another domain: ```{lang: null} Access-Control-Allow-Origin: * @@ -390,110 +249,55 @@ Access-Control-Allow-Origin: * {{index client, HTTP, [interface, HTTP]}} -When building a system that requires ((communication)) between a -JavaScript program running in the ((browser)) (client-side) and a -program on a ((server)) (server-side), there are several different -ways to model this communication. +When building a system that requires ((communication)) between a JavaScript program running in the ((browser)) (client-side) and a program on a ((server)) (server-side), there are several different ways to model this communication. {{index [network, abstraction], abstraction}} -A commonly used model is that of _((remote procedure call))s_. In this -model, communication follows the patterns of normal function calls, -except that the function is actually running on another machine. -Calling it involves making a request to the server that includes the -function's name and arguments. The response to that request contains -the returned value. +A commonly used model is that of _((remote procedure call))s_. In this model, communication follows the patterns of normal function calls, except that the function is actually running on another machine. Calling it involves making a request to the server that includes the function's name and arguments. The response to that request contains the returned value. -When thinking in terms of remote procedure calls, HTTP is just a -vehicle for communication, and you will most likely write an -abstraction layer that hides it entirely. +When thinking in terms of remote procedure calls, HTTP is just a vehicle for communication, and you will most likely write an abstraction layer that hides it entirely. {{index "media type", "document format", [method, HTTP]}} -Another approach is to build your communication around the concept of -((resource))s and ((HTTP)) methods. Instead of a remote procedure -called `addUser`, you use a `PUT` request to `/users/larry`. Instead -of encoding that user's properties in function arguments, you define a -JSON document format (or use an existing format) that represents a -user. The body of the `PUT` request to create a new resource is then -such a document. A resource is fetched by making a `GET` request to -the resource's URL (for example, `/user/larry`), which again returns -the document representing the resource. - -This second approach makes it easier to use some of the features that -HTTP provides, such as support for caching resources (keeping a copy -on the client for fast access). The concepts used in HTTP, which are -well designed, can provide a helpful set of principles to design your -server interface around. +Another approach is to build your communication around the concept of ((resource))s and ((HTTP)) methods. Instead of a remote procedure called `addUser`, you use a `PUT` request to `/users/larry`. Instead of encoding that user's properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the `PUT` request to create a new resource is then such a document. A resource is fetched by making a `GET` request to the resource's URL (for example, `/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. ## Security and HTTPS {{index "man-in-the-middle", security, HTTPS, [network, security]}} -Data traveling over the Internet tends to follow a long, dangerous -road. To get to its destination, it must hop through anything from -coffee shop Wi-Fi hotspots to networks controlled by various companies and -states. At any point along its route it may be inspected or even -modified. +Data traveling over the internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route, it may be inspected or even modified. {{index tampering}} -If it is important that something remain secret, such as the -((password)) to your ((email)) account, or that it arrive at its -destination unmodified, such as the account number you transfer money -to via your bank's website, plain HTTP is not good enough. +If it is important that something remain secret, such as the ((password)) to your ((email)) account, or that it arrive at its destination unmodified, such as the account number you transfer money to via your bank's website, plain HTTP is not good enough. {{index cryptography, encryption}} {{indexsee "Secure HTTP", HTTPS, [browser, security]}} -The secure ((HTTP)) protocol, used for ((URL))s starting with _https://_, -wraps HTTP traffic in a way that makes it harder to read and tamper -with. Before exchanging data, the client verifies that the server is -who it claims to be by asking it to prove that it has a cryptographic -((certificate)) issued by a certificate authority that the browser -recognizes. Next, all data going over the ((connection)) is encrypted -in a way that should prevent eavesdropping and tampering. - -Thus, when it works right, ((HTTPS)) prevents other people from -impersonating the website you are trying to talk to and from -snooping on your communication. It is not perfect, and there have been -various incidents where HTTPS failed because of forged or stolen -certificates and broken software, but it is a _lot_ safer than plain -HTTP. +The secure ((HTTP)) protocol, used for ((URL))s starting with _https://_, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic ((certificate)) issued by a certificate authority that the browser recognizes. Next, all data going over the ((connection)) is encrypted in a way that should prevent eavesdropping and tampering. + +Thus, when it works right, ((HTTPS)) prevents other people from impersonating the website you are trying to talk to _and_ from snooping on your communication. It's not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a _lot_ safer than plain HTTP. {{id forms}} ## Form fields -Forms were originally designed for the pre-JavaScript Web to allow -web sites to send user-submitted information in an HTTP request. This -design assumes that interaction with the server always happens by -navigating to a new page. +Forms were originally designed for the pre-JavaScript web to allow websites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page. {{index [DOM, fields]}} -But their elements are part of the DOM like the rest of the page, -and the DOM elements that represent form ((field))s support a number -of properties and events that are not present on other elements. These -make it possible to inspect and control such input fields with -JavaScript programs and do things such as adding new functionality to -a form or using forms and fields as building blocks in a JavaScript -application. +However, the form elements are part of the DOM, like the rest of the page, and the DOM elements that represent form ((field))s support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application. {{index "form (HTML tag)"}} -A web form consists of any number of input ((field))s grouped in a -`<form>` tag. HTML allows several different styles of fields, ranging -from simple on/off checkboxes to drop-down menus and fields for text -input. This book won't try to comprehensively discuss all field types, -but we'll start with a rough overview. +A web form consists of any number of input ((field))s grouped in a `<form>` tag. HTML allows several different styles of fields, ranging from simple on/off checkboxes to drop-down menus and fields for text input. This book won't try to comprehensively discuss all field types, but we'll start with a rough overview. {{index "input (HTML tag)", "type attribute"}} -A lot of field types use the -`<input>` tag. This tag's `type` attribute is used to select the -field's style. These are some commonly used `<input>` types: +A lot of field types use the `<input>` tag. This tag's `type` attribute is used to select the field's style. These are some commonly used `<input>` types: {{index "password field", checkbox, "radio button", "file field"}} @@ -502,21 +306,21 @@ field's style. These are some commonly used `<input>` types: | `text` | A single-line ((text field)) | `password` | Same as `text` but hides the text that is typed | `checkbox` | An on/off switch +| `color` | A color +| `date` | A calendar date | `radio` | (Part of) a ((multiple-choice)) field | `file` | Allows the user to choose a file from their computer {{index "value attribute", "checked attribute", "form (HTML tag)"}} -Form fields do not necessarily have to appear in a `<form>` tag. You -can put them anywhere in a page. Such form-less fields cannot be -((submit))ted (only a form as a whole can), but when responding to -input with JavaScript, we often don't want to submit our fields -normally anyway. +Form fields do not necessarily have to appear in a `<form>` tag. You can put them anywhere in a page. Such form-less fields cannot be ((submit))ted (only a form as a whole can), but when responding to input with JavaScript, we often don't want to submit our fields normally anyway. -```{lang: "text/html"} +```{lang: html} <p><input type="text" value="abc"> (text)</p> <p><input type="password" value="abc"> (password)</p> <p><input type="checkbox" checked> (checkbox)</p> +<p><input type="color" value="orange"> (color)</p> +<p><input type="date" value="2023-10-13"> (date)</p> <p><input type="radio" value="A" name="choice"> <input type="radio" value="B" name="choice" checked> <input type="radio" value="C" name="choice"> (radio)</p> @@ -527,22 +331,17 @@ normally anyway. The fields created with this HTML code look like this: -{{figure {url: "img/form_fields.png", alt: "Various types of input tags",width: "4cm"}}} +{{figure {url: "img/form_fields.png", alt: "Screenshot showing various types of input tags", width: "4cm"}}} if}} -The JavaScript interface for such elements differs with the type of -the element. +The JavaScript interface for such elements differs with the type of the element. {{index "textarea (HTML tag)", "text field"}} -Multiline text fields have their own tag, `<textarea>`, mostly because -using an attribute to specify a multiline starting value would be -awkward. The `<textarea>` tag requires a matching `</textarea>` -closing tag and uses the text between those two, instead of the -`value` attribute, as starting text. +Multiline text fields have their own tag, `<textarea>`, mostly because using an attribute to specify a multiline starting value would be awkward. The `<textarea>` tag requires a matching `</textarea>` closing tag and uses the text between those two, instead of the `value` attribute, as starting text. -```{lang: "text/html"} +```{lang: html} <textarea> one two @@ -552,11 +351,9 @@ three {{index "select (HTML tag)", "option (HTML tag)", "multiple choice", "drop-down menu"}} -Finally, the `<select>` tag is used to -create a field that allows the user to select from a number of -predefined options. +Finally, the `<select>` tag is used to create a field that allows the user to select from a number of predefined options. -```{lang: "text/html"} +```{lang: html} <select> <option>Pancakes</option> <option>Pudding</option> @@ -568,14 +365,13 @@ predefined options. Such a field looks like this: -{{figure {url: "img/form_select.png", alt: "A select field", width: "4cm"}}} +{{figure {url: "img/form_select.png", alt: "Screenshot showing a select field", width: "4cm"}}} if}} {{index "change event"}} -Whenever the value of a form field changes, it will fire a `"change"` -event. +Whenever the value of a form field changes, it will fire a `"change"` event. ## Focus @@ -583,26 +379,17 @@ event. {{indexsee "keyboard focus", focus}} -Unlike most elements in HTML documents, form fields can get _keyboard -((focus))_. When clicked 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)"}} -Thus, you can type into a ((text field)) only when it is focused. Other -fields respond differently to keyboard events. For example, a -`<select>` menu tries to move to the option that contains the text the -user typed and responds to the arrow keys by moving its selection up -and down. +Thus, you can type into a ((text field)) only when it is focused. Other fields respond differently to keyboard events. For example, a `<select>` menu tries to move to the option that contains the text the user typed and responds to the arrow keys by moving its selection up and down. {{index "focus method", "blur method", "activeElement property"}} -We can control ((focus)) from JavaScript with the `focus` and `blur` -methods. The first moves focus to the DOM element it is called on, and -the second removes focus. The value in `document.activeElement` -corresponds to the currently focused element. +We can control ((focus)) from JavaScript with the `focus` and `blur` methods. The first moves focus to the DOM element it is called on, and the second removes focus. The value in `document.activeElement` corresponds to the currently focused element. -```{lang: "text/html"} +```{lang: html} <input type="text"> <script> document.querySelector("input").focus(); @@ -616,84 +403,55 @@ corresponds to the currently focused element. {{index "autofocus attribute"}} -For some pages, the user is expected to want to interact with a form -field immediately. JavaScript can be used to ((focus)) this field when -the document is loaded, but HTML also provides the `autofocus` -attribute, which produces the same effect while letting the browser -know what we are trying to achieve. This gives the browser the option -to disable the behavior when it is not appropriate, such as when the -user has put the focus on something else. +For some pages, the user is expected to want to interact with a form field immediately. JavaScript can be used to ((focus)) this field when the document is loaded, but HTML also provides the `autofocus` attribute, which produces the same effect while letting the browser know what we are trying to achieve. This gives the browser the option to disable the behavior when it is not appropriate, such as when the user has put the focus on something else. {{index "tab key", keyboard, "tabindex attribute", "a (HTML tag)"}} -Browsers traditionally also allow the user to move the focus -through the document by pressing the [tab]{keyname} key. We can influence the -order in which elements receive focus with the `tabindex` attribute. -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: "text/html", focus: true} +```{lang: html, focus: true} <input type="text" tabindex=1> <a href=".">(help)</a> <button onclick="console.log('ok')" tabindex=2>OK</button> ``` {{index "tabindex attribute"}} -By default, most types of HTML elements cannot be focused. But you can -add a `tabindex` attribute to any element that will make it -focusable. A `tabindex` of -1 makes tabbing skip over an element, even -if it is normally focusable. +By default, most types of HTML elements cannot be focused. You can add a `tabindex` attribute to any element to make it focusable. A `tabindex` of 0 makes an element focusable without affecting the focus order. ## Disabled fields {{index "disabled attribute"}} -All ((form)) ((field))s can be _disabled_ through their `disabled` -attribute. It is an ((attribute)) that can be specified without -value—the fact that it is present at all disables the element. +All ((form)) ((field))s can be _disabled_ through their `disabled` attribute. It is an ((attribute)) that can be specified without value—the fact that it is present at all disables the element. -```{lang: "text/html"} +```{lang: html} <button>I'm all right</button> <button disabled>I'm out</button> ``` -Disabled fields cannot be ((focus))ed or changed, and browsers make -them look gray and faded. +Disabled fields cannot be ((focus))ed or changed, and browsers make them look gray and faded. {{if book -{{figure {url: "img/button_disabled.png", alt: "A disabled button",width: "3cm"}}} +{{figure {url: "img/button_disabled.png", alt: "Screenshot of a disabled button", width: "3cm"}}} if}} {{index "user experience"}} -When a program is -in the process of handling an action caused by some ((button)) or other control -that might require communication with the server and thus take a -while, it can be a good idea to -disable the control until the action finishes. That way, when the user -gets impatient and clicks it again, they don't accidentally repeat -their action. +When a program is in the process of handling an action caused by some ((button)) or other control that might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don't accidentally repeat their action. ## The form as a whole {{index "array-like object", "form (HTML tag)", "form property", "elements property"}} -When a ((field)) is contained in a `<form>` element, its DOM element -will have a `form` property linking back to the form's DOM element. -The `<form>` element, in turn, has a property called `elements` that -contains an array-like collection of the fields inside it. +When a ((field)) is contained in a `<form>` element, its DOM element will have a `form` property linking back to the form's DOM element. The `<form>` element, in turn, has a property called `elements` that contains an array-like collection of the fields inside it. {{index "elements property", "name attribute"}} -The `name` attribute of a form field determines the way its value will -be identified when the form is ((submit))ted. It can also be used as a -property name when accessing the form's `elements` property, which -acts both as an array-like object (accessible by number) and a ((map)) -(accessible by name). +The `name` attribute of a form field determines the way its value will be identified when the form is ((submit))ted. It can also be used as a property name when accessing the form's `elements` property, which acts both as an array-like object (accessible by number) and a ((map)) (accessible by name). -```{lang: "text/html"} +```{lang: html} <form action="example/submit.html"> Name: <input type="text" name="name"><br> Password: <input type="password" name="password"><br> @@ -712,20 +470,14 @@ acts both as an array-like object (accessible by number) and a ((map)) {{index "button (HTML tag)", "type attribute", submit, "enter key"}} -A button with a `type` attribute of `submit` will, when pressed, -cause the form to be submitted. Pressing [enter]{keyname} when a form field is -focused has the same effect. +A button with a `type` attribute of `submit` will, when pressed, cause the form to be submitted. Pressing [enter]{keyname} when a form field is focused has the same effect. {{index "submit event", "event handling", "preventDefault method", "page reload", "GET method", "POST method"}} -Submitting a ((form)) normally means that the ((browser)) navigates to -the page indicated by the form's `action` attribute, using either a -`GET` or a `POST` ((request)). But before that happens, a `"submit"` -event is fired. You can handle this event with JavaScript and prevent -this default behavior by calling `preventDefault` on the event object. +Submitting a ((form)) normally means that the ((browser)) navigates to the page indicated by the form's `action` attribute, using either a `GET` or a `POST` ((request)). But before that happens, a `"submit"` event is fired. You can handle this event with JavaScript and prevent this default behavior by calling `preventDefault` on the event object. -```{lang: "text/html"} -<form action="example/submit.html"> +```{lang: html} +<form> Value: <input type="text" name="value"> <button type="submit">Save</button> </form> @@ -740,49 +492,28 @@ this default behavior by calling `preventDefault` on the event object. {{index "submit event", validation}} -Intercepting `"submit"` events in JavaScript has various uses. We can -write code to verify that the values the user entered make sense and -immediately show an error message instead of submitting the form. Or -we can disable the regular way of submitting the form entirely, as in -the example, and have our program handle the input, possibly using -`fetch` to send it to a server without reloading the page. +Intercepting `"submit"` events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form. Or we can disable the regular way of submitting the form entirely, as in the example, and have our program handle the input, possibly using `fetch` to send it to a server without reloading the page. ## Text fields {{index "value attribute", "input (HTML tag)", "text field", "textarea (HTML tag)", [DOM, fields], [interface, object]}} -Fields created by `<textarea>` tags, or `<input>` tags with a type of -`text` or `password`, share a common interface. Their DOM -elements have a `value` property that holds their current content as a -string value. Setting this property to another string changes the -field's content. +Fields created by `<textarea>` tags, or `<input>` tags with a type of `text` or `password`, share a common interface. Their DOM elements have a `value` property that holds their current content as a string value. Setting this property to another string changes the field's content. {{index "selectionStart property", "selectionEnd property"}} -The -`selectionStart` and `selectionEnd` properties of ((text field))s give -us information about the ((cursor)) and ((selection)) in the ((text)). -When nothing is selected, these two properties hold the same number, -indicating the position of the cursor. For example, 0 indicates the -start of the text, and 10 indicates the cursor is after the 10^th^ ((character)). -When part of the field is selected, the two properties will differ, giving us the -start and end of the selected text. Like `value`, these properties may -also be written to. +The `selectionStart` and `selectionEnd` properties of ((text field))s give us information about the ((cursor)) and ((selection)) in the ((text)). When nothing is selected, these two properties hold the same number, indicating the position of the cursor. For example, 0 indicates the start of the text, and 10 indicates the cursor is after the 10^th^ ((character)). When part of the field is selected, the two properties will differ, giving us the start and end of the selected text. Like `value`, these properties may also be written to. {{index Khasekhemwy, "textarea (HTML tag)", keyboard, "event handling"}} -Imagine you are writing an article about Khasekhemwy but have some -trouble spelling his name. The following code wires up a `<textarea>` -tag with an event handler that, when you press F2, inserts the string -"Khasekhemwy" for you. +Imagine you are writing an article about Khasekhemwy, last pharaoh of the Second Dynasty, but have some trouble spelling his name. The following code wires up a `<textarea>` tag with an event handler that, when you press F2, inserts the string "Khasekhemwy" for you. -```{lang: "text/html"} +```{lang: html} <textarea></textarea> <script> let textarea = document.querySelector("textarea"); textarea.addEventListener("keydown", event => { - // The key code for F2 happens to be 113 - if (event.keyCode == 113) { + if (event.key == "F2") { replaceSelection(textarea, "Khasekhemwy"); event.preventDefault(); } @@ -800,25 +531,15 @@ tag with an event handler that, when you press F2, inserts the string {{index "replaceSelection function", "text field"}} -The `replaceSelection` -function replaces the currently selected part of a text field's -content with the given word and then moves the ((cursor)) after that -word so that the user can continue typing. +The `replaceSelection` function replaces the currently selected part of a text field's content with the given word and then moves the ((cursor)) after that word so that the user can continue typing. {{index "change event", "input event"}} -The `"change"` event for a ((text -field)) does not fire every time something is typed. Rather, it -fires when the field loses ((focus)) after its content was changed. -To respond immediately to changes in a text field, you should register -a handler for the `"input"` event instead, which fires for every -time the user types a character, deletes text, or otherwise manipulates -the field's content. +The `"change"` event for a ((text field)) does not fire every time something is typed. Rather, it fires when the field loses ((focus)) after its content was changed. To respond immediately to changes in a text field, you should register a handler for the `"input"` event instead, which fires every time the user types a character, deletes text, or otherwise manipulates the field's content. -The following example shows a text field and a counter displaying the -current length of the text in the field: +The following example shows a text field and a counter displaying the current length of the text in the field: -```{lang: "text/html"} +```{lang: html} <input type="text"> length: <span id="length">0</span> <script> let text = document.querySelector("input"); @@ -833,10 +554,9 @@ current length of the text in the field: {{index "input (HTML tag)", "checked attribute"}} -A ((checkbox)) field is a binary toggle. Its value can be extracted or -changed through its `checked` property, which holds a Boolean value. +A ((checkbox)) field is a binary toggle. Its value can be extracted or changed through its `checked` property, which holds a Boolean value. -```{lang: "text/html"} +```{lang: html} <label> <input type="checkbox" id="purple"> Make this page purple </label> @@ -851,18 +571,13 @@ changed through its `checked` property, which holds a Boolean value. {{index "for attribute", "id attribute", focus, "label (HTML tag)", labeling}} -The `<label>` tag associates a piece of document with an input -((field)). Clicking anywhere on the label will activate the field, -which focuses it and toggles its value when it is a checkbox or radio -button. +The `<label>` tag associates a piece of document with an input ((field)). Clicking anywhere on the label will activate the field, which focuses it and toggles its value when it is a checkbox or radio button. {{index "input (HTML tag)", "multiple-choice"}} -A ((radio button)) is similar to a checkbox, but it's implicitly -linked to other radio buttons with the same `name` attribute so that -only one of them can be active at any time. +A ((radio button)) is similar to a checkbox, but it's implicitly linked to other radio buttons with the same `name` attribute so that only one of them can be active at any time. -```{lang: "text/html"} +```{lang: html} Color: <label> <input type="radio" name="color" value="orange"> Orange @@ -885,53 +600,31 @@ Color: {{index "name attribute", "querySelectorAll method"}} -The ((square brackets)) in the CSS query given to `querySelectorAll` -are used to match attributes. It selects elements whose `name` -attribute is `"color"`. +The ((square brackets)) in the CSS query given to `querySelectorAll` are used to match attributes. It selects elements whose `name` attribute is `"color"`. ## Select fields {{index "select (HTML tag)", "multiple-choice", "option (HTML tag)"}} -Select fields are conceptually similar to radio buttons—they -also allow the user to choose from a set of options. But where a radio -button puts the layout of the options under our control, the -appearance of a `<select>` tag is determined by the browser. +Select fields are conceptually similar to radio buttons—they also allow the user to choose from a set of options. But where a radio button puts the layout of the options under our control, the appearance of a `<select>` tag is determined by the browser. {{index "multiple attribute", "drop-down menu"}} -Select fields also have a variant that is more akin to a list of -checkboxes, rather than radio boxes. When given the `multiple` -attribute, a `<select>` tag will allow the user to select any number -of options, rather than just a single option. This will, in most -browsers, show up differently than a normal select field, which is -typically drawn as a _drop-down_ control that shows the options only -when you open it. +Select fields also have a variant more akin to a list of checkboxes rather than radio boxes. When given the `multiple` attribute, a `<select>` tag will allow the user to select any number of options, rather than just a single option. Whereas a regular select field is drawn as a _drop-down_ control, which shows the inactive options only when you open it, a field with `multiple` enabled shows multiple options at the same time, allowing the user to enable or disable them individually. {{index "option (HTML tag)", "value attribute"}} -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"}} -The `<option>` tags for a `<select>` field can be accessed as an -array-like object through the field's `options` property. Each option -has a property called `selected`, which indicates whether that option -is currently selected. The property can also be written to select or -deselect an option. +The `<option>` tags for a `<select>` field can be accessed as an array-like object through the field's `options` property. Each option has a property called `selected`, which indicates whether that option is currently selected. The property can also be written to select or deselect an option. {{index "multiple attribute", "binary number"}} -This example extracts the selected values from a `multiple` select -field and uses them to compose a binary number from individual bits. -Hold [control]{keyname} (or [command]{keyname} on a Mac) to select multiple options. +This example extracts the selected values from a `multiple` select field and uses them to compose a binary number from individual bits. Hold [ctrl]{keyname} (or [command]{keyname} on a Mac) to select multiple options. -```{lang: "text/html"} +```{lang: html} <select multiple> <option value="1">0001</option> <option value="2">0010</option> @@ -955,21 +648,13 @@ Hold [control]{keyname} (or [command]{keyname} on a Mac) to select multiple opti ## 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. +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. -A file field usually looks like a button labeled with something like -"choose file" or "browse", with information about the chosen file next -to it. +A file field usually looks like a button labeled with something like "choose file" or "browse", with information about the chosen file next to it. -```{lang: "text/html"} +```{lang: html} <input type="file"> <script> let input = document.querySelector("input"); @@ -985,30 +670,19 @@ to it. {{index "multiple attribute", "files property"}} -The `files` property of a -((file field)) element is an ((array-like object)) (again, not a real -array) containing the files chosen in the field. It is initially -empty. The reason there isn't simply a `file` property is that file -fields also support a `multiple` attribute, which makes it possible to -select multiple files at the same time. +The `files` property of a ((file field)) element is an ((array-like object)) (once again, not a real array) containing the files chosen in the field. It is initially empty. The reason there isn't simply a `file` property is that file fields also support a `multiple` attribute, which makes it possible to select multiple files at the same time. {{index "File type"}} -Objects in the `files` object have properties such as `name` (the -filename), `size` (the file's size in bytes, which are chunks of 8 -bits), and `type` (the media type of the file, such as `text/plain` or -`image/jpeg`). +The objects in `files` have properties such as `name` (the filename), `size` (the file's size in bytes, which are chunks of 8 bits), and `type` (the media type of the file, such as `text/plain` or `image/jpeg`). {{index ["asynchronous programming", "reading files"], "file reading", "FileReader class"}} {{id filereader}} -What it does not have is a property that contains the content of the -file. Getting at that is a little more involved. Since reading a file -from disk can take time, the interface must be asynchronous to avoid -freezing the document. +What it does not have is a property that contains the content of the file. Getting at that is a little more involved. Since reading a file from disk can take time, the interface is asynchronous to avoid freezing the window. -```{lang: "text/html"} +```{lang: html} <input type="file" multiple> <script> let input = document.querySelector("input"); @@ -1027,17 +701,11 @@ freezing the document. {{index "FileReader class", "load event", "readAsText method", "result property"}} -Reading a file is done by creating a `FileReader` object, registering -a `"load"` event handler for it, and calling its `readAsText` method, -giving it the file we want to read. Once loading finishes, the -reader's `result` property contains the file's content. +Reading a file is done by creating a `FileReader` object, registering a `"load"` event handler for it, and calling its `readAsText` method, giving it the file we want to read. Once loading finishes, the reader's `result` property contains the file's content. {{index "error event", "FileReader class", "Promise class"}} -`FileReader`s also fire an `"error"` event when reading the file fails -for any reason. The error object itself will end up in the reader's -`error` property. This interface was designed before promises became -part of the language. You could wrap it in a promise like this: +`FileReader`s also fire an `"error"` event when reading the file fails for any reason. The error object itself will end up in the reader's `error` property. This interface was designed before promises became part of the language. You could wrap it in a promise like this: ``` function readFileText(file) { @@ -1056,27 +724,15 @@ function readFileText(file) { {{index "web application"}} -Simple ((HTML)) pages with a bit of JavaScript can be a great format -for "((mini application))s"—small helper programs that automate basic -tasks. By connecting a few form ((field))s with event handlers, you -can do anything from converting between centimeters and inches to -computing passwords from a master password and a website name. +Simple ((HTML)) pages with a bit of JavaScript can be a great format for "((mini application))s"—small helper programs that automate basic tasks. By connecting a few form ((field))s with event handlers, you can do anything from converting between centimeters and inches to computing passwords from a master password and a website name. {{index persistence, [binding, "as state"], [browser, storage]}} -When such an application needs to remember something between sessions, -you cannot use JavaScript bindings—those are thrown away every -time the page is closed. You could set up a server, connect it to the -Internet, and have your application store something there. We will see -how to do that in [Chapter ?](node). But that's a lot of extra work -and complexity. Sometimes it is enough to just keep the data in the -((browser)). +When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the internet, and have your application store something there (we'll see how to do that in [Chapter ?](node)). But that's a lot of extra work and complexity. Sometimes it's enough to just keep the data in the ((browser)). {{index "localStorage object", "setItem method", "getItem method", "removeItem method"}} -The `localStorage` object can be used to store data in a way that -survives ((page reload))s. This object allows you to file string -values under names. +The `localStorage` object can be used to store data in a way that survives ((page reload))s. This object allows you to file string values under names. ``` localStorage.setItem("username", "marijn"); @@ -1087,30 +743,21 @@ localStorage.removeItem("username"); {{index "localStorage object"}} -A value in `localStorage` sticks around until it is overwritten, it is -removed with `removeItem`, or the user clears their local data. +A value in `localStorage` sticks around until it is overwritten or is removed with `removeItem`, or the user clears their local data. {{index security}} -Sites from different ((domain))s get different storage -compartments. That means data stored in `localStorage` by a given -website can, in principle, be read (and overwritten) only by scripts on -that same site. +Sites from different ((domain))s get different storage compartments. That means data stored in `localStorage` by a given website can, in principle, be read (and overwritten) only by scripts on that same site. {{index "localStorage object"}} -Browsers do enforce a limit on the size of the data a site can store -in `localStorage`. That restriction, along with the fact that filling -up people's ((hard drive))s with junk is not really profitable, -prevents the feature from eating up too much space. +Browsers do enforce a limit on the size of the data a site can store in `localStorage`. That restriction, along with the fact that filling up people's ((hard drive))s with junk is not really profitable, prevents the feature from eating up too much space. {{index "localStorage object", "note-taking example", "select (HTML tag)", "button (HTML tag)", "textarea (HTML tag)"}} -The following code implements a crude note-taking application. It -keeps a set of named notes and allows the user to edit notes and -create new ones. +The following code implements a crude note-taking application. It keeps a set of named notes and allows the user to edit notes and create new ones. -```{lang: "text/html", startCode: true} +```{lang: html, startCode: true} Notes: <select></select> <button>Add</button><br> <textarea style="width: 100%"></textarea> @@ -1132,7 +779,7 @@ Notes: <select></select> <button>Add</button><br> localStorage.setItem("Notes", JSON.stringify(newState)); state = newState; } - setState(JSON.parse(localStorage.getItem("Notes")) || { + setState(JSON.parse(localStorage.getItem("Notes")) ?? { notes: {"shopping list": "Carrots\nRaisins"}, selected: "shopping list" }); @@ -1141,65 +788,42 @@ Notes: <select></select> <button>Add</button><br> setState({notes: state.notes, selected: list.value}); }); note.addEventListener("change", () => { + let {selected} = state; setState({ - notes: Object.assign({}, state.notes, - {[state.selected]: note.value}), - selected: state.selected + notes: {...state.notes, [selected]: note.value}, + selected }); }); document.querySelector("button") .addEventListener("click", () => { let name = prompt("Note name"); if (name) setState({ - notes: Object.assign({}, state.notes, {[name]: ""}), + notes: {...state.notes, [name]: ""}, selected: name }); }); </script> ``` -{{index "getItem method", JSON, "|| operator", "default value"}} +{{index "getItem method", JSON, "?? operator", "default value"}} -The script gets its starting state from the `"Notes"` value stored in -`localStorage` or, if that is missing, creates an example state -that has only a shopping list in it. Reading a field that does not -exist from `localStorage` will yield `null`. Passing `null` to -`JSON.parse` will make it parse the string `"null"` and return `null`. -Thus, the `||` operator can be used to provide a default value in a -situation like this. +The script gets its starting state from the `"Notes"` value stored in `localStorage` or, if that's missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from `localStorage` will yield `null`. Passing `null` to `JSON.parse` will make it parse the string `"null"` and return `null`. Thus, the `??` operator can be used to provide a default value in a situation like this. -The `setState` method makes sure the DOM is showing a given state and -stores the new state to `localStorage`. Event handlers call this -function to move to a new state. +The `setState` method makes sure the DOM is showing a given state and stores the new state to `localStorage`. Event handlers call this function to move to a new state. -{{index "Object.assign function", [object, creation], property, "computed property"}} +{{index [object, creation], property, "computed property"}} -The use of `Object.assign` in the example is intended to create a new -object that is a clone of the old `state.notes`, but with one property -added or overwritten. `Object.assign` takes its first argument and -adds all properties from any further arguments to it. Thus, giving it -an empty object will cause it to fill a fresh object. The ((square -brackets)) notation in the third argument is used to create a property -whose name is based on some dynamic value. +The `...` syntax in the example is used to create a new object that is a clone of the old `state.notes`, but with one property added or overwritten. It uses ((spread)) syntax to first add the properties from the old object and then set a new property. The ((square brackets)) notation in the object literal is used to create a property whose name is based on some dynamic value. {{index "sessionStorage object", [browser, storage]}} -There is another object, similar to `localStorage`, called -`sessionStorage`. The difference between the two is that the content -of `sessionStorage` is forgotten at the end of each _((session))_, -which for most browsers means whenever the browser is closed. +There is another object, similar to `localStorage`, called `sessionStorage`. The difference between the two is that the content of `sessionStorage` is forgotten at the end of each _((session))_, which for most browsers means whenever the browser is closed. ## Summary -In this chapter, we discussed how the HTTP protocol works. A _client_ -sends a request, which contains a method (usually `GET`) and a path -that identifies a resource. The _server_ then decides what to do with -the request and responds with a status code and a response body. Both -requests and responses may contain headers that provide additional -information. +In this chapter, we discussed how the HTTP protocol works. A _client_ sends a request, which contains a method (usually `GET`) and a path that identifies a resource. The _server_ then decides what to do with the request and responds with a status code and a response body. Both requests and responses may contain headers that provide additional information. -The interface through which browser JavaScript can make HTTP requests -is called `fetch`. Making a request looks like this: +The interface through which browser JavaScript can make HTTP requests is called `fetch`. Making a request looks like this: ``` fetch("/18_http.html").then(r => r.text()).then(text => { @@ -1207,34 +831,15 @@ fetch("/18_http.html").then(r => r.text()).then(text => { }); ``` -Browsers make `GET` requests to fetch the resources needed to display -a web page. A page may also contain forms, which allow information -entered by the user to be sent as a request for a new page when the -form is submitted. - -HTML can represent various types of form fields, such as text fields, -checkboxes, multiple-choice fields, and file pickers. +Browsers make `GET` requests to fetch the resources needed to display a web page. A page may also contain forms, which allow information entered by the user to be sent as a request for a new page when the form is submitted. -Such fields can be inspected and manipulated with JavaScript. They -fire the `"change"` event when changed, fire the `"input"` event when text -is typed, and receive keyboard events when they have keyboard focus. -Properties like `value` (for text and select fields) or `checked` (for -checkboxes and radio buttons) are used to read or set the field's -content. +HTML can represent various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers. Such fields can be inspected and manipulated with JavaScript. They fire the `"change"` event when changed, fire the `"input"` event when text is typed, and receive keyboard events when they have keyboard focus. Properties like `value` (for text and select fields) or `checked` (for checkboxes and radio buttons) are used to read or set the field's content. -When a form is submitted, a `"submit"` event is fired on it. A -JavaScript handler can call `preventDefault` on that event to disable -the browser's default behavior. Form field elements may also occur -outside of a form tag. +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. +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. ## Exercises @@ -1242,29 +847,17 @@ saves it until the browser is closed. {{index "Accept header", "media type", "document format", "content negotiation (exercise)"}} -One of the things HTTP can do is called _content negotiation_. -The `Accept` request header is used to tell the server what type of -document the client would like to get. Many servers ignore this -header, but when a server knows of various ways to encode a resource, -it can look at this header and send the one that the client prefers. +One of the things HTTP can do is called _content negotiation_. The `Accept` request header is used to tell the server what type of document the client would like to get. Many servers ignore this header, but when a server knows of various ways to encode a resource, it can look at this header and send the one that the client prefers. {{index "MIME type"}} -The URL -[_https://eloquentjavascript.net/author_](https://eloquentjavascript.net/author) -is configured to respond with either plaintext, HTML, or JSON, -depending on what the client asks for. These formats are identified by -the standardized _((media type))s_ `text/plain`, `text/html`, and -`application/json`. +The URL [_https://eloquentjavascript.net/author_](https://eloquentjavascript.net/author) is configured to respond with either plaintext, HTML, or JSON, depending on what the client asks for. These formats are identified by the standardized _((media type))s_ `text/plain`, `text/html`, and `application/json`. {{index "headers property", "fetch function"}} -Send requests to fetch all three formats of this resource. Use the -`headers` property in the options object passed to `fetch` to set the -header named `Accept` to the desired media type. +Send requests to fetch all three formats of this resource. Use the `headers` property in the options object passed to `fetch` to set the header named `Accept` to the desired media type. -Finally, try asking for the media type `application/rainbows+unicorns` -and see which status code that produces. +Finally, try asking for the media type `application/rainbows+unicorns` and see which status code that produces. {{if interactive @@ -1278,14 +871,11 @@ if}} {{index "content negotiation (exercise)"}} -Base your code on the `fetch` examples [earlier in the -chapter](http#fetch). +Base your code on the `fetch` examples [earlier in the chapter](http#fetch). {{index "406 (HTTP status code)", "Accept header"}} -Asking for a bogus media type will return a response with code 406, -"Not acceptable", which is the code a server should return when it -can't fulfill the `Accept` header. +Asking for a bogus media type will return a response with code 406, "Not acceptable", which is the code a server should return when it can't fulfill the `Accept` header. hint}} @@ -1293,20 +883,15 @@ 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"}} -Put a button next to a `<textarea>` field that, when pressed, uses -the `Function` constructor we saw in [Chapter ?](modules#eval) to wrap -the text in a function and call it. Convert the return value of the -function, or any error it raises, to a string and display it below the -text field. +Put a button next to a `<textarea>` field that, when pressed, uses the `Function` constructor we saw in [Chapter ?](modules#eval) to wrap the text in a function and call it. Convert the return value of the function, or any error it raises, to a string and display it below the text field. {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <textarea id="code">return "hi";</textarea> <button id="button">Run</button> <pre id="output"></pre> @@ -1322,25 +907,15 @@ if}} {{index "click event", "mousedown event", "Function constructor", "workbench (exercise)"}} -Use `document.querySelector` or `document.getElementById` to get -access to the elements defined in your HTML. An event handler for -`"click"` or `"mousedown"` events on the button can get the `value` -property of the text field and call `Function` on it. +Use `document.querySelector` or `document.getElementById` to get access to the elements defined in your HTML. An event handler for `"click"` or `"mousedown"` events on the button can get the `value` property of the text field and call `Function` on it. {{index "try keyword", "exception handling"}} -Make sure you wrap both the call to `Function` and the call to its -result in a `try` block so you can catch the exceptions it -produces. In this case, we really don't know what type of exception we -are looking for, so catch everything. +Make sure you wrap both the call to `Function` and the call to its result in a `try` block so you can catch the exceptions it produces. In this case, we really don't know what type of exception we are looking for, so catch everything. {{index "textContent property", output, text, "createTextNode method", "newline character"}} -The `textContent` property of the output element can be used to fill -it with a string message. Or, if you want to keep the old content -around, create a new text node using `document.createTextNode` and -append it to the element. Remember to add a newline character to the -end so that not all output appears on a single line. +The `textContent` property of the output element can be used to fill it with a string message. Or, if you want to keep the old content around, create a new text node using `document.createTextNode` and append it to the element. Remember to add a newline character to the end so that not all output appears on a single line. hint}} @@ -1348,41 +923,27 @@ hint}} {{index "game of life (exercise)", "artificial life", "Conway's Game of Life"}} -Conway's Game of Life is a simple ((simulation)) that creates -artificial "life" on a ((grid)), each cell of which is either alive or -not. Each ((generation)) (turn), the following rules are applied: +Conway's Game of Life is a simple ((simulation)) that creates artificial "life" on a ((grid)), each cell of which is either alive or not. In each ((generation)) (turn), the following rules are applied: -* Any live ((cell)) with fewer than two or more than three live - ((neighbor))s dies. +* Any live ((cell)) with fewer than two or more than three live ((neighbor))s dies. -* Any live cell with two or three live neighbors lives on to the next - generation. +* Any live cell with two or three live neighbors lives on to the next generation. * Any dead cell with exactly three live neighbors becomes a live cell. -A _neighbor_ is defined as any adjacent cell, including diagonally -adjacent ones. +A _neighbor_ is defined as any adjacent cell, including diagonally adjacent ones. {{index "pure function"}} -Note that these rules are applied to the whole grid at once, not one -square at a time. That means the counting of neighbors is based on the -situation at the start of the generation, and changes happening to -neighbor cells during this generation should not influence the new -state of a given cell. +Note that these rules are applied to the whole grid at once, not one square at a time. That means the counting of neighbors is based on the situation at the start of the generation, and changes happening to neighbor cells during this generation should not influence the new state of a given cell. {{index "Math.random function"}} -Implement this game using whichever ((data structure)) you find -appropriate. Use `Math.random` to populate the grid with a random -pattern initially. Display it as a grid of ((checkbox)) ((field))s, -with a ((button)) next to it to advance to the next ((generation)). -When the user checks or unchecks the checkboxes, their changes should -be included when computing the next generation. +Implement this game using whichever ((data structure)) you find appropriate. Use `Math.random` to populate the grid with a random pattern initially. Display it as a grid of ((checkbox)) ((field))s, with a ((button)) next to it to advance to the next ((generation)). When the user checks or unchecks the checkboxes, their changes should be included when computing the next generation. {{if interactive -```{lang: "text/html", test: no} +```{lang: html, test: no} <div id="grid"></div> <button id="next">Next generation</button> @@ -1397,33 +958,18 @@ if}} {{index "game of life (exercise)"}} -To solve the problem of having the changes conceptually happen at the -same time, try to see the computation of a ((generation)) as a ((pure -function)), which takes one ((grid)) and produces a new grid that -represents the next turn. +To solve the problem of having the changes conceptually happen at the same time, try to see the computation of a ((generation)) as a ((pure function)), which takes one ((grid)) and produces a new grid that represents the next turn. -Representing the matrix can be done in the way shown in [Chapter -?](object#matrix). You can count live ((neighbor))s with two nested -loops, looping over adjacent coordinates in both dimensions. Take care -not to count cells outside of the field and to ignore the cell in the -center, whose neighbors we are counting. +Representing the matrix can be done with a single array of width × height elements, storing values row by row, so, for example, the third element in the fifth row is (using zero-based indexing) stored at position 4 × _width_ + 2. You can count live ((neighbor))s with two nested loops, looping over adjacent coordinates in both dimensions. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting. {{index "event handling", "change event"}} -Ensuring that changes to ((checkbox))es take effect on the next generation -can be done in two ways. An event handler could notice these changes -and update the current grid to reflect them, or you could generate a -fresh grid from the values in the checkboxes before computing the next -turn. +Ensuring that changes to ((checkbox))es take effect on the next generation can be done in two ways. An event handler could notice these changes and update the current grid to reflect them, or you could generate a fresh grid from the values in the checkboxes before computing the next turn. -If you choose to go with event handlers, you might want to attach -((attribute))s that identify the position that each checkbox -corresponds to so that it is easy to find out which cell to change. +If you choose to go with event handlers, you might want to attach ((attribute))s that identify the position that each checkbox corresponds to so that it is easy to find out which cell to change. {{index drawing, "table (HTML tag)", "br (HTML tag)"}} -To draw the grid of checkboxes, you can either use a `<table>` element -(see [Chapter ?](dom#exercise_table)) or simply put them all in the -same element and put `<br>` (line break) elements between the rows. +To draw the grid of checkboxes, you can either use a `<table>` element (see [Chapter ?](dom#exercise_table)) or simply put them all in the same element and put `<br>` (line break) elements between the rows. hint}} diff --git a/19_paint.md b/19_paint.md index 3bc43f266..339378e5f 100644 --- a/19_paint.md +++ b/19_paint.md @@ -2,143 +2,79 @@ # Project: A Pixel Art Editor -{{quote {author: "Joan Miro", chapter: true} +{{quote {author: "Joan Miró", chapter: true} -I look at the many colors before me. I look at my blank canvas. Then, -I try to apply colors like words that shape poems, like notes that -shape music. +I look at the many colors before me. I look at my blank canvas. Then, I try to apply colors like words that shape poems, like notes that shape music. quote}} -{{index "Miro, Joan", "drawing program example", "project chapter"}} +{{index "Miró, Joan", "drawing program example", "project chapter"}} -{{figure {url: "img/chapter_picture_19.jpg", alt: "Picture of a tiled mosaic", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_19.jpg", alt: "Illustration showing a mosaic of black tiles, with jars of other tiles next to it", chapter: "framed"}}} -The material from the previous chapters gives you all the elements you -need to build a basic ((web application)). In this chapter, we will do -just that. +The material from the previous chapters gives you all the elements you need to build a basic ((web application)). In this chapter, we will do just that. {{index [file, image]}} -Our ((application)) will be a ((pixel)) ((drawing)) program, where you -can modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, -scribble on them with your mouse or other pointer device, and save -them. This is what it will look like: +Our ((application)) will be a ((pixel))-((drawing)) program that allows you to modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, scribble on them with your mouse or other pointer device, and save them. This is what it will look like: -{{figure {url: "img/pixel_editor.png", alt: "The pixel editor interface, with colored pixels at the top and a number of controls below that", width: "8cm"}}} +{{figure {url: "img/pixel_editor.png", alt: "Screenshot of the pixel editor interface, with a grid of colored pixels at the top and a number of controls, in the form of HTML fields and buttons, below that", width: "8cm"}}} -Painting on a computer is great. You don't need to worry about -materials, ((skill)), or talent. You just start smearing. +Painting on a computer is great. You don't need to worry about materials, ((skill)), or talent. You just start smearing and see where you end up. ## Components {{index drawing, "select (HTML tag)", "canvas (HTML tag)", component}} -The interface for the application shows a big `<canvas>` element on -top, with a number of form ((field))s below it. The user draws on the -((picture)) by selecting a tool from a `<select>` field and then -clicking, ((touch))ing, or ((dragging)) across the canvas. There are -((tool))s for drawing single pixels or rectangles, for filling an -area, and for picking a ((color)) from the picture. +The interface for the application shows a big `<canvas>` element on top, with a number of form ((field))s below it. The user draws on the ((picture)) by selecting a tool from a `<select>` field and then clicking, ((touch))ing, or ((dragging)) across the canvas. There are ((tool))s for drawing single pixels or rectangles, for filling an area, and for picking a ((color)) from the picture. {{index [DOM, components]}} -We will structure the editor interface as a number of -_((component))s_, objects that are responsible for a piece of the -DOM and that may contain other components inside them. +We will structure the editor interface as a number of _((component))s_, objects that are responsible for a piece of the DOM and that may contain other components inside them. {{index [state, "of application"]}} -The state of the application consists of the current picture, the -selected tool, and the selected color. We'll set things up so that the -state lives in a single value, and the interface components always -base the way they look on the current state. - -To see why this is important, let's consider the -alternative—distributing pieces of state throughout the interface. Up -to a certain point, this is easier to program. We can just put in a -((color field)) and read its value when we need to know the current -color. - -But then we add the ((color picker))—a tool that lets you click the -picture to select the color of a given pixel. To keep the -color field showing the correct color, that tool would have to know -that it exists and update it whenever it picks a new color. If -you ever add another place that makes the color visible (maybe the -mouse cursor could show it), you have to update your -color-changing code to keep that synchronized. +The state of the application consists of the current picture, the selected tool, and the selected color. We'll set things up so that the state lives in a single value and the interface components always base the way they look on the current state. + +To see why this is important, let's consider the alternative—distributing pieces of state throughout the interface. Up to a certain point, this is easier to program. We can just put in a ((color field)) and read its value when we need to know the current color. + +But then we add the ((color picker))—a tool that lets you click the picture to select the color of a given pixel. To keep the color field showing the correct color, that tool would have to know that the color field exists and update it whenever it picks a new color. If you ever add another place that makes the color visible (maybe the mouse cursor could show it), you have to update your color-changing code to keep that synchronized as well. {{index modularity}} -In effect, this creates a problem where each part of the interface -needs to know about all other parts, which is not very modular. For -small applications like the one in this chapter, that may not be a -problem. For bigger projects, it can turn into a real nightmare. +In effect, this creates a problem where each part of the interface needs to know about all other parts, which is not very modular. For small applications like the one in this chapter, that may not be a problem. For bigger projects, it can turn into a real nightmare. -To avoid this nightmare on principle, we're going to be strict -about _((data flow))_. There is a state, and the interface is drawn -based on that state. An interface component may respond to user -actions by updating the state, at which point the components get a -chance to synchronize themselves with this new state. +To avoid this nightmare on principle, we're going to be strict about _((data flow))_. There is a state, and the interface is drawn based on that state. An interface component may respond to user actions by updating the state, at which point the components get a chance to synchronize themselves with this new state. {{index library, framework}} -In practice, each ((component)) is set up so that when it is given a -new state, it also notifies its child components, insofar as those -need to be updated. Setting this up is a bit of a hassle. Making this -more convenient is the main selling point of many browser programming -libraries. But for a small application like this, we can do it without -such infrastructure. +In practice, each ((component)) is set up so that when it is given a new state, it also notifies its child components, insofar as those need to be updated. Setting this up is a bit of a hassle. Making this more convenient is the main selling point of many browser programming libraries. But for a small application like this, we can do it without such infrastructure. {{index [state, transitions]}} -Updates to the state are represented as objects, which we'll call -_((action))s_. Components may create such actions and _((dispatch))_ -them—give them to a central state management function. That function -computes the next state, after which the interface components update -themselves to this new state. +Updates to the state are represented as objects, which we'll call _((action))s_. Components may create such actions and _((dispatch))_ them—give them to a central state management function. That function computes the next state, after which the interface components update themselves to this new state. {{index [DOM, components]}} -We're taking the messy task of running a ((user interface)) and -applying some ((structure)) to it. Though the DOM-related pieces -are still full of ((side effect))s, they are held up by a conceptually -simple backbone: the state update cycle. The state determines what the -DOM looks like, and the only way DOM events can change the state is by -dispatching actions to the state. +We're taking the messy task of running a ((user interface)) and applying ((structure)) to it. Though the DOM-related pieces are still full of ((side effect))s, they are held up by a conceptually simple backbone: the state update cycle. The state determines what the DOM looks like, and the only way DOM events can change the state is by dispatching actions to the state. {{index "data flow"}} -There are _many_ variants of this approach, each with its own -benefits and problems, but their central idea is the same: state -changes should go through a single well-defined channel, not happen -all over the place. +There are _many_ variants of this approach, each with its own benefits and problems, but their central idea is the same: state changes should go through a single well-defined channel, not happen all over the place. {{index "dom property", [interface, object]}} -Our ((component))s will be ((class))es conforming to an interface. -Their constructor is given a state—which may be the whole application -state or some smaller value if it doesn't need access to everything—and -uses that to build up a `dom` property. This is the DOM element that represents -the component. Most constructors will also take some other values -that won't change over time, such as the function they can use to -((dispatch)) an action. +Our ((component))s will be ((class))es conforming to an interface. Their constructor is given a state—which may be the whole application state or some smaller value if it doesn't need access to everything—and uses that to build up a `dom` property. This is the DOM element that represents the component. Most constructors will also take some other values that won't change over time, such as the function they can use to ((dispatch)) an action. {{index "syncState method"}} -Each component has a `syncState` method that is used to synchronize it -to a new state value. The method takes one argument, the state, which -is of the same type as the first argument to its constructor. +Each component has a `syncState` method that is used to synchronize it to a new state value. The method takes one argument, the state, which is of the same type as the first argument to its constructor. ## The state -{{index "Picture class", "picture property", "tool property", "color property", "Matrix class"}} +{{index "Picture class", "picture property", "tool property", "color property"}} -The application state will be an object with `picture`, `tool`, and -`color` properties. The picture is itself an object that stores the -width, height, and pixel content of the picture. The ((pixel))s are -stored in an array, in the same way as the matrix class from [Chapter -?](object)—row by row, from top to bottom. +The application state will be an object with `picture`, `tool`, and `color` properties. The picture is itself an object that stores the width, height, and pixel content of the picture. The ((pixel))s are stored in a single array, row by row, from top to bottom. ```{includeCode: true} class Picture { @@ -166,77 +102,41 @@ class Picture { {{index "side effect", "persistent data structure"}} -We want to be able to treat a picture as an ((immutable)) value, for -reasons that we'll get back to later in the chapter. But we also -sometimes need to update a whole bunch of pixels at a time. To be able -to do that, the class has a `draw` method that expects an array of -updated pixels—objects with `x`, `y`, and `color` properties—and -creates a new picture with those pixels overwritten. This method uses -`slice` without arguments to copy the entire pixel array—the start of -the slice defaults to 0, and the end defaults to the array's length. +We want to be able to treat a picture as an ((immutable)) value, for reasons we'll get back to later in the chapter. But we also sometimes need to update a whole bunch of pixels at a time. To be able to do that, the class has a `draw` method that expects an array of updated pixels—objects with `x`, `y`, and `color` properties—and creates a new picture with those pixels overwritten. This method uses `slice` without arguments to copy the entire pixel array—the start of the slice defaults to 0, and the end defaults to the array's length. {{index "Array constructor", "fill method", ["length property", "for array"], [array, creation]}} -The `empty` method uses two pieces of array functionality that we -haven't seen before. The `Array` constructor can be called with a -number to create an empty array of the given length. The `fill` -method can then be used to fill this array with a given value. These -are used to create an array in which all pixels have the same color. +The `empty` method uses two pieces of array functionality that we haven't seen before. The `Array` constructor can be called with a number to create an empty array of the given length. The `fill` method can then be used to fill this array with a given value. These are used to create an array in which all pixels have the same color. {{index "hexadecimal number", "color component", "color field", "fillStyle property"}} -Colors are stored as strings containing traditional ((CSS)) ((color -code))s made up of a ((hash sign)) (`#`) followed by six hexadecimal (base-16) -digits—two for the ((red)) component, two for the ((green)) -component, and two for the ((blue)) component. This is a somewhat -cryptic and inconvenient way to write colors, but it is the format the -HTML color input field uses, and it can be used in the `fillStyle` -property of a canvas drawing context, so for the ways we'll use colors -in this program, it is practical enough. +Colors are stored as strings containing traditional ((CSS)) ((color code))s made up of a ((hash sign)) (`#`) followed by six hexadecimal (base-16) digits—two for the ((red)) component, two for the ((green)) component, and two for the ((blue)) component. This is a somewhat cryptic and inconvenient way to write colors, but it is the format the HTML color input field uses, and it can be used in the `fillStyle` property of a canvas drawing context, so for the ways we'll use colors in this program, it is practical enough. {{index black}} -Black, where all components are zero, is written `"#000000"`, and -bright ((pink)) looks like `"#ff00ff"`, where the red and blue -components have the maximum value of 255, written `ff` in hexadecimal -((digit))s (which use _a_ to _f_ to represent digits 10 to 15). +Black, where all components are zero, is written `"#000000"`, and bright ((pink)) looks like `"#ff00ff"`, where the red and blue components have the maximum value of 255, written `ff` in hexadecimal ((digit))s (which use _a_ to _f_ to represent digits 10 to 15). {{index [state, transitions]}} -We'll allow the interface to ((dispatch)) ((action))s as objects whose -properties overwrite the properties of the previous state. The -color field, when the user changes it, could dispatch an object like -`{color: field.value}`, from which this update function can compute a -new state. +We'll allow the interface to ((dispatch)) ((action))s as objects whose properties overwrite the properties of the previous state. The color field, when the user changes it, could dispatch an object like `{color: field.value}`, from which this update function can compute a new state. {{index "updateState function"}} ```{includeCode: true} function updateState(state, action) { - return Object.assign({}, state, action); + return {...state, ...action}; } ``` -{{index "period character", spread, "Object.assign function"}} +{{index "period character"}} -This rather cumbersome pattern, in which `Object.assign` is used to -first add the properties of `state` to an empty object and then -overwrite some of those with the properties from `action`, is common -in JavaScript code that uses ((immutable)) objects. A more convenient -notation for this, in which the triple-dot operator is used to include -all properties from another object in an object expression, is in the -final stages of being standardized. With that addition, you could -write `{...state, ...action}` instead. At the time of writing, this -doesn't yet work in all browsers. +This pattern, in which object ((spread)) is used to first add the properties an existing object and then override some of those, is common in JavaScript code that uses ((immutable)) objects. ## DOM building {{index "createElement method", "elt function", [DOM, construction]}} -One of the main things that interface components do is creating -DOM structure. We again don't want to directly use the verbose DOM -methods for that, so here's a slightly expanded version of the `elt` -function: +One of the main things that interface components do is create DOM structure. We again don't want to directly use the verbose DOM methods for that, so here's a slightly expanded version of the `elt` function: ```{includeCode: true} function elt(type, props, ...children) { @@ -252,18 +152,13 @@ function elt(type, props, ...children) { {{index "setAttribute method", "attribute", "onclick property", "click event", "event handling"}} -The main difference between this version and the one we used in -[Chapter ?](game#domdisplay) is that it assigns _properties_ to DOM -nodes, not _attributes_. This means we can't use it to set arbitrary -attributes, but we _can_ use it to set properties whose value isn't a -string, such as `onclick`, which can be set to a function to register -a click event handler. +The main difference between this version and the one we used in [Chapter ?](game#domdisplay) is that it assigns _properties_ to DOM nodes, not _attributes_. This means we can't use it to set arbitrary attributes, but we _can_ use it to set properties whose value isn't a string, such as `onclick`, which can be set to a function to register a click event handler. {{index "button (HTML tag)"}} -This allows the following style of registering event handlers: +This allows this convenient style for registering event handlers: -```{lang: "text/html"} +```{lang: html} <body> <script> document.body.appendChild(elt("button", { @@ -275,19 +170,11 @@ This allows the following style of registering event handlers: ## The canvas -The first component we'll define is the part of the interface that -displays the picture as a grid of colored boxes. This component is -responsible for two things: showing a picture and communicating -((pointer event))s on that picture to the rest of the application. +The first component we'll define is the part of the interface that displays the picture as a grid of colored boxes. This component is responsible for two things: showing a picture and communicating ((pointer event))s on that picture to the rest of the application. {{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 knows about only 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; @@ -310,16 +197,11 @@ class PictureCanvas { {{index "syncState method", efficiency}} -We draw each pixel as a 10-by-10 square, as determined by the `scale` -constant. To avoid unnecessary work, the component keeps track of its -current picture and does a redraw only when `syncState` is given a new -picture. +We draw each pixel as a 10-by-10 square, as determined by the `scale` constant. To avoid unnecessary work, the component keeps track of its current picture and does a redraw only when `syncState` is given a new picture. {{index "drawPicture function"}} -The actual drawing function sets the size of the canvas based on the -scale and picture size and fills it with a series of squares, one for -each pixel. +The actual drawing function sets the size of the canvas based on the scale and picture size and fills it with a series of squares, one for each pixel. ```{includeCode: true} function drawPicture(picture, canvas, scale) { @@ -338,12 +220,7 @@ function drawPicture(picture, canvas, scale) { {{index "mousedown event", "mousemove event", "button property", "buttons property", "pointerPosition function"}} -When the left mouse button is pressed while the mouse is over the -picture canvas, the component calls the `pointerDown` callback, giving -it the position of the pixel that was clicked—in picture coordinates. -This will be used to implement mouse interaction with the picture. The -callback may return another callback function to be notified when the -pointer is moved to a different pixel while the button is held down. +When the left mouse button is pressed while the mouse is over the picture canvas, the component calls the `pointerDown` callback, giving it the position of the pixel that was clicked—in picture coordinates. This will be used to implement mouse interaction with the picture. The callback may return another callback function to be notified when the pointer is moved to a different pixel while the button is held down. ```{includeCode: true} PictureCanvas.prototype.mouse = function(downEvent, onDown) { @@ -373,17 +250,11 @@ function pointerPosition(pos, domNode) { {{index "getBoundingClientRect method", "clientX property", "clientY property"}} -Since we know the size of the ((pixel))s and we can use -`getBoundingClientRect` to find the position of the canvas on the -screen, it is possible to go from mouse event coordinates (`clientX` -and `clientY`) to picture coordinates. These are always rounded down -so that they refer to a specific pixel. +Since we know the size of the ((pixel))s and we can use `getBoundingClientRect` to find the position of the canvas on the screen, it is possible to go from mouse event coordinates (`clientX` and `clientY`) to picture coordinates. These are always rounded down so that they refer to a specific pixel. {{index "touchstart event", "touchmove event", "preventDefault method"}} -With touch events, we have to do something similar, but using -different events and making sure we call `preventDefault` on the -`"touchstart"` event to prevent ((panning)). +With touch events, we have to do something similar, but using different events and making sure we call `preventDefault` on the `"touchstart"` event to prevent ((panning)). ```{includeCode: true} PictureCanvas.prototype.touch = function(startEvent, @@ -410,34 +281,17 @@ PictureCanvas.prototype.touch = function(startEvent, {{index "touches property", "clientX property", "clientY property"}} -For touch events, `clientX` and `clientY` aren't available directly on -the event object, but we can use the coordinates of the first touch -object in the `touches` property. +For touch events, `clientX` and `clientY` aren't available directly on the event object, but we can use the coordinates of the first touch object in the `touches` property. ## The application -To make it possible to build the application piece by piece, we'll -implement the main component as a shell around a picture canvas and a -dynamic set of ((tool))s and ((control))s that we pass to its -constructor. +To make it possible to build the application piece by piece, we'll implement the main component as a shell around a picture canvas and a dynamic set of ((tool))s and ((control))s that we pass to its constructor. -The _controls_ are the interface elements that appear below the -picture. They'll be provided as an array of ((component)) -constructors. +The _controls_ are the interface elements that appear below the picture. They'll be provided as an array of ((component)) constructors. {{index "br (HTML tag)", "flood fill", "select (HTML tag)", "PixelEditor class", dispatch}} -The _tools_ do things like drawing pixels or filling in an area. The -application shows the set of available tools as a `<select>` field. -The currently selected tool determines what happens when the user -interacts with the picture with a pointer device. The set of -available tools is provided as -an object that maps the names that appear in the drop-down field to -functions that implement the tools. Such functions get a picture -position, a current application state, and a `dispatch` function as -arguments. They may return a move handler function that gets called -with a new position and a current state when the pointer moves to a -different pixel. +The _tools_ do things like drawing pixels or filling in an area. The application shows the set of available tools as a `<select>` field. The currently selected tool determines what happens when the user interacts with the picture with a pointer device. The set of available tools is provided as an object that maps the names that appear in the drop-down field to functions that implement the tools. Such functions get a picture position, a current application state, and a `dispatch` function as arguments. They may return a move handler function that gets called with a new position and a current state when the pointer moves to a different pixel. ```{includeCode: true} class PixelEditor { @@ -464,23 +318,15 @@ class PixelEditor { } ``` -The pointer handler given to `PictureCanvas` calls the currently -selected tool with the appropriate arguments and, if that returns a -move handler, adapts it to also receive the state. +The pointer handler given to `PictureCanvas` calls the currently selected tool with the appropriate arguments and, if that returns a move handler, adapts it to also receive the state. {{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"}} -The first control is the ((tool)) selection menu. It creates a -`<select>` element with an option for each tool and sets up a -`"change"` event handler that updates the application state when the -user selects a different tool. +The first control is the ((tool)) selection menu. It creates a `<select>` element with an option for each tool and sets up a `"change"` event handler that updates the application state when the user selects a different tool. ```{includeCode: true} class ToolSelect { @@ -498,31 +344,23 @@ class ToolSelect { {{index "label (HTML tag)"}} -By wrapping the label text and the field in a `<label>` element, we -tell the browser that the label belongs to that field so that you -can, for example, click the label to focus the field. +By wrapping the label text and the field in a `<label>` element, we tell the browser that the label belongs to that field so that you can, for example, click the label to focus the field. {{index "color field", "input (HTML tag)"}} -We also need to be able to change the color, so let's add a control for -that. An HTML `<input>` element with a `type` attribute of `color` -gives us a form field that is specialized for selecting colors. Such a -field's value is always a CSS color code in `"#RRGGBB"` format (red, -green, and blue components, two digits per color). The browser will -show a ((color picker)) interface when the user interacts with it. +We also need to be able to change the color, so let's add a control for that. An HTML `<input>` element with a `type` attribute of `color` gives us a form field that is specialized for selecting colors. Such a field's value is always a CSS color code in `"#RRGGBB"` format (red, green, and blue components, two digits per color). The browser will show a ((color picker)) interface when the user interacts with it. {{if book Depending on the browser, the color picker might look like this: -{{figure {url: "img/color-field.png", alt: "A color field", width: "6cm"}}} +{{figure {url: "img/color-field.png", alt: "Screenshot of color field", width: "6cm"}}} if}} {{index "ColorSelect class", "syncState method"}} -This ((control)) creates such a field and wires it up to stay -synchronized with the application state's `color` property. +This ((control)) creates such a field and wires it up to stay synchronized with the application state's `color` property. ```{includeCode: true} class ColorSelect { @@ -540,15 +378,11 @@ class ColorSelect { ## Drawing tools -Before we can draw anything, we need to implement the ((tool))s that -will control the functionality of mouse or touch events on the canvas. +Before we can draw anything, we need to implement the ((tool))s that will control the functionality of mouse or touch events on the canvas. {{index "draw function"}} -The most basic tool is the draw tool, which changes any ((pixel)) you -click or tap to the currently selected color. It dispatches an action -that updates the picture to a version in which the pointed-at pixel is -given the currently selected color. +The most basic tool is the draw tool, which changes any ((pixel)) you click or tap to the currently selected color. It dispatches an action that updates the picture to a version in which the pointed-at pixel is given the currently selected color. ```{includeCode: true} function draw(pos, state, dispatch) { @@ -561,15 +395,11 @@ function draw(pos, state, dispatch) { } ``` -The function immediately calls the `drawPixel` function but then also -returns it so that it is called again for newly touched pixels when -the user drags or ((swipe))s over the picture. +The function immediately calls the `drawPixel` function but then also returns it so that it's called again for newly touched pixels when the user drags or ((swipe))s over the picture. {{index "rectangle function"}} -To draw larger shapes, it can be useful to quickly create -((rectangle))s. The `rectangle` ((tool)) draws a rectangle between the -point where you start ((dragging)) and the point that you drag to. +To draw larger shapes, it can be useful to quickly create ((rectangle))s. The `rectangle` ((tool)) draws a rectangle between the point where you start ((dragging)) and the point that you drag to. ```{includeCode: true} function rectangle(start, state, dispatch) { @@ -593,29 +423,15 @@ function rectangle(start, state, dispatch) { {{index "persistent data structure", [state, persistence]}} -An important detail in this implementation is that when dragging, the -rectangle is redrawn on the picture from the _original_ state. -That way, you can make the rectangle larger and smaller again while -creating it, without the intermediate rectangles sticking around in -the final picture. This is one of the reasons why ((immutable)) -picture objects are useful—we'll see another reason later. +An important detail in this implementation is that when dragging, the rectangle is redrawn on the picture from the _original_ state. That way, you can make the rectangle larger and smaller again while creating it, without the intermediate rectangles sticking around in the final picture. This is one of the reasons why ((immutable)) picture objects are useful—we'll see another reason later. -Implementing ((flood fill)) is somewhat more involved. This is a -((tool)) that fills the pixel under the pointer and all adjacent -pixels that have the same color. "Adjacent" means directly -horizontally or vertically adjacent, not diagonally. This picture -illustrates the set of ((pixel))s colored when the flood fill tool is -used at the marked pixel: +Implementing ((flood fill)) is somewhat more involved. This is a ((tool)) that fills the pixel under the pointer and all adjacent pixels that have the same color. "Adjacent" means directly horizontally or vertically adjacent, not diagonally. This picture illustrates the set of ((pixel))s colored when the flood fill tool is used at the marked pixel: -{{figure {url: "img/flood-grid.svg", alt: "A pixel grid showing the area filled by a flood fill operation", width: "6cm"}}} +{{figure {url: "img/flood-grid.svg", alt: "Diagram of a pixel grid showing the area filled by a flood fill operation", width: "6cm"}}} {{index "fill function"}} -Interestingly, the way we'll do this looks a bit like the -((pathfinding)) code from [Chapter ?](robot). Whereas that code -searched through a graph to find a route, this code searches through a -grid to find all "connected" pixels. The problem of keeping track of a -branching set of possible routes is similar. +Interestingly, the way we'll do this looks a bit like the ((pathfinding)) code from [Chapter ?](robot). Whereas that code searched through a graph to find a route, this code searches through a grid to find all "connected" pixels. The problem of keeping track of a branching set of possible routes is similar. ```{includeCode: true} const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, @@ -624,14 +440,16 @@ const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, function fill({x, y}, state, dispatch) { let targetColor = state.picture.pixel(x, y); let drawn = [{x, y, color: state.color}]; + let visited = new Set(); for (let done = 0; done < drawn.length; done++) { for (let {dx, dy} of around) { let x = drawn[done].x + dx, y = drawn[done].y + dy; if (x >= 0 && x < state.picture.width && y >= 0 && y < state.picture.height && - state.picture.pixel(x, y) == targetColor && - !drawn.some(p => p.x == x && p.y == y)) { + !visited.has(x + "," + y) && + state.picture.pixel(x, y) == targetColor) { drawn.push({x, y, color: state.color}); + visited.add(x + "," + y); } } } @@ -639,18 +457,11 @@ function fill({x, y}, state, dispatch) { } ``` -The array of drawn pixels doubles as the function's ((work list)). -For each pixel reached, we have to see whether any adjacent pixels have the -same color and haven't already been painted over. The loop counter -lags behind the length of the `drawn` array as new pixels are added. -Any pixels ahead of it still need to be explored. When it catches up -with the length, no unexplored pixels remain, and the function is -done. +The array of drawn pixels doubles as the function's ((work list)). For each pixel reached, we have to see whether any adjacent pixels have the same color and haven't already been painted over. The loop counter lags behind the length of the `drawn` array as new pixels are added. Any pixels ahead of it still need to be explored. When it catches up with the length, no unexplored pixels remain, and the function is done. {{index "pick function"}} -The final ((tool)) is a ((color picker)), which allows you to point at -a color in the picture to use it as the current drawing color. +The final ((tool)) is a ((color picker)), which allows you to point at a color in the picture to use it as the current drawing color. ```{includeCode: true} function pick(pos, state, dispatch) { @@ -662,7 +473,7 @@ function pick(pos, state, dispatch) { We can now test our application! -```{lang: "text/html"} +```{lang: html} <div></div> <script> let state = { @@ -688,9 +499,7 @@ if}} {{index "SaveButton class", "drawPicture function", [file, image]}} -When we've drawn our masterpiece, we'll want to save it for later. We -should add a button for ((download))ing the current picture as an -image file. This ((control)) provides that button: +When we've drawn our masterpiece, we'll want to save it for later. We should add a button for ((download))ing the current picture as an image file. This ((control)) provides that button: ```{includeCode: true} class SaveButton { @@ -717,34 +526,19 @@ class SaveButton { {{index "canvas (HTML tag)"}} -The component keeps track of the current picture so that it can access -it when saving. To create the image file, it uses a `<canvas>` element -that it draws the picture on (at a scale of one pixel per pixel). +The component keeps track of the current picture so that it can access it when saving. To create the image file, it uses a `<canvas>` element on which it draws the picture (at a scale of one pixel per pixel). {{index "toDataURL method", "data URL"}} -The `toDataURL` method on a canvas element creates a URL that starts -with `data:`. Unlike `http:` and `https:` URLs, data URLs contain the -whole resource in the URL. They are usually very long, but they -allow us to create working links to arbitrary pictures, right here in -the browser. +The `toDataURL` method on a canvas element creates a URL that uses the `data:` scheme. Unlike `http:` and `https:` URLs, data URLs contain the whole resource in the URL. They are usually very long, but they allow us to create working links to arbitrary pictures, right here in the browser. {{index "a (HTML tag)", "download attribute"}} -To actually get the browser to download the picture, we then create a -((link)) element that points at this URL and has a `download` -attribute. Such links, when clicked, make the browser show a file save -dialog. We add that link to the document, simulate a click on it, and -remove it again. - -You can do a lot with ((browser)) technology, but sometimes the way to -do it is rather odd. +To actually get the browser to download the picture, we then create a ((link)) element that points at this URL and has a `download` attribute. Such links, when clicked, make the browser show a file save dialog. We add that link to the document, simulate a click on it, and remove it again. You can do a lot with ((browser)) technology, but sometimes the way to do it is rather odd. {{index "LoadButton class", control, [file, image]}} -And it gets worse. We'll also want to be able to load existing image -files into our application. To do that, we again define a button -component. +And it gets worse. We'll also want to be able to load existing image files into our application. To do that, we again define a button component. ```{includeCode: true} class LoadButton { @@ -769,19 +563,11 @@ function startLoad(dispatch) { {{index [file, access], "input (HTML tag)"}} -To get access to a file on the user's computer, we need the user to -select the file through a file input field. But I don't want the load -button to look like a file input field, so we create the file input -when the button is clicked and then pretend that this file input -itself was clicked. +To get access to a file on the user's computer, we need the user to select the file through a file input field. But we don't want the load button to look like a file input field, so we create the file input when the button is clicked and then pretend that this file input itself was clicked. {{index "FileReader class", "img (HTML tag)", "readAsDataURL method", "Picture class"}} -When the user has selected a file, we can use `FileReader` to get -access to its contents, again as a ((data URL)). That URL can be used -to create an `<img>` element, but because we can't get direct access -to the pixels in such an image, we can't create a `Picture` object -from that. +When the user has selected a file, we can use `FileReader` to get access to its contents, again as a ((data URL)). That URL can be used to create an `<img>` element, but because we can't get direct access to the pixels in such an image, we can't create a `Picture` object from that. ```{includeCode: true} function finishLoad(file, dispatch) { @@ -801,10 +587,7 @@ function finishLoad(file, dispatch) { {{index "canvas (HTML tag)", "getImageData method", "pictureFromImage function"}} -To get access to the pixels, we must first draw the picture to a -`<canvas>` element. The canvas context has a `getImageData` method -that allows a script to read its ((pixel))s. So, once the picture is on -the canvas, we can access it and construct a `Picture` object. +To get access to the pixels, we must first draw the picture to a `<canvas>` element. The canvas context has a `getImageData` method that allows a script to read its ((pixel))s. So once the picture is on the canvas, we can access it and construct a `Picture` object. ```{includeCode: true} function pictureFromImage(image) { @@ -827,97 +610,67 @@ function pictureFromImage(image) { } ``` -We'll limit the size of images to 100 by 100 pixels since anything -bigger will look _huge_ on our display and might slow down the -interface. +We'll limit the size of images to 100 by 100 pixels, since anything bigger will look _huge_ on our display and might slow down the interface. {{index "getImageData method", color, transparency}} -The `data` property of the object returned by `getImageData` is an -array of color components. For each pixel in the rectangle specified -by the arguments, it contains four values, which represent the red, -green, blue, and _((alpha))_ components of the pixel's color, as -numbers between 0 and 255. The alpha part represents opacity—when it -is zero, the pixel is fully transparent, and when it is 255, it is -fully opaque. For our purpose, we can ignore it. +The `data` property of the object returned by `getImageData` is an array of color components. For each pixel in the rectangle specified by the arguments, it contains four values that represent the red, green, blue, and _((alpha))_ components of the pixel's color, as numbers between 0 and 255. The alpha part represents opacity—when it is 0, the pixel is fully transparent, and when it is 255, it is fully opaque. For our purpose, we can ignore it. {{index "hexadecimal number", "toString method"}} -The two hexadecimal digits per component, as used in our color -notation, correspond precisely to the 0 to 255 range—two base-16 -digits can express 16^2^ = 256 different numbers. The `toString` -method of numbers can be given a base as argument, so `n.toString(16)` -will produce a string representation in base 16. We have to make sure -that each number takes up two digits, so the `hex` helper function -calls `padStart` to add a leading zero when necessary. +The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 16^2^ = 256 different numbers. The `toString` method of numbers can be given a base as 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 -Half of the process of editing is making little mistakes and -correcting them. So an important feature in a drawing -program is an ((undo history)). +Because half the process of editing is making little mistakes and correcting them, an important feature in a drawing program is an ((undo history)). {{index "persistent data structure", [state, "of application"]}} -To be able to undo changes, we need to store previous versions of the -picture. Since it's an ((immutable)) value, that is easy. But it does -require an additional field in the application state. +To be able to undo changes, we need to store previous versions of the picture. Since pictures are ((immutable)) values, that's easy. But it does require an additional field in the application state. {{index "done property"}} -We'll add a `done` array to keep previous versions of the ((picture)). -Maintaining this property requires a more complicated state update -function that adds pictures to the array. +We'll add a `done` array to keep previous versions of the ((picture)). Maintaining this property requires a more complicated state update function that adds pictures to the array. {{index "doneAt property", "historyUpdateState function", "Date.now function"}} -But we don't want to store _every_ change, only changes a certain -amount of ((time)) apart. To be able to do that, we'll need a second -property, `doneAt`, tracking the time at which we last stored a -picture in the history. +We don't want to store _every_ change, though—just changes that are a certain amount of ((time)) apart. To be able to do that, we'll need a second property, `doneAt`, to track the time at which we last stored a picture in the history. ```{includeCode: true} function historyUpdateState(state, action) { if (action.undo == true) { if (state.done.length == 0) return state; - return Object.assign({}, state, { + return { + ...state, picture: state.done[0], done: state.done.slice(1), doneAt: 0 - }); + }; } else if (action.picture && state.doneAt < Date.now() - 1000) { - return Object.assign({}, state, action, { + return { + ...state, + ...action, done: [state.picture, ...state.done], doneAt: Date.now() - }); + }; } else { - return Object.assign({}, state, action); + return {...state, ...action}; } } ``` {{index "undo history"}} -When the action is an undo action, the function takes the most recent -picture from the history and makes that the current picture. It sets -`doneAt` to zero so that the next change is guaranteed to store the -picture back in the history, allowing you to revert to it another -time if you want. +When the action is an undo action, the function takes the most recent picture from the history and makes that the current picture. It sets `doneAt` to zero so that the next change is guaranteed to store the picture back in the history, allowing you to revert to it another time if you want. -Otherwise, if the action contains a new picture and the last time we -stored something is more than a second (1000 milliseconds) ago, the -`done` and `doneAt` properties are updated to store the previous -picture. +Otherwise, if the action contains a new picture and the last time we stored something is more than a second (1000 milliseconds) ago, the `done` and `doneAt` properties are updated to store the previous picture. {{index "UndoButton class", control}} -The undo button ((component)) doesn't do much. It dispatches undo -actions when clicked and disables itself when there is nothing to -undo. +The undo button ((component)) doesn't do much. It dispatches undo actions when clicked and disables itself when there is nothing to undo. ```{includeCode: true} class UndoButton { @@ -937,11 +690,7 @@ class UndoButton { {{index "PixelEditor class", "startState constant", "baseTools constant", "baseControls constant", "startPixelEditor function"}} -To set up the application, we need to create a state, a set of -((tool))s, a set of ((control))s, and a ((dispatch)) function. We can -pass them to the `PixelEditor` constructor to create the main -component. Since we'll need to create several editors in the -exercises, we first define some bindings. +To set up the application, we need to create a state, a set of ((tool))s, a set of ((control))s, and a ((dispatch)) function. We can pass them to the `PixelEditor` constructor to create the main component. Since we'll need to create several editors in the exercises, we first define some bindings. ```{includeCode: true} const startState = { @@ -975,16 +724,11 @@ function startPixelEditor({state = startState, {{index "destructuring binding", "= operator", [property, access]}} -When destructuring an object or array, you can use `=` after a binding -name to give the binding a ((default value)), which is used when the -property is missing or holds `undefined`. The `startPixelEditor` -function makes use of this to accept an object with a number of -optional properties as an argument. If you don't provide a `tools` -property, for example, `tools` will be bound to `baseTools`. +When destructuring an object or array, you can use `=` after a binding name to give the binding a ((default value)), which is used when the property is missing or holds `undefined`. The `startPixelEditor` function makes use of this to accept an object with a number of optional properties as an argument. If you don't provide a `tools` property, for example, `tools` will be bound to `baseTools`. This is how we get an actual editor on the screen: -```{lang: "text/html", startCode: true} +```{lang: html, startCode: true} <div></div> <script> document.querySelector("div") @@ -994,83 +738,47 @@ This is how we get an actual editor on the screen: {{if interactive -Go ahead and draw something. I'll wait. +Go ahead and draw something. if}} ## Why is this so hard? -Browser technology is amazing. It provides a powerful set of interface -building blocks, ways to style and manipulate them, and tools to -inspect and debug your applications. The software you write for the -((browser)) can be run on almost every computer and phone on the -planet. +Browser technology is amazing. It provides a powerful set of interface building blocks, ways to style and manipulate them, and tools to inspect and debug your applications. The software you write for the ((browser)) can be run on almost every computer and phone on the planet. -At the same time, browser technology is ridiculous. You have to learn -a large number of silly tricks and obscure facts to master it, and the -default programming model it provides is so problematic that most -programmers prefer to cover it in several layers of ((abstraction)) -rather than deal with it directly. +At the same time, browser technology is ridiculous. You have to learn a large number of silly tricks and obscure facts to master it, and the default programming model it provides is so problematic that most programmers prefer to cover it in several layers of ((abstraction)) rather than deal with it directly. {{index standard, evolution}} -And though the situation is definitely improving, it mostly does so in -the form of more elements being added to address shortcomings—creating -even more ((complexity)). A feature used by a million websites can't -really be replaced. Even if it could, it would be hard to decide -what it should be replaced with. +While the situation is definitely improving, it mostly does so in the form of more elements being added to address shortcomings—creating even more ((complexity)). A feature used by a million websites can't really be replaced. Even if it could, it would be hard to decide what it should be replaced with. {{index "social factors", "economic factors", history}} -Technology never exists in a vacuum—we're constrained by our tools and -the social, economic, and historical factors that produced them. -This can be annoying, but it is generally more productive to try to -build a good understanding of how the _existing_ technical reality -works—and why it is the way it is—than to rage against it or hold out -for another reality. - -New ((abstraction))s _can_ be helpful. The component model and ((data -flow)) convention I used in this chapter is a crude form of that. As -mentioned, there are libraries that try to make user interface -programming more pleasant. At the time of writing, -[React](https://reactjs.org/) and [Angular](https://angular.io/) are -popular choices, but there's a whole cottage industry of such -frameworks. If you're interested in programming web applications, I -recommend investigating a few of them to understand how they work and -what benefits they provide. +Technology never exists in a vacuum—we're constrained by our tools and the social, economic, and historical factors that produced them. This can be annoying, but it is generally more productive to try to build a good understanding of how the _existing_ technical reality works—and why it is the way it is—than to rage against it or hold out for another reality. + +New ((abstraction))s _can_ be helpful. The component model and ((data flow)) convention I used in this chapter is a crude form of that. As mentioned, there are libraries that try to make user interface programming more pleasant. At the time of writing, [React](https://reactjs.org/) and [Svelte](https://svelte.dev/) are popular choices, but there's a whole cottage industry of such frameworks. If you're interested in programming web applications, I recommend investigating a few of them to understand how they work and what benefits they provide. ## Exercises -There is still room for improvement in our program. Let's add a few -more features as exercises. +There is still room for improvement in our program. Let's add a few more features as exercises. ### Keyboard bindings {{index "keyboard bindings (exercise)"}} -Add ((keyboard)) shortcuts to the application. The first letter of a -tool's name selects the tool, and [control]{keyname}-Z or [command]{keyname}-Z activates undo. +Add ((keyboard)) shortcuts to the application. The first letter of a tool's name selects the tool, and [ctrl]{keyname}-Z or [command]{keyname}-Z activates undo. {{index "PixelEditor class", "tabindex attribute", "elt function", "keydown event"}} -Do this by modifying the `PixelEditor` component. Add a `tabIndex` -property of 0 to the wrapping `<div>` element so that it can receive -keyboard ((focus)). Note that the _property_ corresponding to the -`tabindex` _attribute_ is called `tabIndex`, with a capital I, and our -`elt` function expects property names. Register the key event handlers -directly on that element. This means you have to click, touch, or -tab to the application before you can interact with it with the -keyboard. +Do this by modifying the `PixelEditor` component. Add a `tabIndex` property of 0 to the wrapping `<div>` element so that it can receive keyboard ((focus)). Note that the _property_ corresponding to the `tabindex` _attribute_ is called `tabIndex`, with a capital I, and our `elt` function expects property names. Register the key event handlers directly on that element. This means you have to click, touch, or tab to the application before you can interact with it with the keyboard. {{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 -```{test: no, lang: "text/html"} +```{test: no, lang: html} <div></div> <script> // The original PixelEditor class. Extend the constructor. @@ -1110,21 +818,15 @@ if}} {{index "keyboard bindings (exercise)", "key property", "shift key"}} -The `key` property of events for letter keys will be the lowercase -letter itself, if [shift]{keyname} isn't being held. We're not interested in -key events with [shift]{keyname} here. +The `key` property of events for letter keys will be the lowercase letter itself, if [shift]{keyname} isn't being held. We're not interested in key events with [shift]{keyname} here. {{index "keydown event"}} -A `"keydown"` handler can inspect its event object to see whether it -matches any of the shortcuts. You can automatically get the list of -first letters from the `tools` object so that you don't have to write -them out. +A `"keydown"` handler can inspect its event object to see whether it matches any of the shortcuts. You can automatically get the list of first letters from the `tools` object so that you don't have to write them out. {{index "preventDefault method"}} -When the key event matches a shortcut, call `preventDefault` on it and -((dispatch)) the appropriate action. +When the key event matches a shortcut, call `preventDefault` on it and ((dispatch)) the appropriate action. hint}} @@ -1132,31 +834,23 @@ hint}} {{index "efficient drawing (exercise)", "canvas (HTML tag)", efficiency}} -During drawing, the majority of work that our application does happens -in `drawPicture`. Creating a new state and updating the rest of the -DOM isn't very expensive, but repainting all the pixels on the canvas -is quite a bit of work. +During drawing, the majority of work that our application does happens in `drawPicture`. Creating a new state and updating the rest of the DOM isn't very expensive, but repainting all the pixels on the canvas is quite a bit of work. {{index "syncState method", "PictureCanvas class"}} -Find a way to make the `syncState` method of `PictureCanvas` faster by -redrawing only the pixels that actually changed. +Find a way to make the `syncState` method of `PictureCanvas` faster by redrawing only the pixels that actually changed. {{index "drawPicture function", compatibility}} -Remember that `drawPicture` is also used by the save button, so if you -change it, either make sure the changes don't break the old use or -create a new version with a different name. +Remember that `drawPicture` is also used by the save button, so if you change it, either make sure the changes don't break the old use or create a new version with a different name. {{index "width property", "height property"}} -Also note that changing the size of a `<canvas>` element, by setting -its `width` or `height` properties, clears it, making it entirely -transparent again. +Also note that changing the size of a `<canvas>` element, by setting its `width` or `height` properties, clears it, making it entirely transparent again. {{if interactive -```{test: no, lang: "text/html"} +```{test: no, lang: html} <div></div> <script> // Change this method @@ -1191,27 +885,15 @@ if}} {{index "efficient drawing (exercise)"}} -This exercise is a good example of how ((immutable)) data structures -can make code _faster_. Because we have both the old and the new -picture, we can compare them and redraw only the pixels that changed -color, saving more than 99 percent of the drawing work in most cases. +This exercise is a good example of how ((immutable)) data structures can make code _faster_. Because we have both the old and the new picture, we can compare them and redraw only the pixels that changed color, saving more than 99 percent of the drawing work in most cases. {{index "drawPicture function"}} -You can either write a new function `updatePicture` or have -`drawPicture` take an extra argument, which may be undefined or the -previous picture. For each ((pixel)), the function checks whether a -previous picture was passed with the same color at this position and -skips the pixel when that is the case. +You can either write a new function `updatePicture` or have `drawPicture` take an extra argument, which may be undefined or the previous picture. For each ((pixel)), the function checks whether a previous picture was passed with the same color at this position and skips the pixel when that is the case. {{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}} @@ -1219,14 +901,11 @@ hint}} {{index "circles (exercise)", dragging}} -Define a ((tool)) called `circle` that draws a filled circle when you -drag. The center of the circle lies at the point where the drag or -touch gesture starts, and its ((radius)) is determined by the distance -dragged. +Define a ((tool)) called `circle` that draws a filled circle when you drag. The center of the circle lies at the point where the drag or touch gesture starts, and its ((radius)) is determined by the distance dragged. {{if interactive -```{test: no, lang: "text/html"} +```{test: no, lang: html} <div></div> <script> function circle(pos, state, dispatch) { @@ -1234,7 +913,7 @@ dragged. } let dom = startPixelEditor({ - tools: Object.assign({}, baseTools, {circle}) + tools: {...baseTools, circle} }); document.querySelector("div").appendChild(dom); </script> @@ -1246,22 +925,11 @@ 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 (`Math.pow(x, 2)`) of the difference in -x-coordinates and the square of the difference in y-coordinates. Then -loop over a square of pixels around the start position, whose sides -are at least twice the ((radius)), and color those that are within the -circle's radius, again using the Pythagorean formula to figure out -their ((distance)) from the center. +To figure out which pixels to color, you can use the ((Pythagorean theorem)). First figure out the distance between the current pointer position and the start position by taking the square root (`Math.sqrt`) of the sum of the square (`x ** 2`) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the ((radius)), and color those that are within the circle's radius, again using the Pythagorean formula to figure out their ((distance)) from the center. -Make sure you don't try to color pixels that are outside of the -picture's boundaries. +Make sure you don't try to color pixels that are outside of the picture's boundaries. hint}} @@ -1269,39 +937,25 @@ hint}} {{index "proper lines (exercise)", "line drawing"}} -This is a more advanced exercise than the preceding two, and it will -require you to design a solution to a nontrivial problem. Make sure -you have plenty of time and ((patience)) before starting to work on -this exercise, and do not get discouraged by initial failures. +This is a more advanced exercise than the preceding 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"}} -On most browsers, when you select the `draw` ((tool)) and quickly drag -across the picture, you don't get a closed line. Rather, you get dots -with gaps between them because the `"mousemove"` or `"touchmove"` -events did not fire quickly enough to hit every ((pixel)). +On most browsers, when you select the `draw` ((tool)) and quickly drag across the picture, you don't get a closed line. Rather, you get dots with gaps between them because the `"mousemove"` or `"touchmove"` events did not fire quickly enough to hit every ((pixel)). -Improve the `draw` tool to make it draw a full line. This means you -have to make the motion handler function remember the previous -position and connect that to the current one. +Improve the `draw` tool to make it draw a full line. This means you have to make the motion handler function remember the previous position and connect that to the current one. -To do this, since the pixels can be an arbitrary distance apart, -you'll have to write a general line drawing function. +To do this, since the pixels can be an arbitrary distance apart, you'll have to write a general line drawing function. -A line between two pixels is a connected chain of pixels, as straight -as possible, going from the start to the end. Diagonally adjacent -pixels count as a connected. So a slanted line should look like the -picture on the left, not the picture on the right. +A line between two pixels is a connected chain of pixels, as straight as possible, going from the start to the end. Diagonally adjacent pixels count as connected. A slanted line should look like the picture on the left, not the picture on the right. -{{figure {url: "img/line-grid.svg", alt: "Two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically", width: "6cm"}}} +{{figure {url: "img/line-grid.svg", alt: "Diagram of two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically", width: "6cm"}}} -Finally, if we have code that draws a line between two arbitrary points, we -might as well use it to also define a `line` tool, -which draws a straight line between the start and end of a drag. +Finally, if we have code that draws a line between two arbitrary points, we might as well use it to also define a `line` tool, which draws a straight line between the start and end of a drag. {{if interactive -```{test: no, lang: "text/html"} +```{test: no, lang: html} <div></div> <script> // The old draw tool. Rewrite this. @@ -1331,44 +985,21 @@ if}} {{index "proper lines (exercise)", "line drawing"}} -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. - -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. +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. + +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 horizontalish line, and if not, a verticalish one. {{index "Math.abs function", "absolute value"}} -Make sure you compare the _absolute_ values of the _x_ and _y_ -difference, which you can get with `Math.abs`. +Make sure you compare the _absolute_ values of the _x_ and _y_ difference, which you can get with `Math.abs`. {{index "swapping bindings"}} -Once you know along which ((axis)) you will be looping, you can check -whether the start point has a higher coordinate along that axis than -the endpoint and swap them if necessary. A succinct way to swap the -values of two bindings in JavaScript uses ((destructuring assignment)) -like this: +Once you know along which ((axis)) you will be looping, you can check whether the start point has a higher coordinate along that axis than the endpoint and swap them if necessary. A succinct way to swap the values of two bindings in JavaScript uses ((destructuring assignment)) like this: ```{test: no} [start, end] = [end, start]; @@ -1376,12 +1007,6 @@ like this: {{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 b85661be3..00f08286f 100644 --- a/20_node.md +++ b/20_node.md @@ -1,95 +1,57 @@ -{{meta {code_links: ["code/file_server.js"]}}} +{{meta {code_links: ["code/file_server.mjs"]}}} # Node.js {{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}} {{index "Yuan-Ma", "Book of Programming"}} -{{figure {url: "img/chapter_picture_20.jpg", alt: "Picture of a telephone pole", chapter: "framed"}}} +{{figure {url: "img/chapter_picture_20.jpg", alt: "Illustration showing a telephone pole with a tangle of wires going in all directions", chapter: "framed"}}} {{index "command line"}} -So far, we have used the JavaScript language in a single environment: -the browser. This chapter and the [next one](skillsharing) will -briefly introduce ((Node.js)), a program that allows you to apply your -JavaScript skills outside of the browser. With it, you can build -anything from small command line tools to HTTP ((server))s that power -dynamic ((website))s. +So far, we've used the JavaScript language in a single environment: the browser. This chapter and the [next one](skillsharing) will briefly introduce ((Node.js)), a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP ((server))s that power dynamic ((website))s. -These chapters aim to teach you the main concepts that Node.js uses -and to give you enough information to write useful programs for it. -They do not try to be a complete, or even a thorough, treatment of the -platform. +These chapters aim to teach you the main concepts that Node.js uses and to give you enough information to write useful programs for it. They do not try to be a complete, or even a thorough, treatment of the platform. {{if interactive -Whereas you could run the code in previous chapters directly on these -pages, because it was either raw JavaScript or written for the -browser, the code samples in this chapter are written for Node and -often won't run in the browser. +Whereas you could run the code in previous chapters directly on these pages, because it was either raw JavaScript or written for the browser, the code samples in this chapter are written for Node and often won't run in the browser. if}} -If you want to follow along and run the code in this chapter, you'll -need to install Node.js version 10.1 or higher. To do so, go to -[_https://nodejs.org_](https://nodejs.org) and follow the installation -instructions for your operating system. You can also find further -((documentation)) for Node.js there. +If you want to follow along and run the code in this chapter, you'll need to install Node.js version 18 or higher. To do so, go to [_https://nodejs.org_](https://nodejs.org) and follow the installation instructions for your operating system. You can also find further ((documentation)) for Node.js there. ## Background {{index responsiveness, input, [network, speed]}} -One of the more difficult problems with writing systems that -communicate over the network is managing input and ((output))—that -is, the reading and writing of data to and from the network and ((hard -drive)). Moving data around takes time, and ((scheduling)) it cleverly -can make a big difference in how quickly a system responds to the user -or to network requests. +When building systems that communicate over the network, the way you manage input and ((output))—that is, the reading and writing of data to and from the network and ((hard drive))—can make a big difference in how quickly a system responds to the user or to network requests. {{index ["asynchronous programming", "in Node.js"]}} -In such programs, asynchronous programming is often helpful. It -allows the program to send and receive data from and to multiple -devices at the same time without complicated thread management and -synchronization. +In such programs, asynchronous programming is often helpful. It allows the program to send and receive data from and to multiple devices at the same time without complicated thread management and synchronization. {{index "programming language", "Node.js", standard}} -Node was initially conceived for the purpose of making asynchronous -programming easy and convenient. JavaScript lends itself well to a -system like Node. It is one of the few programming languages that does -not have a built-in way to do in- and output. Thus, JavaScript could -be fit onto Node's rather eccentric approach to in- and output 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 {{index "node program"}} -When ((Node.js)) is installed on a system, it provides a program -called `node`, which is used to run JavaScript files. Say you have a -file `hello.js`, containing this code: +When ((Node.js)) is installed on a system, it provides a program called `node`, which is used to run JavaScript files. Say you have a file `hello.js`, containing this code: ``` let message = "Hello world"; console.log(message); ``` -You can then run `node` from the ((command line)) like this to execute -the program: +You can then run `node` from the ((command line)) like this to execute the program: ```{lang: null} $ node hello.js @@ -98,18 +60,11 @@ 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"}} -If you run `node` without giving it a file, it provides you with a -prompt at which you can type JavaScript code and immediately see the -result. +If you run `node` without giving it a file, it provides you with a prompt at which you can type JavaScript code and immediately see the result. ```{lang: null} $ node @@ -123,20 +78,11 @@ $ {{index "process object", "global scope", [binding, global], "exit method", "status code"}} -The `process` binding, just like the `console` binding, is available -globally in Node. It provides various ways to inspect and manipulate -the current program. The `exit` method ends the process and can be -given an exit status code, which tells the program that started `node` -(in this case, the command line shell) whether the program completed -successfully (code zero) or encountered an error (any other code). +The `process` binding, just like the `console` binding, is available globally in Node. It provides various ways to inspect and manipulate the current program. The `exit` method ends the process and can be given an exit status code, which tells the program that started `node` (in this case, the command line shell) whether the program completed successfully (code zero) or encountered an error (any other code). {{index "command line", "argv property"}} -To find the command line arguments given to your script, you can read -`process.argv`, which is an array of strings. Note that it also -includes the name of the `node` command and your script name, so the -actual arguments start at index 2. If `showargv.js` contains the -statement `console.log(process.argv)`, you could run it like this: +To find the command line arguments given to your script, you can read `process.argv`, which is an array of strings. Note that it also includes the name of the `node` command and your script name, so the actual arguments start at index 2. If `showargv.js` contains the statement `console.log(process.argv)`, you could run it like this: ```{lang: null} $ node showargv.js one --and two @@ -145,60 +91,36 @@ $ node showargv.js one --and two {{index [binding, global]}} -All the ((standard)) JavaScript global bindings, such as `Array`, -`Math`, and `JSON`, are also present in Node's environment. -Browser-related functionality, such as `document` or `prompt`, is not. +All the ((standard)) JavaScript global bindings, such as `Array`, `Math`, and `JSON`, are also present in Node's environment. Browser-related functionality, such as `document` or `prompt`, is not. ## Modules {{index "Node.js", "global scope", "module loader"}} -Beyond the bindings I mentioned, such as `console` and `process`, -Node puts few additional bindings in the global scope. If you want to access -built-in functionality, you have to ask the module system for it. +Beyond the bindings I mentioned, such as `console` and `process`, Node puts few additional bindings in the global scope. If you want to access built-in functionality, you have to ask the module system for it. {{index "require function"}} -The ((CommonJS)) module system, based on the `require` function, was -described in [Chapter ?](modules#commonjs). This system is built into -Node and is used to load anything from built-in ((module))s to -downloaded ((package))s to ((file))s that are part of your own -program. +Node started out using the ((CommonJS)) module system, based on the `require` function, which we saw in [Chapter ?](modules#commonjs). It will still use this system by default when you load a `.js` file. -{{index [path, "file system"], "relative path", resolution}} +{{index "import keyword", "ES modules"}} -When `require` is called, Node has to resolve the given string to an -actual ((file)) that it can load. Pathnames that start with `/`, -`./`, or `../` are resolved relative to the current module's path, -where `.` stands for the current directory, `../` for one -directory up, and `/` for the root of the file system. So if you ask -for `"./graph"` from the file `/tmp/robot/robot.js`, Node will try to -load the file `/tmp/robot/graph.js`. +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 "index.js"}} +{{index [path, "filesystem"], "relative path", resolution}} -The `.js` ((extension)) may be omitted, and Node will add it if such a -file exists. If the required path refers to a ((directory)), Node will -try to load the file named `index.js` in that directory. +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 -given to `require`, it is assumed to refer to either a built-in -((module)) or a module installed in a `node_modules` directory. For -example, `require("fs")` will give you Node's built-in file system -module. And `require("robot")` might try to load the library found in -`node_modules/robot/`. A common way to install such libraries is by -using ((NPM)), which we'll come back to in a moment. +When a string that does not look like a relative or absolute path is imported, it is assumed to refer to either a built-in ((module)) or a module installed in a `node_modules` directory. For example, importing from `"node:fs"` will give you Node's built-in 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 "require function", "Node.js", "garble example"}} +{{index "import keyword", "Node.js", "garble example"}} -Let's set up a small project consisting of two files. The first one, -called `main.js`, defines a script that can be called from the -((command line)) to reverse a string. +Let's set up a small project consisting of two files. The first one, called `main.mjs`, defines a script that can be called from the ((command line)) to reverse a string. ``` -const {reverse} = require("./reverse"); +import {reverse} from "./reverse.mjs"; // Index 2 holds the first actual command line argument let argument = process.argv[2]; @@ -208,27 +130,22 @@ console.log(reverse(argument)); {{index reuse, "Array.from function", "join method"}} -The file `reverse.js` defines a library for reversing strings, which -can be used both by this command line tool and by other scripts that -need direct access to a string-reversing function. +The file `reverse.mjs` defines a library for reversing strings, which can be used both by this command line tool and by other scripts that need direct access to a string-reversing function. ``` -exports.reverse = function(string) { +export function reverse(string) { return Array.from(string).reverse().join(""); -}; +} ``` -{{index "exports object", CommonJS, [interface, module]}} +{{index "export keyword", "ES modules", [interface, module]}} -Remember that adding properties to `exports` adds them to the -interface of the module. Since Node.js treats files as -((CommonJS)) ((module))s, `main.js` can take the exported `reverse` -function from `reverse.js`. +Remember that `export` is used to declare that a binding is part of the module's interface. That allows `main.mjs` to import and use the function. We can now call our tool like this: ```{lang: null} -$ node main.js JavaScript +$ node main.mjs JavaScript tpircSavaJ ``` @@ -236,24 +153,15 @@ tpircSavaJ {{index NPM, "Node.js", "npm program", library}} -NPM, which was introduced in [Chapter ?](modules#modules_npm), is an -online repository of JavaScript ((module))s, many of which are -specifically written for Node. When you install Node on your computer, -you also get the `npm` command, which you can use to interact with this -repository. +NPM, introduced in [Chapter ?](modules#modules_npm), is an online repository of JavaScript ((module))s, many of which are specifically written for Node. When you install Node on your computer, you also get the `npm` command, which you can use to interact with this repository. {{index "ini package"}} -NPM's main use is ((download))ing packages. We saw the `ini` package in -[Chapter ?](modules#modules_ini). We can use NPM to fetch and install -that package on our computer. +NPM's main use is ((download))ing packages. We saw the `ini` package in [Chapter ?](modules#modules_ini). We can use NPM to fetch and install that package on our computer. ```{lang: null} $ npm install ini -npm WARN enoent ENOENT: no such file or directory, - open '/tmp/package.json' -+ ini@1.3.5 -added 1 package in 0.552s +added 1 package in 723ms $ node > const {parse} = require("ini"); @@ -263,39 +171,25 @@ $ node {{index "require function", "node_modules directory", "npm program"}} -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 call `require("ini")`, this library is loaded, and -we can call its `parse` property to parse a configuration file. +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}} -In the `npm install` example, you could see a ((warning)) about the -fact that the `package.json` file did not exist. It is recommended to -create such a file for each project, either manually or by running -`npm init`. It contains some 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: +The robot simulation from [Chapter ?](robot), as modularized in the exercise in [Chapter ?](modules#modular_robot), might have a `package.json` file like this: -```{lang: "application/json"} +```{lang: "json"} { "author": "Marijn Haverbeke", "name": "eloquent-javascript-robot", "description": "Simulation of a package-delivery robot", "version": "1.0.0", - "main": "run.js", + "main": "run.mjs", "dependencies": { "dijkstrajs": "^1.0.1", "random-item": "^1.0.0" @@ -306,74 +200,40 @@ exercise in [Chapter ?](modules#modular_robot), might have a {{index "npm program", tool}} -When you run `npm install` without naming a package to install, NPM -will install the dependencies listed in `package.json`. When you -install a specific package that is not already listed as a dependency, -NPM will add it to `package.json`. +When you run `npm install` without naming a package to install, NPM will install the dependencies listed in `package.json`. When you install a specific package that is not already listed as a dependency, NPM will add it to `package.json`. ### Versions {{index "package.json", dependency, evolution}} -A `package.json` file lists both the program's own ((version)) and -versions for its dependencies. Versions are a way to deal with the -fact that ((package))s evolve separately, and code written to work -with a package as it existed at one point may not work with a later, -modified version of the package. +A `package.json` file lists both the program's own ((version)) and versions for its dependencies. Versions are a way to deal with the fact that ((package))s evolve separately, and code written to work with a package as it existed at one point may not work with a later, modified version of the package. {{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"}} -A caret character (`^`) in front of the version number for a -dependency in `package.json` indicates that any version compatible -with the given number may be installed. So, for example, `"^2.3.0"` -would mean that any version greater than or equal to 2.3.0 and less -than 3.0.0 is allowed. +A caret character (`^`) in front of the version number for a dependency in `package.json` indicates that any version compatible with the given number may be installed. For example, `"^2.3.0"` would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed. {{index publishing}} -The `npm` command is also used to publish new packages or new versions -of packages. If you run `npm publish` in a ((directory)) that has a -`package.json` file, it will publish a package with the name and -version listed in the JSON file to the registry. Anyone can publish -packages to NPM—though only under a package name that isn't in use yet since it would be -somewhat scary if random people could update existing packages. +The `npm` command is also used to publish new packages or new versions of packages. If you run `npm publish` in a ((directory)) that has a `package.json` file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn't in use yet, since it wouldn't be good if random people could update existing packages. -Since the `npm` program is a piece of software that talks to an open -system—the package registry—there is nothing unique about what it -does. Another program, `yarn`, which can be installed from the NPM -registry, fills the same role as `npm` using a somewhat different -interface and installation strategy. +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. -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. +## The filesystem module -## The file system module +{{index directory, "node:fs package", "Node.js", [file, access]}} -{{index directory, "fs package", "Node.js", [file, access]}} - -One of the most commonly used built-in modules in Node is the `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"}} -For example, the function called `readFile` reads a file -and then calls a callback with the file's contents. +For example, the function called `readFile` reads a file and then calls a callback with the file's contents. ``` -let {readFile} = require("fs"); +import {readFile} from "node:fs"; readFile("file.txt", "utf8", (error, text) => { if (error) throw error; console.log("The file contains:", text); @@ -382,18 +242,10 @@ readFile("file.txt", "utf8", (error, text) => { {{index "Buffer class"}} -The second argument to `readFile` indicates the _((character -encoding))_ used to decode the file into a string. There are several -ways in which ((text)) can be encoded to ((binary data)), but most -modern systems use ((UTF-8)). So unless you have reasons to believe -another encoding is used, pass `"utf8"` when reading a text file. If -you do not pass an encoding, Node will assume you are interested in -the binary data and will give you a `Buffer` object instead of a -string. This is an ((array-like object)) that contains numbers -representing the bytes (8-bit chunks of data) in the files. +The second argument to `readFile` indicates the _((character encoding))_ used to decode the file into a string. There are several ways in which ((text)) can be encoded to ((binary data)), but most modern systems use ((UTF-8)). Unless you have reasons to believe another encoding is used, pass `"utf8"` when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a `Buffer` object instead of a string. This is an ((array-like object)) that contains numbers representing the bytes (8-bit chunks of data) in the files. ``` -const {readFile} = require("fs"); +import {readFile} from "node:fs"; readFile("file.txt", (error, buffer) => { if (error) throw error; console.log("The file contained", buffer.length, "bytes.", @@ -401,12 +253,12 @@ 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. ``` -const {writeFile} = require("fs"); +import {writeFile} from "node:fs"; writeFile("graffiti.txt", "Node was here", err => { if (err) console.log(`Failed to write file: ${err}`); else console.log("File written."); @@ -415,74 +267,52 @@ writeFile("graffiti.txt", "Node was here", err => { {{index "Buffer class", "character encoding"}} -Here it was not necessary to specify the encoding—`writeFile` will -assume that when it is given a string to write, rather than a `Buffer` -object, it should write it out as text using its default character -encoding, which is ((UTF-8)). +Here it was not necessary to specify the encoding—`writeFile` will assume that when it is given a string to write, rather than a `Buffer` object, it should write it out as text using its default character encoding, which is ((UTF-8)). -{{index "fs package", "readdir function", "stat function", "rename function", "unlink function"}} +{{index "node:fs package", "readdir function", "stat function", "rename function", "unlink function"}} -The `fs` module contains many other useful functions: `readdir` will -return the files in a ((directory)) as an array of strings, `stat` -will retrieve information about a file, `rename` will rename a file, -`unlink` will remove one, and so on. See the documentation at -[_https://nodejs.org_](https://nodejs.org) for specifics. +The `node:fs` module contains many other useful functions: `readdir` will give you the files in a ((directory)) as an array of strings, `stat` will retrieve information about a file, `rename` will rename a file, `unlink` will remove one, and so on. See the documentation at [_https://nodejs.org_](https://nodejs.org) for specifics. {{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", "promises package"}} +{{index "Promise class", "node:fs/promises package"}} -Though promises have been part of JavaScript for a while, at the time -of writing their integration into Node.js is still a work in progress. -There is an object `promises` exported from the `fs` package since -version 10.1 that contains most of the same functions as `fs` 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. ``` -const {readFile} = require("fs").promises; +import {readFile} from "node:fs/promises"; readFile("file.txt", "utf8") .then(text => console.log("The file contains:", text)); ``` -{{index "synchronous programming", "fs package", "readFileSync function"}} +{{index "synchronous programming", "node:fs package", "readFileSync function"}} -Sometimes you don't need asynchronicity, and it just gets in the way. -Many of the functions in `fs` also have a synchronous variant, which -has the same name with `Sync` added to the end. For example, the -synchronous version of `readFile` is called `readFileSync`. +Sometimes you don't need asynchronicity and it just gets in the way. Many of the functions in `node:fs` also have a synchronous variant, which has the same name with `Sync` added to the end. For example, the synchronous version of `readFile` is called `readFileSync`. ``` -const {readFileSync} = require("fs"); +import {readFileSync} from "node:fs"; console.log("The file contains:", readFileSync("file.txt", "utf8")); ``` {{index optimization, performance, blocking}} -Do note that while such a synchronous operation is being performed, -your program is stopped entirely. If it should be responding to the -user or to other machines on the network, being stuck on a synchronous -action might produce annoying delays. +Note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays. ## The HTTP module -{{index "Node.js", "http package", [HTTP, server]}} +{{index "Node.js", "node:http package", [HTTP, server]}} -Another central module is called `http`. It provides functionality for -running HTTP ((server))s and making HTTP ((request))s. +Another central module is called `node:http`. It provides functionality for running an HTTP ((server)). {{index "listening (TCP)", "listen method", "createServer function"}} This is all it takes to start an HTTP server: ``` -const {createServer} = require("http"); +import {createServer} from "node:http"; let server = createServer((request, response) => { response.writeHead(200, {"Content-Type": "text/html"}); response.write(` @@ -496,167 +326,66 @@ console.log("Listening! (port 8000)"); {{index port, localhost}} -If you run this script on your own machine, you can point your web -browser at -[_http://localhost:8000/hello_](http://localhost:8000/hello) to make a -request to your server. It will respond with a small HTML page. +If you run this script on your own machine, you can point your web browser at [_http://localhost:8000/hello_](http://localhost:8000/hello) to make a request to your server. It will respond with a small HTML page. {{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. -So, when you open that page in your browser, it sends a request to your -own computer. This causes the server function to run and send back a -response, which you can then see in the browser. +When you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser. {{index "200 (HTTP status code)", "Content-Type header", "writeHead method"}} -To send something back, you call methods on the `response` object. The -first, `writeHead`, will write out the ((response)) ((header))s (see -[Chapter ?](http#headers)). You give it the status code (200 for "OK" -in this case) and an object that contains header values. The example -sets the `Content-Type` header to inform the client that we'll be -sending back an HTML document. +To send something to the client, you call methods on the `response` object. The first, `writeHead`, will write out the ((response)) ((header))s (see [Chapter ?](http#headers)). You give it the status code (200 for "OK" in this case) and an object that contains header values. The example sets the `Content-Type` header to inform the client that we'll be sending back an HTML document. {{index "writable stream", "body (HTTP)", stream, "write method", "end method"}} -Next, the actual response body (the document itself) is sent with -`response.write`. You are allowed to call this method multiple times -if you want to send the response piece by piece, for example to stream -data to the client as it becomes available. Finally, `response.end` -signals the end of the response. +Next, the actual response body (the document itself) is sent with `response.write`. You're allowed to call this method multiple times if you want to send the response piece by piece—for example, to stream data to the client as it becomes available. Finally, `response.end` signals the end of the response. {{index "listen method"}} -The call to `server.listen` causes the ((server)) to start waiting for -connections on ((port)) 8000. This is why you have to connect -to _localhost:8000_ to speak to this server, rather than just -_localhost_, which would use the default port 80. +The call to `server.listen` causes the ((server)) to start waiting for connections on ((port)) 8000. This is why you have to connect to _localhost:8000_ to speak to this server, rather than just _localhost_, which would use the default port 80. {{index "Node.js", "kill process"}} -When you run this script, the process just sits there and waits. When -a script is listening for events—in this case, network -connections—`node` will not automatically exit when it reaches the end -of the script. To close it, press [control]{keyname}-C. +When you run this script, the process just sits there and waits. When a script is listening for events—in this case, network connections—`node` will not automatically exit when it reaches the end of the script. To close it, press [ctrl]{keyname}-C. {{index [method, HTTP]}} -A real web ((server)) usually does more than the one in the example—it -looks at the request's ((method)) (the `method` property) to see what -action the client is trying to perform and looks at the request's ((URL)) to -find out which resource this action is being performed on. We'll see a -more advanced server [later in this chapter](node#file_server). - -{{index "http package", "request function", [HTTP, client]}} - -To act as an HTTP _((client))_, we can use the `request` function -in the `http` module. - -``` -const {request} = require("http"); -let requestStream = request({ - hostname: "eloquentjavascript.net", - path: "/20_node.html", - method: "GET", - headers: {Accept: "text/html"} -}, response => { - console.log("Server responded with status code", - response.statusCode); -}); -requestStream.end(); -``` - -{{index "Node.js", "callback function", "readable stream"}} - -The first argument to `request` configures the request, telling Node -what server to talk to, what path to request from that server, which -method to use, and so on. The second argument is the function that -should be called when a response comes in. It is given an object that -allows us to inspect the response, for example to find out its status -code. - -{{index "GET method", "write method", "end method", "writable stream", "request function"}} - -Just like the `response` object we saw in the server, the object -returned by `request` allows us to ((stream)) data into the -((request)) with the `write` method and finish the request with the -`end` method. The example does not use `write` because `GET` requests -should not contain data in their request body. +A real web ((server)) usually does more than the one in the example—it looks at the request's ((method)) (the `method` property) to see what action the client is trying to perform and looks at the request's ((URL)) to find out on which resource this action is being performed. We'll see a more advanced server [later in this chapter](node#file_server). -{{index HTTPS, "https package", "request function"}} +{{index "node:http package", "request function", "fetch function", [HTTP, client]}} -There's a similar `request` function in the `https` module that -can be used to make requests to `https:` URLs. - -{{index "fetch function", "Promise class", "node-fetch package"}} - -Making requests with Node's raw functionality is rather verbose. -There are much more convenient wrapper packages available on NPM. For -example, `node-fetch` provides the promise-based `fetch` interface that -we know from the browser. +The `node:http` module also provides a `request` function that can be used to make HTTP requests. However, it is a lot more cumbersome to use than `fetch`, which we saw in [Chapter ?](http). Fortunately, `fetch` is also available in Node as a global binding. Unless you want to do something very specific, such as processing the response document piece by piece as the data comes in over the network, I recommend sticking to `fetch`. ## Streams -{{index "Node.js", stream, "writable stream"}} - -We have seen two instances of writable streams in the HTTP -examples—namely, the response object that the server could write to -and the request object that was returned from `request`. +{{index "Node.js", stream, "writable stream", "callback function", ["asynchronous programming", "in Node.js"], "write method", "end method", "Buffer class"}} -{{index "callback function", ["asynchronous programming", "in Node.js"], "write method", "end method", "Buffer class"}} - -_Writable streams_ are a widely used concept in Node. Such objects have -a `write` method that can be passed a string or a `Buffer` object to -write something to the stream. Their `end` method closes the stream -and optionally takes a value to write to the stream before -closing. Both of these methods can also be given a callback as an -additional argument, which they will call when the writing or closing -has finished. +The response object that the HTTP server could write to is an example of a _writable stream_ object, which is a widely used concept in Node. Such objects have a `write` method that can be passed a string or a `Buffer` object to write something to the stream. Their `end` method closes the stream and optionally takes a value to write to the stream before closing. Both of these methods can also be given a callback as an additional argument, which they will call when the writing or closing has finished. {{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 `fs` module. Then you -can 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"}} -Readable ((stream))s are a little more involved. Both the `request` -binding that was passed to the HTTP server's callback and the -`response` binding passed to the HTTP client's callback are readable -streams—a server reads requests and then writes responses, whereas a -client first writes a request and then reads a response. Reading from -a stream is done using event handlers, rather than methods. +_Readable ((stream))s_ are a little more involved. The `request` argument to the HTTP server's callback is a readable stream. Reading from a stream is done using event handlers rather than methods. {{index "on method", "addEventListener method"}} -Objects that emit events in Node have a method called `on` that is -similar to the `addEventListener` method in the browser. You give it -an event name and then a function, and it will register that function -to be called whenever the given event occurs. +Objects that emit events in Node have a method called `on` that is similar to the `addEventListener` method in the browser. You give it an event name and then a function, and it will register that function to be called whenever the given event occurs. {{index "createReadStream function", "data event", "end event", "readable stream"}} -Readable ((stream))s have `"data"` and `"end"` events. The first is -fired every time data comes in, and the second is called whenever the -stream is at its end. This model is most suited for _streaming_ data that -can be immediately processed, even when the whole document isn't -available yet. A file can be read as a readable stream by using the -`createReadStream` function from `fs`. +Readable ((stream))s have `"data"` and `"end"` events. The first is fired every time data comes in, and the second is called whenever the stream is at its end. This model is most suited for _streaming_ data that can be immediately processed, even when the whole document isn't available yet. A file can be read as a readable stream by using the `createReadStream` function from `node:fs`. {{index "upcasing server example", capitalization, "toUpperCase method"}} -This code creates a ((server)) that reads request bodies and streams -them back to the client as all-uppercase text: +This code creates a ((server)) that reads request bodies and streams them back to the client as all-uppercase text: ``` -const {createServer} = require("http"); +import {createServer} from "node:http"; createServer((request, response) => { response.writeHead(200, {"Content-Type": "text/plain"}); request.on("data", chunk => @@ -667,89 +396,53 @@ createServer((request, response) => { {{index "Buffer class", "toString method"}} -The `chunk` value passed to the data handler will be a binary -`Buffer`. We can convert this to a string by decoding it as UTF-8 -encoded characters with its `toString` method. +The `chunk` value passed to the data handler will be a binary `Buffer`. We can convert this to a string by decoding it as UTF-8 encoded characters with its `toString` method. -The following piece of code, when run with the uppercasing server -active, will send a request to that server and write out the response -it gets: +The following piece of code, when run with the uppercasing server active, will send a request to that server and write out the response it gets: ``` -const {request} = require("http"); -request({ - hostname: "localhost", - port: 8000, - method: "POST" -}, response => { - response.on("data", chunk => - process.stdout.write(chunk.toString())); -}).end("Hello server"); +fetch("http://localhost:8000/", { + method: "POST", + body: "Hello server" +}).then(resp => resp.text()).then(console.log); // → HELLO SERVER ``` -{{index "stdout property", "standard output", "writable stream", "console.log"}} - -The example writes to `process.stdout` (the process's standard output, -which is a writable stream) instead of using `console.log`. We can't -use `console.log` because it adds an extra newline character after -each piece of text that it writes, which isn't appropriate here since -the response may come in as multiple chunks. - {{id file_server}} ## A file server {{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. +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.js"} -const {createServer} = require("http"); +```{includeCode: ">code/file_server.mjs"} +import {createServer} from "node:http"; const methods = Object.create(null); createServer((request, response) => { let handler = methods[request.method] || notAllowed; - handler(request) - .catch(error => { - if (error.status != null) return error; - return {body: String(error), status: 500}; - }) - .then(({body, status = 200, type = "text/plain"}) => { - response.writeHead(status, {"Content-Type": type}); - if (body && body.pipe) body.pipe(response); - else response.end(body); - }); + handler(request).catch(error => { + if (error.status != null) return error; + return {body: String(error), status: 500}; + }).then(({body, status = 200, type = "text/plain"}) => { + response.writeHead(status, {"Content-Type": type}); + if (body?.pipe) body.pipe(response); + else response.end(body); + }); }).listen(8000); async function notAllowed(request) { @@ -762,48 +455,31 @@ async function notAllowed(request) { {{index "405 (HTTP status code)"}} -This starts a server that just returns 405 error responses, which is -the code used to indicate that the server refuses to handle a given -method. +This starts a server that just returns 405 error responses, which is the code used to indicate that the server refuses to handle a given method. {{index "500 (HTTP status code)", "error handling", "error response"}} -When a request handler's promise is rejected, the `catch` call -translates the error into a response object, if it isn't one already, so -that the server can send back an error response to inform the client -that it failed to handle the request. +When a request handler's promise is rejected, the `catch` call translates the error into a response object, if it isn't one already, so that the server can send back an error response to inform the client that it failed to handle the request. {{index "200 (HTTP status code)", "Content-Type header"}} -The `status` field of the response description may be omitted, in -which case it defaults to 200 (OK). The content type, in the `type` -property, can also be left off, in which case the response is assumed -to be plain text. +The `status` field of the response description may be omitted, in which case it defaults to 200 (OK). The content type, in the `type` property, can also be left off, in which case the response is assumed to be plain text. {{index "end method", "pipe method", stream}} -When the value of `body` is a ((readable stream)), it will have a -`pipe` method that is used to forward all content from a readable -stream to a ((writable stream)). If not, it is assumed to be either -`null` (no body), a string, or a buffer, and it is passed directly to the -((response))'s `end` method. +When the value of `body` is a ((readable stream)), it will have a `pipe` method that we can use to forward all content from a readable stream to a ((writable stream)). If not, it is assumed to be either `null` (no body), a string, or a buffer, and it is passed directly to the ((response))'s `end` method. -{{index [path, URL], "urlToPath function", "url package", parsing, [escaping, "in URLs"], "decodeURIComponent function", "startsWith method"}} +{{index [path, URL], "urlPath function", "URL class", parsing, [escaping, "in URLs"], "decodeURIComponent function", "startsWith method"}} -To figure out which file path corresponds to a request URL, the -`urlPath` function uses Node's built-in `url` module to parse the URL. -It takes 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. +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.js"} -const {parse} = require("url"); -const {resolve, sep} = require("path"); +```{includeCode: ">code/file_server.mjs"} +import {resolve, sep} from "node:path"; const baseDirectory = process.cwd(); function urlPath(url) { - let {pathname} = parse(url); + let {pathname} = new URL(url, "http://d"); let path = resolve(decodeURIComponent(pathname).slice(1)); if (path != baseDirectory && !path.startsWith(baseDirectory + sep)) { @@ -813,65 +489,38 @@ function urlPath(url) { } ``` -As soon as you set up a program to accept network requests, you have -to start worrying about ((security)). In this case, if we aren't -careful, it is likely that we'll accidentally expose our whole ((file -system)) to the network. - -File paths are strings in Node. To map such a string to an actual -file, there is a nontrivial amount of interpretation going on. Paths -may, for example, include `../` to refer to a parent directory. So -one obvious source of problems would be requests for paths like -`/../secret_file`. - -{{index "path package", "resolve function", "cwd function", "process object", "403 (HTTP status code)", "sep binding", ["backslash character", "as path separator"], "slash character"}} - -To avoid such problems, `urlPath` uses the `resolve` function from the -`path` module, which resolves relative paths. It then verifies that -the result is _below_ the working directory. The `process.cwd` -function (where `cwd` stands for "current working directory") can be -used to find this working directory. The `sep` binding from the -`path` package is the system's path separator—a backslash on Windows -and a forward slash on most other systems. When the path doesn't start -with the base directory, the function throws an error response object, -using the HTTP status code indicating that access to the resource is -forbidden. +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`. + +{{index "node:path package", "resolve function", "cwd function", "process object", "403 (HTTP status code)", "sep binding", ["backslash character", "as path separator"], "slash character"}} + +To avoid such problems, `urlPath` uses the `resolve` function from the `node:path` module, which resolves relative paths. It then verifies that the result is _below_ the working directory. The `process.cwd` function (where `cwd` stands for _current working directory_) can be used to find this working directory. The `sep` binding from the `node:path` package is the system's path separator—a backslash on Windows and a forward slash on most other systems. When the path doesn't start with the base directory, the function throws an error response object, using the HTTP status code indicating that access to the resource is forbidden. {{index "file server example", "Node.js", "GET method", [file, resource]}} -We'll set up the `GET` method to return a list of files when -reading a ((directory)) and to return the file's content when reading -a regular file. +We'll set up the `GET` method to return a list of files when reading a ((directory)) and to return the file's content when reading a regular file. -{{index "media type", "Content-Type header", "mime package"}} +{{index "media type", "Content-Type header", "mime-types package"}} -One tricky question is what kind of `Content-Type` header we should -set when returning a file's content. Since these files could be -anything, our server can't simply return the same content type for all -of them. ((NPM)) can help us again here. The `mime` package (content -type indicators like `text/plain` are also called _((MIME type))s_) -knows the correct type for a large number of ((file extension))s. +One tricky question is what kind of `Content-Type` header we should set when returning a file's content. Since these files could be anything, our server can't simply return the same content type for all of them. ((NPM)) can help us again here. The `mime-types` package (content type indicators like `text/plain` are also called _((MIME type))s_) knows the correct type for a large number of ((file extension))s. -{{index "require function", "npm program"}} +{{index "npm program"}} -The following `npm` command, in the directory where the server script -lives, installs a specific version of `mime`: +The following `npm` command, in the directory where the server script lives, installs a specific version of `mime`: ```{lang: null} -$ npm install mime@2.2.0 +$ npm install mime-types@2.1.0 ``` {{index "404 (HTTP status code)", "stat function", [file, resource]}} -When a requested file does not exist, the correct HTTP status code to -return is 404. We'll use the `stat` function, which looks up -information about a file, to find out both whether the file exists -and whether it is a ((directory)). +When a requested file does not exist, the correct HTTP status code to return is 404. We'll use the `stat` function, which looks up information about a file, to find out both whether the file exists and whether it is a ((directory)). -```{includeCode: ">code/file_server.js"} -const {createReadStream} = require("fs"); -const {stat, readdir} = require("fs").promises; -const mime = require("mime"); +```{includeCode: ">code/file_server.mjs"} +import {createReadStream} from "node:fs"; +import {stat, readdir} from "node:fs/promises"; +import {lookup} from "mime-types"; methods.GET = async function(request) { let path = urlPath(request.url); @@ -886,43 +535,31 @@ methods.GET = async function(request) { return {body: (await readdir(path)).join("\n")}; } else { return {body: createReadStream(path), - type: mime.getType(path)}; + type: lookup(path)}; } }; ``` {{index "createReadStream function", ["asynchronous programming", "in Node.js"], "error handling", "ENOENT (status code)", "Error type", inheritance}} -Because it has to touch the disk and thus might take a while, `stat` -is asynchronous. Since we're using promises rather than callback -style, it has to be imported from `promises` instead of directly from -`fs`. +Because it has to touch the disk and thus might take a while, `stat` is asynchronous. Since we're using promises rather than callback style, it has to be imported from `node:fs/promises` instead of directly from `node:fs`. -When the file does not exist, `stat` will throw an error object with a -`code` property of `"ENOENT"`. These somewhat obscure, -((Unix))-inspired codes are how you recognize error types in Node. +When the file does not exist, `stat` will throw an error object with a `code` property of `"ENOENT"`. These somewhat obscure, ((Unix))-inspired codes are how you recognize error types in Node. {{index "Stats type", "stat function", "isDirectory method"}} -The `stats` object returned by `stat` tells us a number of things -about a ((file)), such as its size (`size` property) and its -((modification date)) (`mtime` property). Here we are interested in -the question of whether it is a ((directory)) or a regular file, which -the `isDirectory` method tells us. +The `stats` object returned by `stat` tells us a number of things about a ((file)), such as its size (`size` property) and its ((modification date)) (`mtime` property). Here we are interested in the question of whether it is a ((directory)) or a regular file, which the `isDirectory` method tells us. {{index "readdir function"}} -We use `readdir` to read the array of files in a ((directory)) and -return it to the client. For normal files, we create a readable stream -with `createReadStream` and return that as the body, along with the -content type that the `mime` package gives us for the file's name. +We use `readdir` to read the array of files in a ((directory)) and return it to the client. For normal files, we create a readable stream with `createReadStream` and return that as the body, along with the content type that the `mime` package gives us for the file's name. {{index "Node.js", "file server example", "DELETE method", "rmdir function", "unlink function"}} The code to handle `DELETE` requests is slightly simpler. -```{includeCode: ">code/file_server.js"} -const {rmdir, unlink} = require("fs").promises; +```{includeCode: ">code/file_server.mjs"} +import {rmdir, unlink} from "node:fs/promises"; methods.DELETE = async function(request) { let path = urlPath(request.url); @@ -941,29 +578,18 @@ methods.DELETE = async function(request) { {{index "204 (HTTP status code)", "body (HTTP)"}} -When an ((HTTP)) ((response)) does not contain any data, the status -code 204 ("no content") can be used to indicate this. Since the -response to deletion doesn't need to transmit any information beyond -whether the operation succeeded, that is a sensible thing to return -here. +When an ((HTTP)) ((response)) does not contain any data, the status code 204 ("no content") can be used to indicate this. Since the response to deletion doesn't need to transmit any information beyond whether the operation succeeded, that is a sensible thing to return here. {{index idempotence, "error response"}} -You may be wondering why trying to delete a nonexistent file returns a -success status code, rather than an error. When the file that is being -deleted is not there, you could say that the request's objective is -already fulfilled. The ((HTTP)) standard encourages us to make -requests _idempotent_, which means that making the same request -multiple times produces the same result as making it once. In a way, -if you try to delete something that's already gone, the effect you -were trying to do has been achieved—the thing is no longer there. +You may be wondering why trying to delete a nonexistent file returns a success status code rather than an error. When the file being deleted is not there, you could say that the request's objective is already fulfilled. The ((HTTP)) standard encourages us to make requests _idempotent_, which means that making the same request multiple times produces the same result as making it once. In a way, if you try to delete something that's already gone, the effect you were trying to create has been achieved—the thing is no longer there. {{index "file server example", "Node.js", "PUT method"}} This is the handler for `PUT` requests: -```{includeCode: ">code/file_server.js"} -const {createWriteStream} = require("fs"); +```{includeCode: ">code/file_server.mjs"} +import {createWriteStream} from "node:fs"; function pipeStream(from, to) { return new Promise((resolve, reject) => { @@ -983,77 +609,42 @@ methods.PUT = async function(request) { {{index overwriting, "204 (HTTP status code)", "error event", "finish event", "createWriteStream function", "pipe method", stream}} -We don't need to check whether the file exists this time—if it does, -we'll just overwrite it. We again use `pipe` to move data from a -readable stream to a writable one, in this case from the request to -the file. But since `pipe` isn't written to return a promise, we have -to write a wrapper, `pipeStream`, that creates a promise around the -outcome of calling `pipe`. +We don't need to check whether the file exists this time—if it does, we'll just overwrite it. We again use `pipe` to move data from a readable stream to a writable one, in this case from the request to the file. But since `pipe` isn't written to return a promise, we have to write a wrapper, `pipeStream`, that creates a promise around the outcome of calling `pipe`. {{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 output stream to the request may also fail, for example if -the network goes down. So we wire up both streams' `"error"` events to -reject the promise. When `pipe` is done, it will close the output -stream, which causes it to fire a `"finish"` event. That's the point -where we can successfully resolve the promise (returning nothing). +When something goes wrong when opening the file, `createWriteStream` will still return a stream, but that stream will fire an `"error"` event. The stream from the request may also fail—for example, if the network goes down. So we wire up both streams' `"error"` events to reject the promise. When `pipe` is done, it will close the output stream, which causes it to fire a `"finish"` event. That's the point at which we can successfully resolve the promise (returning nothing). {{index download, "file server example", "Node.js"}} -The full script for the server is available at -[_https://eloquentjavascript.net/code/file_server.js_](https://eloquentjavascript.net/code/file_server.js). -You can download that and, after installing its dependencies, run it -with Node to start your own file server. And, of course, you can modify -and extend it to solve this chapter's exercises or to experiment. +The full script for the server is available at [_https://eloquentjavascript.net/code/file_server.mjs_](https://eloquentjavascript.net/code/file_server.mjs). You can download that and, after installing its dependencies, run it with Node to start your own file server. And, of course, you can modify and extend it to solve this chapter's exercises or to experiment. {{index "body (HTTP)", "curl program", [HTTP, client], [method, HTTP]}} -The command line tool `curl`, widely available on ((Unix))-like -systems (such as macOS and Linux), can be used to make HTTP -((request))s. The following session briefly tests our server. The `-X` -option is used to set the request's method, and `-d` is used to -include a request body. +The command line tool `curl`, widely available on ((Unix))-like systems (such as macOS and Linux), can be used to make HTTP ((request))s. The following session briefly tests our server. The `-X` option is used to set the request's method, and `-d` is used to include a request body. ```{lang: null} $ curl http://localhost:8000/file.txt File not found -$ curl -X PUT -d hello http://localhost:8000/file.txt +$ curl -X PUT -d CONTENT http://localhost:8000/file.txt $ curl http://localhost:8000/file.txt -hello +CONTENT $ curl -X DELETE http://localhost:8000/file.txt $ curl http://localhost:8000/file.txt File not found ``` -The first request for `file.txt` fails since the file does not exist -yet. The `PUT` request creates the file, and behold, the next request -successfully retrieves it. After deleting it with a `DELETE` request, -the file is again missing. +The first request for `file.txt` fails since the file does not exist yet. The `PUT` request creates the file, and behold, the next request successfully retrieves it. After deleting it with a `DELETE` request, the file is again missing. ## Summary {{index "Node.js"}} -Node is a nice, small system that lets us run JavaScript in a -nonbrowser context. It was originally designed for network tasks to -play the role of a _node_ in a network. But it lends itself to all -kinds of scripting tasks, and if writing JavaScript is something you -enjoy, automating tasks with Node works well. - -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 `fs` module for working with -the file system and the `http` module for running HTTP servers and -making HTTP requests. - -All input and output in Node is done asynchronously, unless you -explicitly use a synchronous variant of a function, such as -`readFileSync`. When calling such asynchronous functions, you provide -callback functions, and Node will call them with an error value and -(if available) a result when it is ready. +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 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 filesystem. ## Exercises @@ -1061,60 +652,35 @@ callback functions, and Node will call them with an error value and {{index grep, "search problem", "search tool (exercise)"}} -On ((Unix)) systems, there is a command line tool called `grep` that -can be used to quickly search files for a ((regular expression)). +On ((Unix)) systems, there is a command line tool called `grep` that can be used to quickly search files for a ((regular expression)). -Write a Node script that can be run from the ((command line)) and acts -somewhat like `grep`. It treats its first command line argument as a -regular expression and treats any further arguments as files to search. It -should output the names of any file whose content matches the regular -expression. +Write a Node script that can be run from the ((command line)) and acts somewhat like `grep`. It treats its first command line argument as a regular expression and treats any further arguments as files to search. It outputs the names of any file whose content matches the regular expression. -When that works, extend it so that when one of the arguments is a -((directory)), it searches through all files in that directory and its -subdirectories. +When that works, extend it so that when one of the arguments is a ((directory)), it searches through all files in that directory and its subdirectories. {{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 {{index "RegExp class", "search tool (exercise)"}} -Your first command line argument, the ((regular expression)), can be -found in `process.argv[2]`. The input files come after that. You can -use the `RegExp` constructor to go from a string to a regular -expression object. +Your first command line argument, the ((regular expression)), can be found in `process.argv[2]`. The input files come after that. You can use the `RegExp` constructor to go from a string to a regular expression object. {{index "readFileSync function"}} -Doing this synchronously, with `readFileSync`, is more -straightforward, but if you use `fs.promises` again to get -promise-returning functions and write an `async` function, the code -looks similar. +Doing this synchronously, with `readFileSync`, is more straightforward, but if you use `node:fs/promises` to get promise-returning functions and write an `async` function, the code looks similar. {{index "stat function", "statSync function", "isDirectory method"}} -To figure out whether something is a directory, you can again use -`stat` (or `statSync`) and the stats object's `isDirectory` method. +To figure out whether something is a directory, you can again use `stat` (or `statSync`) and the stats object's `isDirectory` method. {{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`. 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, putting a ((slash -character)) (`/`) between them. +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}} @@ -1122,20 +688,14 @@ hint}} {{index "file server example", "directory creation (exercise)", "rmdir function"}} -Though the `DELETE` method in our file server is able to delete -directories (using `rmdir`), the server currently does not provide any -way to _create_ a ((directory)). +Though the `DELETE` method in our file server is able to delete directories (using `rmdir`), the server currently does not provide any way to _create_ a ((directory)). {{index "MKCOL method", "mkdir function"}} -Add support for the `MKCOL` method ("make collection"), which should create -a directory by calling `mkdir` from the `fs` module. `MKCOL` is not a -widely used HTTP method, but it does exist for this same purpose in -the _((WebDAV))_ standard, which specifies a set of conventions on top -of ((HTTP)) that make it suitable for creating documents. +Add support for the `MKCOL` method ("make collection"), which should create a directory by calling `mkdir` from the `node:fs` module. `MKCOL` is not a widely used HTTP method, but it does exist for this same purpose in the _((WebDAV))_ standard, which specifies a set of conventions on top of ((HTTP)) that make it suitable for creating documents. -```{hidden: true, includeCode: ">code/file_server.js"} -const {mkdir} = require("fs").promises; +```{hidden: true, includeCode: ">code/file_server.mjs"} +import {mkdir} from "node:fs/promises"; methods.MKCOL = async function(request) { let path = urlPath(request.url); @@ -1156,12 +716,7 @@ methods.MKCOL = async function(request) { {{index "directory creation (exercise)", "file server example", "MKCOL method", "mkdir function", idempotency, "400 (HTTP status code)"}} -You can use the function that implements the `DELETE` method as a -blueprint for the `MKCOL` method. When no file is found, try to create -a directory with `mkdir`. When a directory exists at that path, you -can return a 204 response so that directory creation requests are -idempotent. If a nondirectory file exists here, return an error code. -Code 400 ("bad request") would be appropriate. +You can use the function that implements the `DELETE` method as a blueprint for the `MKCOL` method. When no file is found, try to create a directory with `mkdir`. When a directory exists at that path, you can return a 204 response so that directory creation requests are idempotent. If a nondirectory file exists here, return an error code. Code 400 ("bad request") would be appropriate. hint}} @@ -1169,61 +724,32 @@ hint}} {{index "public space (exercise)", "file server example", "Content-Type header", website}} -Since the file server serves up any kind of file and even includes the -right `Content-Type` header, you can use it to serve a website. Since -it allows everybody to delete and replace files, it would be an -interesting kind of website: one that can be modified, improved, and -vandalized by everybody who takes the time to create the right HTTP -request. +Since the file server serves up any kind of file and even includes the right `Content-Type` header, you can use it to serve a website. Given that this server allows everybody to delete and replace files, this would make for an interesting kind of website: one that can be modified, improved, and vandalized by everybody who takes the time to make the right HTTP request. -Write a basic ((HTML)) page that includes a simple JavaScript file. -Put the files in a directory served by the file server and open them -in your browser. +Write a basic ((HTML)) page that includes a simple JavaScript file. Put the files in a directory served by the file server and open them in your browser. -Next, as an advanced exercise or even a ((weekend project)), combine -all the knowledge you gained from this book to build a more -user-friendly interface for modifying the website—from _inside_ the -website. +Next, as an advanced exercise or even a ((weekend project)), combine all the knowledge you gained from this book to build a more user-friendly interface for modifying the website—from _inside_ the website. -Use an HTML ((form)) to edit the content of the files that make up the -website, allowing the user to update them on the server by using HTTP -requests, as described in [Chapter ?](http). +Use an HTML ((form)) to edit the content of the files that make up the website, allowing the user to update them on the server by using HTTP requests, as described in [Chapter ?](http). -Start by making only a single file editable. Then make it so that the -user can select which file to edit. Use the fact that our file server -returns lists of files when reading a directory. +Start by making only a single file editable. Then make it so that the user can select which file to edit. Use the fact that our file server returns lists of files when reading a directory. {{index overwriting}} -Don't work directly in the code exposed by the file server since if -you make a mistake, you are likely to damage the files there. Instead, -keep your work outside of the publicly accessible directory and copy -it there when testing. +Don't work directly in the code exposed by the file server, since if you make a mistake, you are likely to damage the files there. Instead, keep your work outside of the publicly accessible directory and copy it there when testing. {{hint {{index "file server example", "textarea (HTML tag)", "fetch function", "relative path", "public space (exercise)"}} -You can create a `<textarea>` element to hold the content of the file -that is being edited. A `GET` request, using `fetch`, can retrieve the -current content of the file. You can use relative URLs like -_index.html_, instead of -[_http://localhost:8000/index.html_](http://localhost:8000/index.html), -to refer to files on the same server as the running script. +You can create a `<textarea>` element to hold the content of the file that is being edited. A `GET` request, using `fetch`, can retrieve the current content of the file. You can use relative URLs like _index.html_, instead of [_http://localhost:8000/index.html_](http://localhost:8000/index.html), to refer to files on the same server as the running script. {{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"}} -You can then add a `<select>` element that contains all the files in -the server's top ((directory)) by adding `<option>` elements -containing the lines returned by a `GET` request to the URL `/`. When -the user selects another file (a `"change"` event on the field), the -script must fetch and display that file. When saving a file, use the -currently selected filename. +You can then add a `<select>` element that contains all the files in the server's top ((directory)) by adding `<option>` elements containing the lines returned by a `GET` request to the URL `/`. When the user selects another file (a `"change"` event on the field), the script must fetch and display that file. When saving a file, use the currently selected filename. hint}} diff --git a/21_skillsharing.md b/21_skillsharing.md index d53308d7e..1c8456fb3 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -10,153 +10,73 @@ quote}} {{index "skill-sharing project", meetup, "project chapter"}} -{{figure {url: "img/chapter_picture_21.jpg", alt: "Picture of two unicycles", chapter: "framed"}}} - -A _((skill-sharing))_ meeting is an event where people with a shared -interest come together and give small, informal presentations about -things they know. At a ((gardening)) skill-sharing meeting, someone -might explain how to cultivate ((celery)). Or in a programming -skill-sharing group, you could drop by and tell people about Node.js. - -{{index learning, "users' group"}} - -Such meetups—also often called _users' groups_ when they are about -computers—are a great way to broaden your horizon, learn about new -developments, or simply meet people with similar interests. Many -larger cities have JavaScript meetups. They are typically free to -attend, and I've found the ones I've visited to be friendly and -welcoming. - -In this final project chapter, our goal is to set up a ((website)) for -managing ((talk))s given at a skill-sharing meeting. Imagine a small -group of people meeting up regularly in the office of one of the -members to talk about ((unicycling)). The previous organizer of the -meetings moved to another town, and nobody stepped forward to take -over this task. We want a system that will let the participants -propose and discuss talks among themselves, without a central -organizer. - -[Just like in the [previous chapter](node), some of the code in this -chapter is written for Node.js, and running it directly in the HTML -page that you are looking at is unlikely to work.]{if interactive} The -full code for the project can be ((download))ed from -[_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip). +{{figure {url: "img/chapter_picture_21.jpg", alt: "Illustration showing two unicycles leaned against a mailbox", chapter: "framed"}}} + +A _((skill-sharing))_ meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a ((gardening)) skill-sharing meeting, someone might explain how to cultivate ((celery)). Or in a programming skill-sharing group, you could drop by and tell people about Node.js. + +In this final project chapter, our goal is to set up a ((website)) for managing ((talk))s given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about ((unicycling)). The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves without an active organizer. + +[Just like in the [previous chapter](node), some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work.]{if interactive} The full code for the project can be ((download))ed from [_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip). ## Design {{index "skill-sharing project", persistence}} -There is a _((server))_ part to this project, written for ((Node.js)), -and a _((client))_ part, written for the ((browser)). The server -stores the system's data and provides it to the client. It also serves -the files that implement the client-side system. +There is a _((server))_ part to this project, written for ((Node.js)), and a _((client))_ part, written for the ((browser)). The server stores the system's data and provides it to the client. It also serves the files that implement the client-side system. {{index [HTTP, client]}} -The server keeps the list of ((talk))s proposed for the next meeting, -and the client shows this list. Each talk has a presenter name, a -title, a summary, and an array of ((comment))s associated with it. The -client allows users to propose new talks (adding them to the list), -delete talks, and comment on existing talks. Whenever the user makes -such a change, the client makes an HTTP ((request)) to tell the -server about it. +The server keeps the list of ((talk))s proposed for the next meeting, and the client shows this list. Each talk has a presenter name, a title, a summary, and an array of ((comment))s associated with it. The client allows users to propose new talks (adding them to the list), delete talks, and comment on existing talks. Whenever the user makes such a change, the client makes an HTTP ((request)) to tell the server about it. -{{figure {url: "img/skillsharing.png", alt: "Screenshot of the skill-sharing website",width: "10cm"}}} +{{figure {url: "img/skillsharing.png", alt: "Screenshot of the skill-sharing website", width: "10cm"}}} {{index "live view", "user experience", "pushing data", connection}} -The ((application)) will be set up to show a _live_ view of the -current proposed talks and their comments. Whenever someone, -somewhere, submits a new talk or adds a comment, all people who have -the page open in their browsers should immediately see the change. -This poses a bit of a challenge—there is no way for a web server to -open a connection to a client, nor is there a good way to know which -clients are currently looking at a given website. +The ((application)) will be set up to show a _live_ view of the current proposed talks and their comments. Whenever someone, somewhere, submits a new talk or adds a comment, all people who have the page open in their browsers should immediately see the change. This poses a bit of a challenge—there is no way for a web server to open a connection to a client, nor is there a good way to know which clients are currently looking at a given website. {{index "Node.js"}} -A common solution to this problem is called _((long polling))_, which -happens to be one of the motivations for Node's design. +A common solution to this problem is called _((long polling))_, which happens to be one of the motivations for Node's design. ## 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. - -We can arrange for the client to open the connection and keep it -around so that the server can use it to send information when it needs -to do so. +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. {{index socket}} -But an ((HTTP)) request allows only a simple flow of information: the -client sends a request, the server comes back with a single response, -and that is it. There is a technology called _((WebSockets))_, -supported by modern browsers, that makes it possible to open -((connection))s for arbitrary data exchange. But using them properly -is somewhat tricky. +We can arrange for the client to open the connection and keep it around so that the server can use it to send information when it needs to do so. But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that's it. A technology called _((WebSockets))_ makes it possible to open ((connection))s for arbitrary data exchange, but using such sockets properly is somewhat tricky. -In this chapter, we use a simpler technique—((long polling))—where -clients continuously ask the server for new information using regular -HTTP requests, and the server stalls its answer when it has nothing -new to report. +In this chapter, we use a simpler technique, ((long polling)), where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report. {{index "live view"}} -As long as the client makes sure it constantly has a polling request -open, it will receive information from the server quickly after it -becomes available. For example, if Fatma has our skill-sharing -application open in her browser, that browser will have made a request -for updates and will be waiting for a response to that request. When Iman -submits a talk on Extreme Downhill Unicycling, the server will notice -that Fatma is waiting for updates and send a response containing the -new talk to her pending request. Fatma's browser will receive the data -and update the screen to show the talk. +As long as the client makes sure it constantly has a polling request open, it will receive information from the server quickly after it becomes available. For example, if Fatma has our skill-sharing application open in her browser, that browser will have made a request for updates and will be waiting for a response to that request. When Iman submits a talk on Extreme Downhill Unicycling, the server will notice that Fatma is waiting for updates and send a response containing the new talk to her pending request. Fatma's browser will receive the data and update the screen to show the talk. {{index robustness, timeout}} -To prevent connections from timing out (being aborted because of a -lack of activity), ((long polling)) techniques usually set a maximum -time for each request, after which the server will respond anyway, -even though it has nothing to report, after which the client will -start a new request. Periodically restarting the request also makes -the technique more robust, allowing clients to recover from temporary -((connection)) failures or server problems. +To prevent connections from timing out (being aborted because of a lack of activity), ((long polling)) techniques usually set a maximum time for each request, after which the server will respond anyway, even though it has nothing to report. The client can then start a new request. Periodically restarting the request also makes the technique more robust, allowing clients to recover from temporary ((connection)) failures or server problems. {{index "Node.js"}} -A busy server that is using long polling may have thousands of waiting -requests, and thus ((TCP)) connections, open. Node, which makes it -easy to manage many connections without creating a separate thread of -control for each one, is a good fit for such a system. +A busy server that is using long polling may have thousands of waiting requests, and thus ((TCP)) connections, open. Node, which makes it easy to manage many connections without creating a separate thread of control for each one, is a good fit for such a system. ## HTTP interface {{index "skill-sharing project", [interface, HTTP]}} -Before we start designing either the server or the client, let's think -about the point where they touch: the ((HTTP)) interface over -which they communicate. +Before we start designing either the server or the client, let's think about the point where they touch: the ((HTTP)) interface over which they communicate. {{index [path, URL], [method, HTTP]}} -We will use ((JSON)) as the format of our request and response body. -Like in the file server from [Chapter ?](node#file_server), we'll try -to make good use of HTTP methods and ((header))s. The interface is -centered around the `/talks` path. Paths that do not start with -`/talks` will be used for serving ((static file))s—the HTML and -JavaScript code for the client-side system. +We will use ((JSON)) as the format of our request and response body. Like in the file server from [Chapter ?](node#file_server), we'll try to make good use of HTTP methods and ((header))s. The interface is centered around the `/talks` path. Paths that do not start with `/talks` will be used for serving ((static file))s—the HTML and JavaScript code for the client-side system. {{index "GET method"}} A `GET` request to `/talks` returns a JSON document like this: -```{lang: "application/json"} +```{lang: "json"} [{"title": "Unituning", "presenter": "Jamal", "summary": "Modifying your cycle for extra style", @@ -165,24 +85,18 @@ A `GET` request to `/talks` returns a JSON document like this: {{index "PUT method", URL}} -Creating a new talk is done by making a `PUT` request to a URL like -`/talks/Unituning`, where the part after the second slash is the title -of the talk. The `PUT` request's body should contain a ((JSON)) object -that has `presenter` and `summary` properties. +Creating a new talk is done by making a `PUT` request to a URL like `/talks/Unituning`, where the part after the second slash is the title of the talk. The `PUT` request's body should contain a ((JSON)) object that has `presenter` and `summary` properties. {{index "encodeURIComponent function", [escaping, "in URLs"], [whitespace, "in URLs"]}} -Since talk titles may contain spaces and other characters that may not -appear normally in a URL, title strings must be encoded with the -`encodeURIComponent` function when building up such a URL. +Since talk titles may contain spaces and other characters that may not appear normally in a URL, title strings must be encoded with the `encodeURIComponent` function when building up such a URL. ``` console.log("/talks/" + encodeURIComponent("How to Idle")); // → /talks/How%20to%20Idle ``` -A request to create a talk about idling might look something like -this: +A request to create a talk about idling might look something like this: ```{lang: http} PUT /talks/How%20to%20Idle HTTP/1.1 @@ -193,14 +107,11 @@ Content-Length: 92 "summary": "Standing still on a unicycle"} ``` -Such URLs also support `GET` requests to retrieve the JSON -representation of a talk and `DELETE` requests to delete a talk. +Such URLs also support `GET` requests to retrieve the JSON representation of a talk and `DELETE` requests to delete a talk. {{index "POST method"}} -Adding a ((comment)) to a talk is done with a `POST` request to a URL -like `/talks/Unituning/comments`, with a JSON body that has `author` -and `message` properties. +Adding a ((comment)) to a talk is done with a `POST` request to a URL like `/talks/Unituning/comments`, with a JSON body that has `author` and `message` properties. ```{lang: http} POST /talks/Unituning/comments HTTP/1.1 @@ -213,37 +124,17 @@ Content-Length: 72 {{index "query string", timeout, "ETag header", "If-None-Match header"}} -To support ((long polling)), `GET` requests to `/talks` may include -extra headers that inform the server to delay the response if no new -information is available. We'll use a pair of headers normally -intended to manage caching: `ETag` and `If-None-Match`. +To support ((long polling)), `GET` requests to `/talks` may include extra headers that inform the server to delay the response if no new information is available. We'll use a pair of headers normally intended to manage caching: `ETag` and `If-None-Match`. {{index "304 (HTTP status code)"}} -Servers may include an `ETag` ("entity tag") header in a response. Its -value is a string that identifies the current version of the resource. -Clients, when they later request that resource again, may make a -_((conditional request))_ by including an `If-None-Match` header whose -value holds that same string. If the resource hasn't changed, the -server will respond with status code 304, which means "not modified", -telling the client that its cached version is still current. When the -tag does not match, the server responds as normal. +Servers may include an `ETag` ("entity tag") header in a response. Its value is a string that identifies the current version of the resource. Clients, when they later request that resource again, may make a _((conditional request))_ by including an `If-None-Match` header whose value holds that same string. If the resource hasn't changed, the server will respond with status code 304, which means "not modified", telling the client that its cached version is still current. When the tag does not match, the server responds as normal. {{index "Prefer header"}} -We need something like this, where the client can tell the server -which version of the list of talks it has, and the server -responds only when that list has changed. But instead of immediately -returning a 304 response, the server should stall the response and -return only when something new is available or a given amount of time -has elapsed. To distinguish long polling requests from normal -conditional requests, we give them another header, `Prefer: wait=90`, -which tells the server that the client is willing to wait up to 90 -seconds for the response. +We need something like this, where the client can tell the server which version of the list of talks it has, and the server responds only when that list has changed. But instead of immediately returning a 304 response, the server should stall the response and return only when something new is available or a given amount of time has elapsed. To distinguish long polling requests from normal conditional requests, we give them another header, `Prefer: wait=90`, which tells the server that the client is willing to wait up to 90 seconds for the response. -The server will keep a version number that it updates every time the -talks change and will use that as the `ETag` value. Clients can make -requests like this to be notified when the talks change: +The server will keep a version number that it updates every time the talks change and will use that as the `ETag` value. Clients can make requests like this to be notified when the talks change: ```{lang: null} GET /talks HTTP/1.1 @@ -257,126 +148,79 @@ Content-Type: application/json ETag: "5" Content-Length: 295 -[....] +[...] ``` {{index security}} -The protocol described here does not do any ((access control)). -Everybody can comment, modify talks, and even delete them. (Since the -Internet is full of ((hooligan))s, putting such a system online -without further protection probably wouldn't end well.) +The protocol described here doesn't do any ((access control)). Everybody can comment, modify talks, and even delete them. (Since the internet is full of ((hooligan))s, putting such a system online without further protection probably wouldn't end well.) ## The server {{index "skill-sharing project"}} -Let's start by building the ((server))-side part of the program. The -code in this section runs on ((Node.js)). +Let's start by building the ((server))-side part of the program. The code in this section runs on ((Node.js)). ### Routing {{index "createServer function", [path, URL], [method, HTTP]}} -Our server will use `createServer` to start an HTTP server. In the -function that handles a new request, we must distinguish between the -various kinds of requests (as determined by the method and the -path) that we support. This can be done with a long chain of `if` -statements, but there is a nicer way. +Our server will use Node's `createServer` to start an HTTP server. In the function that handles a new request, we must distinguish between the various kinds of requests (as determined by the method and the path) that we support. This can be done with a long chain of `if` statements, but there's a nicer way. {{index dispatch}} -A _((router))_ is a component that helps dispatch a request to the -function that can handle it. You can tell the router, for example, -that `PUT` requests with a path that matches the regular expression -`/^\/talks\/([^\/]+)$/` (`/talks/` followed by a talk title) can be -handled by a given function. In addition, it can help extract the -meaningful parts of the path (in this case the talk title), wrapped in -parentheses in the ((regular expression)), and pass them to the -handler function. - -There are a number of good router packages on ((NPM)), but here we'll -write one ourselves to illustrate the principle. +A _((router))_ is a component that helps dispatch a request to the function that can handle it. You can tell the router, for example, that `PUT` requests with a path that matches the regular expression `/^\/talks\/([^\/]+)$/` (`/talks/` followed by a talk title) can be handled by a given function. In addition, it can help extract the meaningful parts of the path (in this case the talk title), wrapped in parentheses in the ((regular expression)), and pass them to the handler function. -{{index "require function", "Router class", module}} +There are a number of good router packages on ((NPM)), but here we'll write one ourselves to illustrate the principle. -This is `router.js`, which we will later `require` from our server -module: +{{index "import keyword", "Router class", module}} -```{includeCode: ">code/skillsharing/router.js"} -const {parse} = require("url"); +This is `router.mjs`, which we will later `import` from our server module: -module.exports = class Router { +```{includeCode: ">code/skillsharing/router.mjs"} +export class Router { constructor() { this.routes = []; } add(method, url, handler) { this.routes.push({method, url, handler}); } - resolve(context, request) { - let path = parse(request.url).pathname; - + async resolve(request, context) { + let {pathname} = new URL(request.url, "http://d"); for (let {method, url, handler} of this.routes) { - let match = url.exec(path); + let match = url.exec(pathname); if (!match || request.method != method) continue; - let urlParts = match.slice(1).map(decodeURIComponent); - return handler(context, ...urlParts, request); + let parts = match.slice(1).map(decodeURIComponent); + return handler(context, ...parts, request); } - return null; } -}; +} ``` {{index "Router class"}} -The module exports the `Router` class. A router object allows new -handlers to be registered with the `add` method and can resolve -requests with its `resolve` method. - -{{index "some method"}} - -The latter will return a response when a handler was found, and `null` -otherwise. It tries the routes one at a time (in the order in which -they were defined) until a matching one is found. +The module exports the `Router` class. A router object allows you to register handlers for specific methods and URL patterns with its `add` method. When a request is resolved with the `resolve` method, the router calls the handler whose method and URL match the request and return its result. {{index "capture group", "decodeURIComponent function", [escaping, "in URLs"]}} -The handler functions are called with the `context` value (which -will be the server instance in our case), 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 -When a request matches none of the request types defined in our -router, the server must interpret it as a request for a file in the -`public` directory. It would be possible to use the file server -defined in [Chapter ?](node#file_server) to serve such files, but we -neither need nor want to support `PUT` and `DELETE` requests on files, -and we would like to have advanced features such as support for -caching. So let's use a solid, well-tested ((static file)) server from -((NPM)) instead. - -{{index "createServer function", "ecstatic package"}} - -I opted for `ecstatic`. This isn't the only such server on NPM, but it -works well and fits our purposes. The `ecstatic` package exports a -function that can be called with a configuration object to produce a -request handler function. We use the `root` option to tell the server -where it should look for files. The handler function accepts `request` -and `response` parameters and can be passed directly to `createServer` -to create a server that serves _only_ files. We want to first check -for requests that we should handle specially, though, so we wrap it in -another function. - -```{includeCode: ">code/skillsharing/skillsharing_server.js"} -const {createServer} = require("http"); -const Router = require("./router"); -const ecstatic = require("ecstatic"); +When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the `public` directory. It would be possible to use the file server defined in [Chapter ?](node#file_server) to serve such files, but we neither need nor want to support `PUT` and `DELETE` requests on files, and we would like to have advanced features such as support for caching. Let's use a solid, well-tested ((static file)) server from ((NPM)) instead. -const router = new Router(); -const defaultHeaders = {"Content-Type": "text/plain"}; +{{index "createServer function", "serve-static package"}} + +I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server from `"node:http"`, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests we should handle specially, as defined in the router, so we wrap it in another function. + +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +import {createServer} from "node:http"; +import serveStatic from "serve-static"; + +function notFound(request, response) { + response.writeHead(404, "Not found"); + response.end("<h1>Not found</h1>"); +} class SkillShareServer { constructor(talks) { @@ -384,22 +228,12 @@ class SkillShareServer { this.version = 0; this.waiting = []; - let fileServer = ecstatic({root: "./public"}); + let fileServer = serveStatic("./public"); this.server = createServer((request, response) => { - let resolved = router.resolve(this, request); - if (resolved) { - resolved.catch(error => { - if (error.status != null) return error; - return {body: String(error), status: 500}; - }).then(({body, - status = 200, - headers = defaultHeaders}) => { - response.writeHead(status, headers); - response.end(body); - }); - } else { - fileServer(request, response); - } + serveFromRouter(this, request, response, () => { + fileServer(request, response, + () => notFound(request, response)); + }); }); } start(port) { @@ -411,30 +245,44 @@ class SkillShareServer { } ``` -This uses a similar convention as the file server from the [previous -chapter](node) for responses—handlers return promises that resolve to -objects describing the response. It wraps the server in an object that -also holds its state. +The `serveFromRouter` function has the same interface as `fileServer`, taking `(request, response, next)` arguments. We can use this to “chain” several request handlers, allowing each to either handle the request or pass responsibility for that on to the next handler. The final handler, `notFound`, simply responds with a “not found” error. + +Our `serveFromRouter` function uses a similar convention to the file server from the [previous chapter](node) for responses—handlers in the router return promises that resolve to objects describing the response. + +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +import {Router} from "./router.mjs"; + +const router = new Router(); +const defaultHeaders = {"Content-Type": "text/plain"}; + +async function serveFromRouter(server, request, + response, next) { + let resolved = await router.resolve(request, server) + .catch(error => { + if (error.status != null) return error; + return {body: String(err), status: 500}; + }); + if (!resolved) return next(); + let {body, status = 200, headers = defaultHeaders} = + await resolved; + response.writeHead(status, headers); + response.end(body); +} +``` ### 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. These will be exposed as HTTP ((resource))s under -`/talks/[title]`, so we need to add handlers to our router that -implement the various methods that clients can use to work with them. +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)"}} +{{index "GET method", "404 (HTTP status code)" "hasOwn function"}} -The handler for requests that `GET` a single talk must look up the -talk and respond either with the talk's JSON data or with a 404 error -response. +The handler for requests that `GET` a single talk must look up the talk and respond either with the talk's JSON data or with a 404 error response. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} const talkPath = /^\/talks\/([^\/]+)$/; router.add("GET", talkPath, async (server, title) => { - if (title in server.talks) { + if (Object.hasOwn(server.talks, title)) { return {body: JSON.stringify(server.talks[title]), headers: {"Content-Type": "application/json"}}; } else { @@ -447,9 +295,9 @@ router.add("GET", talkPath, async (server, title) => { Deleting a talk is done by removing it from the `talks` object. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} router.add("DELETE", talkPath, async (server, title) => { - if (title in server.talks) { + if (Object.hasOwn(server.talks, title)) { delete server.talks[title]; server.updated(); } @@ -459,83 +307,53 @@ router.add("DELETE", talkPath, async (server, title) => { {{index "long polling", "updated method"}} -The `updated` method, which we will define -[later](skillsharing#updated), notifies waiting long polling requests -about the change. +The `updated` method, which we will define [later](skillsharing#updated), notifies waiting long polling requests about the change. -{{index "readStream function", "body (HTTP)", stream}} +{{index validation, input, "PUT method"}} -To retrieve the content of a request body, we define a function called -`readStream`, which reads all content from a ((readable stream)) and -returns a promise that resolves to a string. +One handler that needs to read request bodies is the `PUT` handler, which is used to create new ((talk))s. It has to check whether the data it was given has `presenter` and `summary` properties, which are strings. Any data coming from outside the system might be nonsense, and we don't want to corrupt our internal data model or ((crash)) when bad requests come in. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} -function readStream(stream) { - return new Promise((resolve, reject) => { - let data = ""; - stream.on("error", reject); - stream.on("data", chunk => data += chunk.toString()); - stream.on("end", () => resolve(data)); - }); -} -``` +{{index "updated method"}} -{{index validation, input, "PUT method"}} +If the data looks valid, the handler stores an object that represents the new talk in the `talks` object, possibly ((overwriting)) an existing talk with this title, and again calls `updated`. -One handler that needs to read request bodies is the `PUT` handler, -which is used to create new ((talk))s. It has to check whether the -data it was given has `presenter` and `summary` properties, which are -strings. Any data coming from outside the system might be nonsense, -and we don't want to corrupt our internal data model or ((crash)) when -bad requests come in. +{{index "node:stream/consumers package", JSON, "readable stream"}} -{{index "updated method"}} +To read the body from the request stream, we will use the `json` function from `"node:stream/consumers"`, which collects the data in the stream and then parses it as JSON. There are similar exports called `text` (to read the content as a string) and `buffer` (to read it as binary data) in this package. Since `json` is a very generic name, the import renames it to `readJSON` to avoid confusion. -If the data looks valid, the handler stores an object that represents -the new talk in the `talks` object, possibly ((overwriting)) an -existing talk with this title, and again calls `updated`. +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +import {json as readJSON} from "node:stream/consumers"; -```{includeCode: ">code/skillsharing/skillsharing_server.js"} router.add("PUT", talkPath, async (server, title, request) => { - let requestBody = await readStream(request); - let talk; - try { talk = JSON.parse(requestBody); } - catch (_) { return {status: 400, body: "Invalid JSON"}; } - + let talk = await readJSON(request); if (!talk || typeof talk.presenter != "string" || typeof talk.summary != "string") { return {status: 400, body: "Bad talk data"}; } - server.talks[title] = {title, - presenter: talk.presenter, - summary: talk.summary, - comments: []}; + server.talks[title] = { + title, + presenter: talk.presenter, + summary: talk.summary, + comments: [] + }; server.updated(); return {status: 204}; }); ``` -{{index validation, "readStream function"}} - -Adding a ((comment)) to a ((talk)) works similarly. We use -`readStream` to get the content of the request, validate the resulting -data, and store it as a comment when it looks valid. +Adding a ((comment)) to a ((talk)) works similarly. We use `readJSON` to get the content of the request, validate the resulting data, and store it as a comment when it looks valid. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} router.add("POST", /^\/talks\/([^\/]+)\/comments$/, async (server, title, request) => { - let requestBody = await readStream(request); - let comment; - try { comment = JSON.parse(requestBody); } - catch (_) { return {status: 400, body: "Invalid JSON"}; } - + let comment = await readJSON(request); if (!comment || typeof comment.author != "string" || typeof comment.message != "string") { return {status: 400, body: "Bad comment data"}; - } else if (title in server.talks) { + } else if (Object.hasOwn(server.talks, title)) { server.talks[title].comments.push(comment); server.updated(); return {status: 204}; @@ -551,22 +369,16 @@ Trying to add a comment to a nonexistent talk returns a 404 error. ### Long polling support -The most interesting aspect of the server is the part that handles -((long polling)). When a `GET` request comes in for `/talks`, it may -be either a regular request or a long polling request. +The most interesting aspect of the server is the part that handles ((long polling)). When a `GET` request comes in for `/talks`, it may be either a regular request or a long polling request. {{index "talkResponse method", "ETag header"}} -There will be multiple places in which we have to send an array of -talks to the client, so we first define a helper method that builds up -such an array and includes an `ETag` header in the response. +There will be multiple places in which we have to send an array of talks to the client, so we first define a helper method that builds up such an array and includes an `ETag` header in the response. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} SkillShareServer.prototype.talkResponse = function() { - let talks = []; - for (let title of Object.keys(this.talks)) { - talks.push(this.talks[title]); - } + let talks = Object.keys(this.talks) + .map(title => this.talks[title]); return { body: JSON.stringify(talks), headers: {"Content-Type": "application/json", @@ -578,12 +390,9 @@ 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.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} router.add("GET", /^\/talks$/, async (server, request) => { let tag = /"(.*)"/.exec(request.headers["if-none-match"]); let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]); @@ -599,20 +408,13 @@ router.add("GET", /^\/talks$/, async (server, request) => { {{index "long polling", "waitForChanges method", "If-None-Match header", "Prefer header"}} -If no tag was given or a tag was given that doesn't match the -server's current version, the handler responds with the list of talks. -If the request is conditional and the talks did not change, we consult -the `Prefer` header to see whether we should delay the response or respond -right away. +If no tag was given or a tag was given that doesn't match the server's current version, the handler responds with the list of talks. If the request is conditional and the talks did not change, we consult the `Prefer` header to see whether we should delay the response or respond right away. {{index "304 (HTTP status code)", "setTimeout function", timeout, "callback function"}} -Callback functions for delayed requests are stored in the server's -`waiting` array so that they can be notified when something happens. -The `waitForChanges` method also immediately sets a timer to respond -with a 304 status when the request has waited long enough. +Callback functions for delayed requests are stored in the server's `waiting` array so that they can be notified when something happens. The `waitForChanges` method also immediately sets a timer to respond with a 304 status when the request has waited long enough. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} SkillShareServer.prototype.waitForChanges = function(time) { return new Promise(resolve => { this.waiting.push(resolve); @@ -629,10 +431,9 @@ SkillShareServer.prototype.waitForChanges = function(time) { {{id updated}} -Registering a change with `updated` increases the `version` property -and wakes up all waiting requests. +Registering a change with `updated` increases the `version` property and wakes up all waiting requests. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} SkillShareServer.prototype.updated = function() { this.version++; let response = this.talkResponse(); @@ -643,38 +444,27 @@ SkillShareServer.prototype.updated = function() { {{index [HTTP, server]}} -That concludes the server code. If we create an instance of -`SkillShareServer` and start it on port 8000, the resulting HTTP -server serves files from the `public` subdirectory alongside a -talk-managing interface under the `/talks` URL. +That concludes the server code. If we create an instance of `SkillShareServer` and start it on port 8000, the resulting HTTP server serves files from the `public` subdirectory alongside a talk-managing interface under the `/talks` URL. -```{includeCode: ">code/skillsharing/skillsharing_server.js"} -new SkillShareServer(Object.create(null)).start(8000); +```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +new SkillShareServer({}).start(8000); ``` ## The client {{index "skill-sharing project"}} -The ((client))-side part of the skill-sharing website consists of -three files: a tiny HTML page, a style sheet, and a JavaScript file. +The ((client))-side part of the skill-sharing website consists of three files: a tiny HTML page, a style sheet, and a JavaScript file. ### HTML {{index "index.html"}} -It is a widely used convention for web servers to try to serve a file -named `index.html` when a request is made directly to a path that -corresponds to a directory. The ((file server)) module we use, -`ecstatic`, supports this convention. When a request is made to the -path `/`, the server looks for the file `./public/index.html` -(`./public` being the root we gave it) and returns that file if found. +It is a widely used convention for web servers to try to serve a file named `index.html` when a request is made directly to a path that corresponds to a directory. The ((file server)) module we use, `serve-static`, supports this convention. When a request is made to the path `/`, the server looks for the file `./public/index.html` (`./public` being the root we gave it) and returns that file if found. -Thus, if we want a page to show up when a browser is pointed at our -server, we should put it in `public/index.html`. This is our index -file: +Thus, if we want a page to show up when a browser is pointed at our server, we should put it in `public/index.html`. This is our index file: -```{lang: "text/html", includeCode: ">code/skillsharing/public/index.html"} +```{lang: "html", includeCode: ">code/skillsharing/public/index.html"} <!doctype html> <meta charset="utf-8"> <title>Skill Sharing @@ -687,34 +477,23 @@ file: {{index CSS}} -It defines the document ((title)) and includes a style sheet, -which defines a few styles to, among other things, make sure there is -some space between talks. - -At the bottom, it adds a heading at the top of the page and loads the -script that contains the ((client))-side application. +It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks. It then adds a heading at the top of the page and loads the script that contains the ((client))-side application. ### Actions -The application state consists of the list of talks and the name of -the user, and we'll store it in a `{talks, user}` object. We don't -allow the user interface to directly manipulate the state or send off -HTTP requests. Rather, it may emit _actions_ that describe what the -user is trying to do. +The application state consists of the list of talks and the name of the user, and we'll store it in a `{talks, user}` object. We don't allow the user interface to directly manipulate the state or send off HTTP requests. Rather, it may emit _actions_ that describe what the user is trying to do. {{index "handleAction function"}} -The `handleAction` function takes such an action and makes it happen. -Because our state updates are so simple, state changes are handled in -the same function. +The `handleAction` function takes such an action and makes it happen. Because our state updates are so simple, state changes are handled in the same function. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function handleAction(state, action) { if (action.type == "setUser") { localStorage.setItem("userName", action.user); - return Object.assign({}, state, {user: action.user}); + return {...state, user: action.user}; } else if (action.type == "setTalks") { - return Object.assign({}, state, {talks: action.talks}); + return {...state, talks: action.talks}; } else if (action.type == "newTalk") { fetchOK(talkURL(action.title), { method: "PUT", @@ -743,15 +522,11 @@ function handleAction(state, action) { {{index "localStorage object"}} -We'll store the user's name in `localStorage` so that it can be -restored when the page is loaded. +We'll store the user's name in `localStorage` so that it can be restored when the page is loaded. {{index "fetch function", "status property"}} -The actions that need to involve the server make network requests, -using `fetch`, to the HTTP interface described earlier. We use a -wrapper function, `fetchOK`, which makes sure the returned promise is -rejected when the server returns an error code. +The actions that need to involve the server make network requests, using `fetch`, to the HTTP interface described earlier. We use a wrapper function, `fetchOK`, which makes sure the returned promise is rejected when the server returns an error code. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function fetchOK(url, options) { @@ -764,8 +539,7 @@ function fetchOK(url, options) { {{index "talkURL function", "encodeURIComponent function"}} -This helper function is used to build up a ((URL)) for a talk -with a given title. +This helper function is used to build up a ((URL)) for a talk with a given title. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function talkURL(title) { @@ -775,10 +549,7 @@ function talkURL(title) { {{index "error handling", "user experience", "reportError function"}} -When the request fails, we don't want to have our page just sit there, -doing nothing without explanation. So we define a function called -`reportError`, which at least shows the user a dialog that tells them -something went wrong. +When the request fails, we don't want our page to just sit there doing nothing without explanation. The function called `reportError`, which we used as 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) { @@ -790,12 +561,7 @@ function reportError(error) { {{index "renderUserField function"}} -We'll use an approach similar to the one we saw in [Chapter ?](paint), -splitting the application into components. But since some of the -components either never need to update or are always fully redrawn -when updated, we'll define those not as classes but as functions that -directly return a DOM node. For example, here is a component that -shows the field where the user can enter their name: +We'll use an approach similar to the one we saw in [Chapter ?](paint), splitting the application into components. However, since some of the components either never need to update or are always fully redrawn when updated, we'll define those not as classes but as functions that directly return a DOM node. For example, here is a component that shows the field where the user can enter their name: ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function renderUserField(name, dispatch) { @@ -811,8 +577,7 @@ function renderUserField(name, dispatch) { {{index "elt function"}} -The `elt` function used to construct DOM elements is the one we used -in [Chapter ?](paint). +The `elt` function used to construct DOM elements is the one we used in [Chapter ?](paint). ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, hidden: true} function elt(type, props, ...children) { @@ -828,8 +593,7 @@ function elt(type, props, ...children) { {{index "renderTalk function"}} -A similar function is used to render talks, which include a list of -comments and a form for adding a new ((comment)). +A similar function is used to render talks, which include a list of comments and a form for adding a new ((comment)). ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function renderTalk(talk, dispatch) { @@ -861,19 +625,11 @@ function renderTalk(talk, dispatch) { {{index "submit event"}} -The `"submit"` event handler calls `form.reset` to clear the form's -content after creating a `"newComment"` action. +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. There's a widely used -(non-standard) JavaScript extension called _((JSX))_ that lets you -write HTML directly in your scripts, which can make such code prettier -(depending on what you consider pretty). Before you can actually run -such code, you have to run a program on your script to convert the -pseudo-HTML into JavaScript function calls much like the ones we use -here. +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 simpler to render. +Comments are simple to render. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function renderComment(comment) { @@ -885,8 +641,7 @@ function renderComment(comment) { {{index "form (HTML tag)", "renderTalkForm function"}} -Finally, the form that the user can use to create a new talk is -rendered like this: +Finally, the form that the user can use to create a new talk is rendered like this: ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} function renderTalkForm(dispatch) { @@ -911,11 +666,7 @@ function renderTalkForm(dispatch) { {{index "pollTalks function", "long polling", "If-None-Match header", "Prefer header", "fetch function"}} -To start the app we need the current list of talks. Since the initial -load is closely related to the long polling process—the `ETag` from -the load must be used when polling—we'll write a function that keeps -polling the server for `/talks` and calls a ((callback function)) -when a new set of talks is available. +To start the app, we need the current list of talks. Since the initial load is closely related to the long polling process—the `ETag` from the load must be used when polling—we'll write a function that keeps polling the server for `/talks` and calls a ((callback function)) when a new set of talks is available. ```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} async function pollTalks(update) { @@ -941,27 +692,15 @@ async function pollTalks(update) { {{index "async function"}} -This is an `async` function so that looping and waiting for the -request is easier. It runs an infinite loop that, on each iteration, -retrieves the list of talks—either normally or, if this isn't the -first request, with the headers included that make it a long polling -request. +This is an `async` function so that looping and waiting for the request is easier. It runs an infinite loop that, on each iteration, retrieves the list of talks—either normally or, if this isn't the first request, with the headers included that make it a long polling request. {{index "error handling", "Promise class", "setTimeout function"}} -When a request fails, the function waits a moment and then tries -again. This way, if your network connection goes away for a while and -then comes back, the application can recover and continue updating. -The promise resolved via `setTimeout` is a way to force the `async` -function to wait. +When a request fails, the function waits a moment and then tries again. This way, if your network connection goes away for a while and then comes back, the application can recover and continue updating. The promise resolved via `setTimeout` is a way to force the `async` function to wait. {{index "304 (HTTP status code)", "ETag header"}} -When the server gives back a 304 response, that means a long polling -request timed out, so the function should just immediately start the -next request. If the response is a normal 200 response, its body is -read as ((JSON)) and passed to the callback, and its `ETag` header -value is stored for the next iteration. +When the server gives back a 304 response, that means a long polling request timed out, so the function should just immediately start the next request. If the response is a normal 200 response, its body is read as ((JSON)) and passed to the callback, and its `ETag` header value is stored for the next iteration. ### The application @@ -996,8 +735,7 @@ class SkillShareApp { {{index synchronization, "live view"}} -When the talks change, this component redraws all of them. This is -simple but also wasteful. We'll get back to that in the exercises. +When the talks change, this component redraws all of them. This is simple but also wasteful. We'll get back to that in the exercises. We can start the application like this: @@ -1024,60 +762,33 @@ function runApp() { runApp(); ``` -If you run the server and open two browser windows for -[_http://localhost:8000_](http://localhost:8000/) next to each other, you can -see that the actions you perform in one window are immediately visible -in the other. +If you run the server and open two browser windows for [_http://localhost:8000_](http://localhost:8000/) next to each other, you can see that the actions you perform in one window are immediately visible in the other. ## Exercises {{index "Node.js", NPM}} -The following exercises will involve modifying the system defined in -this chapter. To work on them, make sure you ((download)) the code -first -([_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip)), -have Node installed [_https://nodejs.org_](https://nodejs.org), and have -installed the project's dependency with `npm install`. +The following exercises will involve modifying the system defined in this chapter. To work on them, make sure you've ((download))ed the code ([_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip)), installed Node ([_https://nodejs.org_](https://nodejs.org)), and installed the project's dependency with `npm install`. ### Disk persistence {{index "data loss", persistence, [memory, persistence]}} -The skill-sharing server keeps its data purely in memory. This -means that when it ((crash))es or is restarted for any reason, all -talks and comments are lost. +The skill-sharing server keeps its data purely in memory. This means that when it ((crash))es or is restarted for any reason, all talks and comments are lost. {{index "hard drive"}} -Extend the server so that it stores the talk data to disk and -automatically reloads the data when it is restarted. Do not worry -about efficiency—do the simplest thing that works. +Extend the server so that it stores the talk data to disk and automatically reloads the data when it is restarted. Don't worry about efficiency—do the simplest thing that works. {{hint -{{index "file system", "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. - -{{index "readFile function"}} +{{index "filesystem", "writeFile function", "updated method", persistence}} -Pick a ((file))name, for example `./talks.json`. When the server -starts, it can try to read that file with `readFile`, and if that -succeeds, the server can use the file's contents as its starting data. +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. -{{index prototype, "JSON.parse function"}} +{{index "readFile function", "JSON.parse function"}} -Beware, though. The `talks` object started as a prototype-less object -so that the `in` operator could reliably be used. `JSON.parse` will -return regular objects with `Object.prototype` as their prototype. If -you use JSON as your file format, you'll have to copy the properties -of the object returned by `JSON.parse` into a new, prototype-less -object. +Pick a ((file))name, for example `./talks.json`. When the server starts, it can try to read that file with `readFile`, and if that succeeds, the server can use the file's contents as its starting data. hint}} @@ -1085,40 +796,20 @@ hint}} {{index "comment field reset (exercise)", template, [state, "of application"]}} -The wholesale redrawing of talks works pretty well because you usually -can't tell the difference between a DOM node and its identical -replacement. But there are exceptions. If you start typing something -in the comment ((field)) for a talk in one browser window and then, in -another, add a comment to that talk, the field in the first window -will be redrawn, removing both its content and its ((focus)). +The wholesale redrawing of talks works pretty well because you usually can't tell the difference between a DOM node and its identical replacement. But there are exceptions. If you start typing something in the comment ((field)) for a talk in one browser window and then, in another, add a comment to that talk, the field in the first window will be redrawn, removing both its content and its ((focus)). -In a heated discussion, where multiple people are adding comments at -the same time, this would be annoying. Can you come up with a way -to solve it? +When multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it? {{hint {{index "comment field reset (exercise)", template, "syncState method"}} -The best way to do this is probably to make talks component objects, -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 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"}} -To do this, it might be helpful to keep a data structure that stores -the talk components under the talk titles so that you can easily -figure out whether a component exists for a given talk. You can then -loop over the new array of talks, and for each of them, either -synchronize an existing component or create a new one. To delete -components for deleted talks, you'll have to also loop over the -components and check whether the corresponding talks still exist. +To do this, it might be helpful to keep a data structure that stores the talk components under the talk titles so that you can easily figure out whether a component exists for a given talk. You can then loop over the new array of talks, and for each of them, either synchronize an existing component or create a new one. To delete components for deleted talks, you'll have to also loop over the components and check whether the corresponding talks still exist. hint}} diff --git a/Makefile b/Makefile index 095d737da..c4ea65417 100644 --- a/Makefile +++ b/Makefile @@ -4,37 +4,38 @@ SVGS := $(wildcard img/*.svg) all: html book.pdf book_mobile.pdf book.epub book.mobi -html: $(foreach CHAP,$(CHAPTERS),html/$(CHAP).html) html/js/acorn_codemirror.js \ - code/skillsharing.zip code/solutions/20_3_a_public_space_on_the_web.zip html/js/chapter_info.js - -html/%.html: %.md - node src/render_html.js $< > $@ - node src/build_code.js $< - -html/js/chapter_info.js: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) code/solutions/* src/chapter_info.js - node src/chapter_info.js > html/js/chapter_info.js - -html/js/acorn_codemirror.js: node_modules/codemirror/lib/codemirror.js \ - node_modules/codemirror/mode/javascript/javascript.js \ - node_modules/codemirror/mode/css/css.js \ - node_modules/codemirror/mode/xml/xml.js \ - node_modules/codemirror/mode/htmlmixed/htmlmixed.js \ - node_modules/codemirror/addon/edit/matchbrackets.js \ - node_modules/acorn/dist/acorn.js \ - node_modules/acorn/dist/walk.js - node_modules/.bin/uglifyjs $^ -m -o $@ - -code/skillsharing.zip: html/21_skillsharing.html +html: $(foreach CHAP,$(CHAPTERS),html/$(CHAP).html) html/ejs.js \ + code/skillsharing.zip code/solutions/20_3_a_public_space_on_the_web.zip html/code/chapter_info.js + +html/%.html: %.md src/render_html.mjs src/chapter.html + node src/render_html.mjs $< > $@ + node src/build_code.mjs $< + +html/code/chapter_info.js: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) code/solutions/* src/chapter_info.mjs + node src/chapter_info.mjs > html/code/chapter_info.js + +html/ejs.js: node_modules/codemirror/dist/index.js \ + node_modules/@codemirror/view/dist/index.js \ + node_modules/@codemirror/state/dist/index.js \ + node_modules/@codemirror/language/dist/index.js \ + node_modules/@codemirror/lang-html/dist/index.js \ + node_modules/@codemirror/lang-javascript/dist/index.js \ + node_modules/acorn/dist/acorn.js \ + node_modules/acorn-walk/dist/walk.js \ + src/client/*.mjs + node_modules/.bin/rollup -c src/client/rollup.config.mjs + +code/skillsharing.zip: html/21_skillsharing.html code/skillsharing/package.json rm -f $@ - cd code; zip skillsharing.zip skillsharing/*.js* skillsharing/public/*.* + cd code; zip skillsharing.zip skillsharing/*.mjs skillsharing/package.json skillsharing/public/*.* code/solutions/20_3_a_public_space_on_the_web.zip: $(wildcard code/solutions/20_3_a_public_space_on_the_web/*) rm -f $@ cd code/solutions; zip 20_3_a_public_space_on_the_web.zip 20_3_a_public_space_on_the_web/* test: html - @for F in $(CHAPTERS); do echo Testing $$F:; node src/run_tests.js $$F.md; done - @node src/check_links.js + @for F in $(CHAPTERS); do echo Testing $$F:; node src/run_tests.mjs $$F.md; done + @node src/check_links.mjs @echo Done. tex: $(foreach CHAP,$(CHAPTERS),pdf/$(CHAP).tex) pdf/hints.tex $(patsubst img/%.svg,img/generated/%.pdf,$(SVGS)) @@ -50,31 +51,31 @@ book_mobile.pdf: pdf/book_mobile.tex tex cd pdf && sh build.sh book_mobile > /dev/null mv pdf/book_mobile.pdf . -pdf/hints.tex: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.js - node src/extract_hints.js | node src/render_latex.js - > $@ +pdf/hints.tex: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.mjs + node src/extract_hints.mjs | node src/render_latex.mjs - > $@ img/generated/%.pdf: img/%.svg inkscape --export-pdf=$@ $< pdf/%.tex: %.md - node src/render_latex.js $< > $@ + node src/render_latex.mjs $< > $@ book.epub: epub/titlepage.xhtml epub/toc.xhtml epub/hints.xhtml $(foreach CHAP,$(CHAPTERS),epub/$(CHAP).xhtml) \ - epub/content.opf.src epub/style.css src/add_images_to_epub.js + epub/content.opf.src epub/style.css src/add_images_to_epub.mjs rm -f $@ grep ' $@ + node src/generate_epub_toc.mjs $^ > $@ -epub/%.xhtml: %.md src/render_html.js - node src/render_html.js --epub $< > $@ +epub/%.xhtml: %.md src/render_html.mjs + node src/render_html.mjs --epub $< > $@ -epub/hints.xhtml: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.js src/render_html.js - node src/extract_hints.js | node src/render_html.js --epub - > $@ +epub/hints.xhtml: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.mjs src/render_html.mjs + node src/extract_hints.mjs | node src/render_html.mjs --epub - > $@ epubcheck: book.epub epubcheck book.epub 2>&1 | grep -v 'img/.*\.svg' diff --git a/README.md b/README.md index dfda90912..6702073d4 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/LICENSE b/code/LICENSE index b03a229d6..c36bc0217 100644 --- a/code/LICENSE +++ b/code/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2008-2020 by Marijn Haverbeke +Copyright (C) 2008-2024 by Marijn Haverbeke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/code/_stop_keys.js b/code/_stop_keys.js new file mode 100644 index 000000000..bc5de4e01 --- /dev/null +++ b/code/_stop_keys.js @@ -0,0 +1,3 @@ +window.addEventListener("keydown", e => { + if (/Arrow|Home|End|Page/.test(e.key)) e.preventDefault() +}) diff --git a/code/animatevillage.js b/code/animatevillage.js index 0f8efc334..3a4f2303f 100644 --- a/code/animatevillage.js +++ b/code/animatevillage.js @@ -33,12 +33,15 @@ this.node = outer.appendChild(doc.createElement("div")) this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px" this.map = this.node.appendChild(doc.createElement("img")) - this.map.src = "img/village2x.png" + 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 = "img/robot_moving2x.gif" + robotPic.src = this.imgPath + "robot_moving2x.gif" this.parcels = [] this.text = this.node.appendChild(doc.createElement("span")) @@ -72,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" @@ -96,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() } @@ -110,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/chapter/22_fast.js b/code/chapter/22_fast.js index 75161751d..8e0d12ad3 100644 --- a/code/chapter/22_fast.js +++ b/code/chapter/22_fast.js @@ -1,156 +1,144 @@ -var GraphNode = class GraphNode { - constructor() { - this.pos = new Vec(Math.random() * 1000, - Math.random() * 1000); - this.edges = []; +var Graph = class Graph { + #nodes = []; + + get size() { + return this.#nodes.length; + } + + addNode() { + let id = this.#nodes.length; + this.#nodes.push(new Set()); + return id; } - connect(other) { - this.edges.push(other); - other.edges.push(this); + + addEdge(nodeA, nodeB) { + this.#nodes[nodeA].add(nodeB); + this.#nodes[nodeB].add(nodeA); + } + + neighbors(node) { + return this.#nodes[node]; } - hasEdge(other) { - return this.edges.includes(other); +} + +function randomLayout(graph) { + let layout = []; + for (let i = 0; i < graph.size; i++) { + layout.push(new Vec(Math.random() * 1000, + Math.random() * 1000)); } + return layout; } -function treeGraph(depth, branches) { - let graph = [new GraphNode()]; - if (depth > 1) { - for (let i = 0; i < branches; i++) { - let subGraph = treeGraph(depth - 1, branches); - graph[0].connect(subGraph[0]); - graph = graph.concat(subGraph); +function gridGraph(size) { + let grid = new Graph(); + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + let id = grid.addNode(); + if (x > 0) grid.addEdge(id, id - 1); + if (y > 0) grid.addEdge(id, id - size); } } - return graph; + return grid; } -var springLength = 40; +var springLength = 20; var springStrength = 0.1; - var repulsionStrength = 1500; -function forceDirected_simple(graph) { - for (let node of graph) { - for (let other of graph) { - if (other == node) continue; - let apart = other.pos.minus(node.pos); - let distance = Math.max(1, apart.length); - let forceSize = -repulsionStrength / (distance * distance); - if (node.hasEdge(other)) { - forceSize += (distance - springLength) * springStrength; - } - let normalized = apart.times(1 / distance); - node.pos = node.pos.plus(normalized.times(forceSize)); - } +function forceSize(distance, connected) { + let repulse = -repulsionStrength / (distance * distance); + let spring = 0; + if (connected) { + spring = (distance - springLength) * springStrength; } + return spring + repulse; } -function runLayout(implementation, graph) { - function run(steps, time) { - let startTime = Date.now(); - for (let i = 0; i < 100; i++) { - implementation(graph); +function forceDirected_simple(layout, graph) { + for (let a = 0; a < graph.size; a++) { + for (let b = 0; b < graph.size; b++) { + if (a == b) continue; + let apart = layout[b].minus(layout[a]); + let distance = Math.max(1, apart.length); + let connected = graph.neighbors(a).has(b); + let size = forceSize(distance, connected); + let force = apart.times(1 / distance).times(size); + layout[a] = layout[a].plus(force); } - time += Date.now() - startTime; - drawGraph(graph); - - if (steps == 0) console.log(time); - else requestAnimationFrame(() => run(steps - 100, time)); } - run(4000, 0); } -function forceDirected_noRepeat(graph) { - for (let i = 0; i < graph.length; i++) { - let node = graph[i]; - for (let j = i + 1; j < graph.length; j++) { - let other = graph[j]; - let apart = other.pos.minus(node.pos); - let distance = Math.max(1, apart.length); - let forceSize = -repulsionStrength / (distance * distance); - if (node.hasEdge(other)) { - forceSize += (distance - springLength) * springStrength; - } - let applied = apart.times(forceSize / distance); - node.pos = node.pos.plus(applied); - other.pos = other.pos.minus(applied); +function pause() { + return new Promise(done => setTimeout(done, 0)) +} + +async function runLayout(implementation, graph) { + let time = 0, iterations = 0; + let layout = randomLayout(graph); + while (time < 3000) { + let start = Date.now(); + for (let i = 0; i < 100; i++) { + implementation(layout, graph); + iterations++; } + time += Date.now() - start; + drawGraph(graph, layout); + await pause(); } + let perSecond = Math.round(iterations / (time / 1000)); + console.log(`${perSecond} iterations per second`); } -var skipDistance = 175; - -function forceDirected_skip(graph) { - for (let i = 0; i < graph.length; i++) { - let node = graph[i]; - for (let j = i + 1; j < graph.length; j++) { - let other = graph[j]; - let apart = other.pos.minus(node.pos); +function forceDirected_noRepeat(layout, graph) { + for (let a = 0; a < graph.size; a++) { + for (let b = a + 1; b < graph.size; b++) { + let apart = layout[b].minus(layout[a]); let distance = Math.max(1, apart.length); - let hasEdge = node.hasEdge(other); - if (!hasEdge && distance > skipDistance) continue; - let forceSize = -repulsionStrength / (distance * distance); - if (hasEdge) { - forceSize += (distance - springLength) * springStrength; - } - let applied = apart.times(forceSize / distance); - node.pos = node.pos.plus(applied); - other.pos = other.pos.minus(applied); + let connected = graph.neighbors(a).has(b); + let size = forceSize(distance, connected); + let force = apart.times(1 / distance).times(size); + layout[a] = layout[a].plus(force); + layout[b] = layout[b].minus(force); } } } -GraphNode.prototype.hasEdgeFast = function(other) { - for (let i = 0; i < this.edges.length; i++) { - if (this.edges[i] === other) return true; - } - return false; -}; +var skipDistance = 175; -function forceDirected_hasEdgeFast(graph) { - for (let i = 0; i < graph.length; i++) { - let node = graph[i]; - for (let j = i + 1; j < graph.length; j++) { - let other = graph[j]; - let apart = other.pos.minus(node.pos); +function forceDirected_skip(layout, graph) { + for (let a = 0; a < graph.size; a++) { + for (let b = a + 1; b < graph.size; b++) { + let apart = layout[b].minus(layout[a]); let distance = Math.max(1, apart.length); - let hasEdge = node.hasEdgeFast(other); - if (!hasEdge && distance > skipDistance) continue; - let forceSize = -repulsionStrength / (distance * distance); - if (hasEdge) { - forceSize += (distance - springLength) * springStrength; - } - let applied = apart.times(forceSize / distance); - node.pos = node.pos.plus(applied); - other.pos = other.pos.minus(applied); + let connected = graph.neighbors(a).has(b); + if (distance > skipDistance && !connected) continue; + let size = forceSize(distance, connected); + let force = apart.times(1 / distance).times(size); + layout[a] = layout[a].plus(force); + layout[b] = layout[b].minus(force); } } } -function forceDirected_noVector(graph) { - for (let i = 0; i < graph.length; i++) { - let node = graph[i]; - for (let j = i + 1; j < graph.length; j++) { - let other = graph[j]; - let apartX = other.pos.x - node.pos.x; - let apartY = other.pos.y - node.pos.y; - let distance = Math.max(1, Math.sqrt(apartX * apartX + apartY * apartY)); - let hasEdge = node.hasEdgeFast(other); - if (!hasEdge && distance > skipDistance) continue; - let forceSize = -repulsionStrength / (distance * distance); - if (hasEdge) { - forceSize += (distance - springLength) * springStrength; - } - let forceX = apartX * forceSize / distance; - let forceY = apartY * forceSize / distance; - node.pos.x += forceX; node.pos.y += forceY; - other.pos.x -= forceX; other.pos.y -= forceY; +function forceDirected_noVector(layout, graph) { + for (let a = 0; a < graph.size; a++) { + let posA = layout[a]; + for (let b = a + 1; b < graph.size; b++) { + let posB = layout[b]; + let apartX = posB.x - posA.x + let apartY = posB.y - posA.y; + let distance = Math.sqrt(apartX * apartX + + apartY * apartY); + let connected = graph.neighbors(a).has(b); + if (distance > skipDistance && !connected) continue; + let size = forceSize(distance, connected); + let forceX = (apartX / distance) * size; + let forceY = (apartY / distance) * size; + posA.x += forceX; + posA.y += forceY; + posB.x -= forceX; + posB.y -= forceY; } } } - -var mangledGraph = treeGraph(4, 4); -for (let node of mangledGraph) { - node[`p${Math.floor(Math.random() * 999)}`] = true; -} diff --git a/code/crow-tech.js b/code/crow-tech.js deleted file mode 100644 index b61560aa0..000000000 --- a/code/crow-tech.js +++ /dev/null @@ -1,136 +0,0 @@ -(function() { - const connections = [ - "Church Tower-Sportsgrounds", "Church Tower-Big Maple", "Big Maple-Sportsgrounds", - "Big Maple-Woods", "Big Maple-Fabienne's Garden", "Fabienne's Garden-Woods", - "Fabienne's Garden-Cow Pasture", "Cow Pasture-Big Oak", "Big Oak-Butcher Shop", - "Butcher Shop-Tall Poplar", "Tall Poplar-Sportsgrounds", "Tall Poplar-Chateau", - "Chateau-Great Pine", "Great Pine-Jacques' Farm", "Jacques' Farm-Hawthorn", - "Great Pine-Hawthorn", "Hawthorn-Gilles' Garden", "Great Pine-Gilles' Garden", - "Gilles' Garden-Big Oak", "Gilles' Garden-Butcher Shop", "Chateau-Butcher Shop" - ] - - function storageFor(name) { - let storage = Object.create(null) - storage["food caches"] = ["cache in the oak", "cache in the meadow", "cache under the hedge"] - storage["cache in the oak"] = "A hollow above the third big branch from the bottom. Several pieces of bread and a pile of acorns." - storage["cache in the meadow"] = "Buried below the patch of nettles (south side). A dead snake." - storage["cache under the hedge"] = "Middle of the hedge at Gilles' garden. Marked with a forked twig. Two bottles of beer." - storage["enemies"] = ["Farmer Jacques' dog", "The butcher", "That one-legged jackdaw", "The boy with the airgun"] - if (name == "Church Tower" || name == "Hawthorn" || name == "Chateau") - storage["events on 2017-12-21"] = "Deep snow. Butcher's garbage can fell over. We chased off the ravens from Saint-Vulbas." - let hash = 0 - for (let i = 0; i < name.length; i++) hash += name.charCodeAt(i) - for (let y = 1985; y <= 2018; y++) { - storage[`chicks in ${y}`] = hash % 6 - hash = Math.abs((hash << 2) ^ (hash + y)) - } - if (name == "Big Oak") storage.scalpel = "Gilles' Garden" - else if (name == "Gilles' Garden") storage.scalpel = "Woods" - else if (name == "Woods") storage.scalpel = "Chateau" - else if (name == "Chateau" || name == "Butcher Shop") storage.scalpel = "Butcher Shop" - else storage.scalpel = "Big Oak" - for (let prop of Object.keys(storage)) storage[prop] = JSON.stringify(storage[prop]) - return storage - } - - class Network { - constructor(connections, storageFor) { - let reachable = Object.create(null) - for (let [from, to] of connections.map(conn => conn.split("-"))) { - ;(reachable[from] || (reachable[from] = [])).push(to) - ;(reachable[to] || (reachable[to] = [])).push(from) - } - this.nodes = Object.create(null) - for (let name of Object.keys(reachable)) - this.nodes[name] = new Node(name, reachable[name], this, storageFor(name)) - this.types = Object.create(null) - } - - defineRequestType(name, handler) { - this.types[name] = handler - } - - everywhere(f) { - for (let node of Object.values(this.nodes)) f(node) - } - } - - const $storage = Symbol("storage"), $network = Symbol("network") - - function ser(value) { - return value == null ? null : JSON.parse(JSON.stringify(value)) - } - - class Node { - constructor(name, neighbors, network, storage) { - this.name = name - this.neighbors = neighbors - this[$network] = network - this.state = Object.create(null) - this[$storage] = storage - } - - send(to, type, message, callback) { - let toNode = this[$network].nodes[to] - if (!toNode || !this.neighbors.includes(to)) - return callback(new Error(`${to} is not reachable from ${this.name}`)) - let handler = this[$network].types[type] - if (!handler) - return callback(new Error("Unknown request type " + type)) - if (Math.random() > 0.03) setTimeout(() => { - try { - handler(toNode, ser(message), this.name, (error, response) => { - setTimeout(() => callback(error, ser(response)), 10) - }) - } catch(e) { - callback(e) - } - }, 10 + Math.floor(Math.random() * 10)) - } - - readStorage(name, callback) { - let value = this[$storage][name] - setTimeout(() => callback(value && JSON.parse(value)), 20) - } - - writeStorage(name, value, callback) { - setTimeout(() => { - this[$storage][name] = JSON.stringify(value) - callback() - }, 20) - } - } - - let network = new Network(connections, storageFor) - exports.bigOak = network.nodes["Big Oak"] - exports.everywhere = network.everywhere.bind(network) - exports.defineRequestType = network.defineRequestType.bind(network) - - if (typeof __sandbox != "undefined") { - __sandbox.handleDeps = false - __sandbox.notify.onLoad = () => { - // Kludge to make sure some functions are delayed until the - // nodes have been running for 500ms, to give them a chance to - // propagate network information. - let waitFor = Date.now() + 500 - function wrapWaiting(f) { - return function(...args) { - let wait = waitFor - Date.now() - if (wait <= 0) return f(...args) - return new Promise(ok => setTimeout(ok, wait)).then(() => f(...args)) - } - } - for (let n of ["routeRequest", "findInStorage", "chicks"]) - window[n] = wrapWaiting(window[n]) - } - } - - if (typeof window != "undefined") { - window.require = name => { - if (name != "./crow-tech") throw new Error("Crow nests can only require \"./crow-tech\"") - return exports - } - } else if (typeof module != "undefined" && module.exports) { - module.exports = exports - } -})() diff --git a/code/draw_layout.js b/code/draw_layout.js index 0a2541c7e..0c1a87c56 100644 --- a/code/draw_layout.js +++ b/code/draw_layout.js @@ -23,38 +23,39 @@ class Vec { // in advance how big the graph is, the `Scale` object computes a // scale and offset so that all nodes fit onto the given canvas. -const nodeSize = 8; +const nodeSize = 6; -function drawGraph(graph) { - let canvas = document.querySelector("canvas"); +function drawGraph(graph, layout) { + let parent = (window.__sandbox ? window.__sandbox.output.div : document.body); + let canvas = parent.querySelector("canvas"); if (!canvas) { - canvas = document.body.appendChild(document.createElement("canvas")); + canvas = parent.appendChild(document.createElement("canvas")); canvas.width = canvas.height = 400; } let cx = canvas.getContext("2d"); cx.clearRect(0, 0, canvas.width, canvas.height); - let scale = new Scale(graph, canvas.width, canvas.height); + let scale = new Scale(layout, canvas.width, canvas.height); // Draw the edges. cx.strokeStyle = "orange"; cx.lineWidth = 3; - for (let i = 0; i < graph.length; i++) { - let origin = graph[i]; - for (let target of origin.edges) { - if (graph.indexOf(target) <= i) continue; + for (let i = 0; i < layout.length; i++) { + let conn = graph.neighbors(i); + for (let target of conn) { + if (conn <= i) continue; cx.beginPath(); - cx.moveTo(scale.x(origin.pos.x), scale.y(origin.pos.y)); - cx.lineTo(scale.x(target.pos.x), scale.y(target.pos.y)); + cx.moveTo(scale.x(layout[i].x), scale.y(layout[i].y)); + cx.lineTo(scale.x(layout[target].x), scale.y(layout[target].y)); cx.stroke(); } } // Draw the nodes. cx.fillStyle = "purple"; - for (let node of graph) { + for (let pos of layout) { cx.beginPath(); - cx.arc(scale.x(node.pos.x), scale.y(node.pos.y), nodeSize, 0, 7); + cx.arc(scale.x(pos.x), scale.y(pos.y), nodeSize, 0, 7); cx.fill(); } } @@ -79,9 +80,9 @@ function drawGraph(graph) { // that the circles drawn around nodes’ center points don't get cut off. class Scale { - constructor(graph, width, height) { - let xs = graph.map(node => node.pos.x); - let ys = graph.map(node => node.pos.y); + constructor(layout, width, height) { + let xs = layout.map(node => node.x); + let ys = layout.map(node => node.y); let minX = Math.min(...xs); let minY = Math.min(...ys); let maxX = Math.max(...xs); diff --git a/code/hangar2.js b/code/hangar2.js new file mode 100644 index 000000000..19329d11b --- /dev/null +++ b/code/hangar2.js @@ -0,0 +1,350 @@ +var readTextFile = function() { + let min = 60 * 1000, hour = min * 60, day = hour * 24 + + let logs = [], year = 2023, start = new Date(year, 8, 21).getTime() + for (let i = 21; i <= 30; i++) { + if (i != 27) logs.push({name: "activity-" + year + "-09-" + i + ".log", time: start + i * day, day: (i - 17) % 7}) + } + + let rState = 81782 + function r1() { + rState ^= rState << 13 + rState ^= rState << 17 + rState ^= rState << 5 + return (rState & 0xffffff) / 0xffffff + } + function r(n) { + return Math.floor(r1() * n) + } + + let weekday = [1, 1, 1, 1, 1, 3, 8, 20, 10, 15, 15, 20, 25, 12, 15, 20, 18, 16, 10, 8, 8, 7, 4, 2] + let saturday = [1, 1, 1, 1, 1, 2, 3, 5, 3, 2, 2, 5, 8, 7, 9, 5, 5, 3, 3, 3, 4, 2, 2, 2] + let sunday = [2, 2, 1, 1, 1, 1, 1, 2, 2, 3, 6, 6, 2, 1, 1, 1, 1, 4, 4, 4, 3, 2, 1, 1] + + let activity = (day, base) => { + let schedule = day == 0 ? sunday : day == 6 ? saturday : weekday + let events = [] + for (let h = 0; h < 24; h++) { + let n = schedule[h] * 2 + r(5) - 2 + for (let i = 0; i < n; i++) { + let t = base + h * hour + r(hour) + let j = events.length + while (j > 0 && events[j - 1] > t) --j + events.splice(j, 0, t) + } + } + return events + } + + let generated = false + function generateLogs() { + if (generated) return + generated = true + for (let log of logs) { + files[log.name] = activity(log.day, log.time).join("\n") + } + } + + let files = { + __proto__: null, + "shopping_list.txt": "Peanut butter\nBananas", + "old_shopping_list.txt": "Peanut butter\nJelly", + "package.json": '{"name":"test project","author":"cāāw-krö","version":"1.1.2"}', + "plans.txt": "* Write a book\n * Figure out asynchronous chapter\n * Find an artist for the cover\n * Write the rest of the book\n\n* Don't be sad\n * Sit under tree\n * Study bugs\n", + "camera_logs.txt": logs.map(l => l.name).join("\n") + } + + return function readTextFile(filename, callback) { + if (/^activity/.test(filename)) generateLogs() + let file = filename == "files.list" ? Object.keys(files).join("\n") : files[filename] + Promise.resolve().then(() => { + if (file == null) callback(null, "File " + filename + " does not exist") + else callback(file) + }) + } +}() + +var activityGraph = table => { + let widest = Math.max(50, Math.max(...table)) + return table.map((n, i) => { + let width = (n / widest) * 20 + let full = Math.floor(width), rest = " ▏▎▍▌▋▊▉"[Math.floor((width - full) * 8)] + return String(i).padStart(2, " ") + " " + "█".repeat(full) + rest + }).join("\n") +} + +var joinWifi = function(networkID, code) { + return new Promise((accept, reject) => { + setTimeout(() => { + if (networkID != "HANGAR 2") return reject(new Error("Network not found")) + let correct = "555555" + if (code == correct) return accept(null) + if (!correct.startsWith(code)) return reject(new Error("Invalid passcode")) + }, 20) + }) +} + +var request = function(){ + function error(device) { + return (req, resolve, reject) => reject(new Error(device + ": malformed request")) + } + + let hosts = { + "10.0.0.1": error("ROUTER772"), + "10.0.0.2": () => {}, + "10.0.0.4": () => {}, + "10.0.0.20": error("Puxel 7"), + "10.0.0.33": error("jPhone[K]"), + } + + function screen(n) { + return (req, resolve, reject) => { + if (!req || req.command !== "display") { + reject(new Error("LedTec SIG-5030: INVALID REQUEST " + req?.type)) + } else if (!Array.isArray(req.data) || req.data.length !== 1500) { + reject(new Error("LedTec SIG-5030: INVALID DISPLAY DATA")) + } else { + if (!screens) { + if (typeof window != "object" || !window.document) return + screens = new Screens + } + setTimeout(() => { + screens.update(n, req.data) + resolve({status: "ok"}) + }, 3 + Math.floor(Math.random() * 20)) + } + } + } + + ;["10.0.0.44", "10.0.0.45", "10.0.0.41", + "10.0.0.31", "10.0.0.40", "10.0.0.42", + "10.0.0.48", "10.0.0.47", "10.0.0.46"].forEach((addr, i) => hosts[addr] = screen(i)) + + class Screens { + constructor() { + this.getParent() + let doc = this.parent.ownerDocument + this.dom = this.parent.appendChild(doc.createElement("div")) + this.dom.style.cssText = "position: relative; max-width: 500px" + let inner = this.dom.appendChild(doc.createElement("div")) + inner.style.cssText = "position: relative; width: 100%; padding-bottom: 60%" + this.screens = [] + for (let i = 0; i < 9; i++) { + let screen = inner.appendChild(doc.createElement("div")) + let row = Math.floor(i / 3), col = i % 3 + screen.style.cssText = "border: 1px solid #222; background: black; position: absolute; width: 33.3%; height: 33.3%; left: " + (col * 33.3) + "%; top: " + (row * 33.3) + "%" + let canvas = screen.appendChild(doc.createElement("canvas")) + canvas.style.cssText = "width: 100%; height: 100%" + this.screens.push(canvas) + } + this.screens.forEach(c => { c.width = c.offsetWidth; c.height = c.offsetHeight }) + } + + getParent() { + this.parent = window.__sandbox ? window.__sandbox.output.div : document.body + } + + update(n, data) { + this.getParent() + if (!this.parent.ownerDocument.body.contains(this.dom)) this.parent.appendChild(this.dom) + + let canvas = this.screens[n], cx = canvas.getContext("2d") + cx.clearRect(0, 0, canvas.width, canvas.height) + let gapX = (canvas.width * 0.4) / 51, sizeX = (canvas.width * 0.6) / 50, skipX = gapX + sizeX + let gapY = (canvas.height * 0.4) / 31, sizeY = (canvas.height * 0.6) / 30, skipY = gapY + sizeY + for (let i = 0, col = 0, row = 0; i < 1500; i++) { + let pixel = data[i] + if (pixel) { + cx.fillStyle = pixel == 3 ? "#fd4" : pixel == 2 ? "#a82" : "#741" + cx.fillRect(gapX + col * skipX, gapY + row * skipY, sizeX, sizeY) + } + if (col == 49) { col = 0; row++ } + else { col++ } + } + } + } + let screens = null + + return function request(address, content) { + return new Promise((resolve, reject) => { + let host = hosts[address] + if (!host) reject(new Error("No route to host " + address)) + else host(JSON.parse(JSON.stringify(content)), resolve, reject) + }) + } +}() + +var clipImages = [ + [ + " 5dc", + " e9.2 2b.3 .3 2b.o.3o. 2b.o.3o. 27.2 2.o2.o2. 27.o. .Oo.oO. . 25.o. .Oo.oO.4 20.2 2.Oo .Oo.oO.2o. 20.o 2.Oo.2Oo.oO.2o. 20.O. .O2.2Oo2O2o3. 20.O2 2oOo.O2oO2o3. 20.oOo oO2oO2oO2oOo. 1e.o2O3o2O8oOo 1f.O6oOao 1e.oO11o 1eoO12o. 1c.O13o. 6. 14.O14o. 1boO15. 1bO15o. 1aoO15o 1a.O16. 18. .O15o 1boO15. 1a.O15o 1b.O15. f", + " 57e.2 5c", + " 12d.2 1f0. 30.O 2f.oO 2foO2 2eoO3 2d.O4 2c.oO4 2b.O5o 2b.oOo.2 2d.2 fe", + " b.oO15. 1aO16o 1b.oO14. 1a.oO14. 1boO15. 1b.O14o 1c.O14o 1b.O15. 1a.O16. 1a.O16. 1aoO16o.3 16.O1bo. 14oO1e. 11.O20o a.6oO22. 8O2b. 6O2c. 5O29o.3 5O25o2. aO23o. dO1fo.2 10O5o2O11o3.3 14o.4 2.5oO7o.3 27.3oO3 2fo2. 30. e7", + " 33c.2 b7.4 7a.2 31. 135", + " 370.3 31. 237", + " 4c1.2 119", + " 5dc" + ], + [ + " 5dc", + " 1e3.2 30.3 .3 2a.o2. .o. 26.3 .o2.2o2. 26.o. .Oo.2o2. 26.O. .Oo.oOo. 22. 3.Oo oOo.oO.5 1e.o2 2oOo.oOo2O2.5 1foO. .O2.oOo2O2.2o2. 1d. oOo .O2.oOo2Oo.o3. 1c.o3O2o.O3oO2oOo4. 1c.2O6oO8o2Oo 1c.oO13. 1b.oO13o. c.2 c.O15o. 1aoO16. 18.oO17. 6.3 f.O17o. 18oO17. 18oO17o 19oO16o. b", + " 5dc", + " 2edo 30.O 2f.O2 2e.O3 2d.oO3 2c.oO4 2coO3o. 2co3. 192", + " c.oO16o 19oO16o 1a.oO14o. 19.O15o. 1a.O14o. 1b.oO13o 1b.O15o 1b.O15. 1b.O15o 1boO17o. 17.O1bo 14.O1do. b.2o3.oO20o. 8.O29o. 6O2c. 5O2ao2. 5O26o2.2 8O24o. cO5o3Oo2O15o2.2 eOo.3 3. 2.o2O6o6.5 13. c.3O3o 2e.o2. 2b.2 181", + " 29f. 36. 81.2 30.4 7a.2 1d2", + " 304.4 6.2 2cc", + " 5dc", + " 5dc" + ], + [ + " 47a. 132.2 2d", + " 345.2 2.2 2c.o. .3 26. 3.o2.2o2. 26.2 2.o2.2o2. 25.o2 .oOo.o2. 22o2 2.O2.2O2o2Oo.3 20oO. .O2.oOo2O2o.3 1d.o.oOo .O2.oOo2O2o.2o. 1a.2oO5.oO2o2O2oO2o.o2 19.2oO8oO8o4. 18.O14oO2o. 17.O17o. 17oO18o. 16.oO18o. 6", + " 5dc", + " 2ba.o 30oO 2e.oO2 2eoO3 2d.O4 2c.O3o. 2coOo. 2e.2 1c6", + " f.O1ao 15.oO19o. 14.O1ao. 14.o2O17o.2 15oO16o2. 17.oO14o.2 19oO14o 1b.O15. 1a.oO17o. 16.oO1ao. f.4oO1eo. boO26. 9oO29o. 6O2bo. 5O29o3. 5O26o2. 9O2o4.2o.2oO18o. a.2o.2 9.2oO13o. 1doO2o. .d 1f.o.2 213", + " 265.2 3. b5.2 7a.2 23e", + " 29a. 31.2 7. 2e5. 20", + " 5dc", + " 5dc" + ], + [ + " 5dc", + " 5dc", + " 5a1.2 39", + " 1a1. 83. 30.o 2f.oO 2e.oO2 2c.oO2o. 2b.oOo.2 2b.2o.2 2e.2 25d", + " 28.3 2. 27.2 2.7 21.2 3.6o2.4 16.co. 2.o2.2oO2o3.2 e.o5O7o6Oo.2oOo.oO4o2.2 c.oO14o.oO2o2O6o.2 a.oO18oO2oO6o2.3 8.O24o3.2 5.2oO26o2. 3.oO2ao.2 3oO2ao. 5O2ao. 6O2ao. 6o5.2oO20o3. 6. 6oO1do.2 10.oO1ao2.2 12.oO17o2. 16.oO15. 1b.o6Oo2Oc. 1e.3o4Oco 21.o3Oo2O9o 22.o2O2oOa. . 20.o2Oco.3 20.2o5O2oO4o3. 22.5o.2o4O3o.2 24.3 .4oO3o. 2b.3o3. 2e.3 73", + " 5dc", + " 259.4 6.2 2f.4 344", + " 5dc", + " 5dc" + ], + [ + " 433. 1a8", + " 5dc", + " 598.3 41", + " 256.o 2f.oO 2d.o3. 2b.2o2. 2c.o2. 2f. 290", + " a3.5 29.o2O7o3.2 21.oO10o2. 1c.O15o.4 16.oO1ao. 13.O1eo. e.2oO21o c.oO24o. aO28o.7 2o2.o4O23o.4 3. 7.oO1do4.4 9.oO1do2.o4.3 9oO1bo6O3o3. 9oO18o. 3.o2O5o2. 9.oO14.3 6.2oO5o. 9oO15 a.2oO3o2. 9.oO14. a.2o3.3 9.O15o c.4 b.O16. 1b.oO14. 1c.oO13. 1d.o5Oe. 1f.2o3Odo 21oOfo 21oO4oOb. 20.3oOd. 22.oOdo 13", + " 5dc", + " 28b.5 34c", + " f.o2Oco 22.2oO2oO9o. 23.o2.oO2o2O2o3.3 21.3 .Oo.2o2.2o.2o. 20.2 2.o.5 .4o.2 23.3 2. 3.5 4bf", + " 443.3 196" + ], + [ + " 5dc", + " 48a. 151", + " 4b8.2 d6.2 31.2 17", + " 256.o 2e.o3 2c.2o.2 2c.3 2e.2 37. 28a", + " a2.6 29.oO9o.3 21.oO10o. 1d.O15o.3 17.O1bo. 12.oO1eo. e.oO22o c.O25o. aO28o. 8.6oO23o e.oO1fo2. e.oO1co. 11oO1c. 7. 6.4 2oO1bo 15oO1c. 13.o2O1bo 14oO1d. 13O1e. 13.oO1co 13.oO15oO2oO4. 12.O16o3.oO3o 12.oO15o. .2o4. 12.O15o 3.d coO14o 3.e b.O14o 9.4o2.2 coO13o 9.3o4. doO2oOfo b.6 3", + " 5dc", + " 3fe.2 1dc", + " a.o2.o3Oco c.3 10. oO10 20.O11. 1f.O11. 1f.o3Oeo 21.2oOdo 22oOeo. 20.Ofo. 21o3Oco. 21.2oO2o2O8o. 23oOo.oO2oO2oOo.2 23oO. oOo.O2o3.2 23o2 2oOo.oO.5 23.2 2oOo o2.8 24.o. .o. . 2.3 24.o. .2 6. 26.2 2. 2a1", + " 43b.3 19e" + ], + [ + " 5dc", + " 4b8.2 122", + " 4e6.3 d5.3 1b", + " 257. 2f.o2 2d.2o. 2c.4 2d.3 66.3 258", + " d3.2o5.3 26.oObo2.2 1f.O13o. 1boO16o3.2 15oO1co. 11oO21. c.o2O23o bO28. 9o6O24. d.oO23. e.O1eo.3 d.oO1co.2 11oO1bo 5.2 eoO1bo 15.O1c. 14oO1co 14.oO1bo 14oO1d 14o2O1c. 14.O1c. 13.O1do 14O1e. 13.O1do 14oO16oO5o. 13oO15o.oO5. 13.oO6oOd.2oO2oOo. b", + " 16. 4ca. 30.2 c8", + " 42d. 1ae", + " 9oO14 2.o2.o2.5 11oO12o 2.d f.O2o5Oc 2.2 .3 .7 e.o2.o3Od. 9.6 10.oO10. 9.6 10.O5oOb. 1f.O11o 1f.o3Of 21.2oOe. 21oOeo. 20.Ofo. 20.Ofo.2 1f.o3Oco. 21.2oO2.oO7o2. 22.oOo.O3oO2oOo2. 22.O2 .O2o.O2.o2.2 22.o2 .O2.2Oo.o2.2 22.o. .O2.2Oo.2o.2 23. 2.oO.2o2.4 28o2 2.o. .2 28.o 2.3 2b.2 3. 114.3 90", + " 469.3 170" + ], + [ + " 5dc", + " 53b.3 9e", + " 5dc", + " 225. 2e.oO2 2b.2o4. 2a.2o. 2e. ba.7 234", + " 104.5o3.3 25.oOeo2.2 1c.oO15o.4 14.oO1do. f.oO21o coO26. aO28o. 8.2o2O26o. b.oO25. b.O21o.3 c.O1eo.2 10.O1bo.2 13.O1b 16.O1b 16.O1b. 15.O1b. 16oO1a. 16oO1a. 16oO1a. 16.O1a. 17oO19. 17oO19o 17.O19o 17.O1a 17.O1a. e", + " 4.2 32. 4fa.5 a4", + " 5dc", + " 9oO19. 17oO19o 17oOo5O13o 17.o2O18 18.oO17o. 17.O5oO11o2. 17oO4o2O12. 18.O4oO13. 18.o3O14o. 19.2oO13o2. 19.oO11o2Oo2.2 18.O12o5.2 18oO3oOeo3.4 18.O3oOeo3.4 18.2oOo2Oco4.2 1b.oOo3Obo.5 1b.o2.2oOo2O7o.6 1boOo.2O2o2O7o.5 1b.o2.2oOo.oOo2Oo2Oo.3 1d.o2 .oOo.oOo2Oo2O. 20.o. .Oo.2O2o2Oo2O. 21. 2oOo.oOo.O2.oO. 23.oO.2oOo.O2.2o. 23.o2.2oO.2Oo.2o. 23.o2 .o2.2o2. .2 23.o. .o2.2o2. .2 24.2 .o2 .o2. 27. 2.o. .o2. 2a.3 2.3 2b.2 2.2 19", + " 48b.2 14f" + ], + [ + " 3c7.3 212", + " 563.2 77", + " 59f.2 3b", + " 255.2o 2c.o2O3 2boOo3.2 10b.6 2c.5 1de", + " 90.2 f.3 c3.2o5O5o9.2 19.oO17o3.2 12.oO1eo. d.2oO22. aoO27o 9O2ao. 6.3oO28o 8.2o2O24o2 coO20o. 10oO1co. 13.O19o. 16.O17o. 18.O17. 1aoO16. 1aoO16. 1aoO16 1boO15o 1boO15o 1boO15o 1boO15o 1boO15o 1boO15. 1boO14o. 12", + " 516. 44.3 7e", + " 494. d9. 6d", + " 9oO14o. 1boO13o.2 1boO12o.2 1coO12.3 1b.oO11o. 1e.oO10o. 1e.oO10.2 1e.oOoOdo. 1f.o3Odo. 1e.o4Oao3.2 1e.o.2oOao3. 1f.4o4O6o.4 1f.3o5O6o.4 20.2o5O5o.2 23.2o5O2oO2o. 24.4o3Oo2Oo.2 24.4o8. 25.4o7.2 26.4o3.5 27.a 29.6 2d.4 30.2 17f", + " 3bf. f2.2 128" + ], + [ + " 420.2 1ba", + " 5ba.3 1f", + " 8c.2 30.3 51b", + " 2e6. 3.2o2 2b.oO5 2b.oO2o2. 2d.2 9e.7 140.2 74", + " b6. 43.2 21.3 26.o6. 27.2oO7. 25.oOa. 1f.4o2Obo.3 18.o3O16o. 14.O1e. f.2oO20o a.2oO26. 7O2co. 4O2oO2ao 4. .oO26o.3 8.2o3O1eo. 12oO1ao. 15oO18o. 16.O16o. 19.Ofo4.2 1coOoO9o2.2 21.o3Oo2O2o2Oo2. 23.3oa.2 8.2 19.3o9.2 24.7o4.3 25.7o2.3 26.b 26.b 20", + " 1b.3 563.3 58", + " 595. 46", + " 8. .8 29.8 29.8 2a.8 29.7 2b.6 2b.7 2c.4 2f. 2b0.3 bf. d0", + " 5dc" + ], + [ + " 41a. 1c1", + " 5b2.4 26", + " b6.3 523", + " 124.3 1e9.2 c.2 2c.2o2O2 29. .oO2o2. 2c.2 68.3 2f.3 110. 7c", + " b4.2 2b.4o4. 27.2o2O6. 25.2o2O7o 23.3oO9o. 22.2oObo 21.o2Odo .2 1c.oO11oO3o. 17.oO19o. 13oO1e. f.oO21o. 9.2o2O25o. 5oO2c. 4O2ao.3 4.3oO23o.2 c.2oO1eo. 10.2oO1bo 12.2o9O11o. 13.3oOo3.2 2oOeo. 14.3o4.5 .oOo.3o3.4 17.2o4.4 5. 22.2o3.3 d. 1c.2o2.4 29.8 2b.6 2d.4 5d", + " 13.3 564. 31.2 2e", + " 4e5. da. 1b", + " 441.3 198", + " 5dc" + ], + [ + " 351. 31. 31. 30.2 30.2 30.2 30.2 30.2 30.2 30.2 31. 26.3 8. 31. 31.", + " 25f.3 2b.a 26.e 23.7o4.7 20.2ob.7 1e.o7O5o5.3 1d.o2Ofo2.2 1c.oO12o2. 1boO15o.2 19oO16o2. 18oO17o2. 17oO19o. 16oO1ao. 15o2O1b. 14oO1co. 13o2O1co 13.oO1d. 12.2oO1co 12", + " 3e. 59d", + " 265. 30.3 b7.2 2c.6 2b.5 228", + ".3o2O1b 12.4o3O18o 15.3oO18o 16.3oO17. 18.2O17. 1aoO16 1boO16 1boO15o 1boO16.4 17oO1bo. 14oO1do. 12oO1eo. 11.O20o. foO22o. a.oO24o. 7.2oO22o2.2 7o2O23o. b.2oO20o. 10.oO1co. 15.o2O16o. 1a.2oO10o2.2 1f.2oO8o2.3 24.oOo.5 2a.2 14c", + " 2e6.3 2f3", + " 5dc", + " 3c8.2 212", + " 575.2 31.2 32" + ], + [ + " 565. 31.2 43", + " ce. 2e.9 29.2o6.2 28.oO6o.2 27.oO7o.2 24.3oO8o.2 23.3o2O8o.2 22.2o3O9o2.3 20.o2Oco.3 1e.2o2Odo3. 1c.3o2O10o. 1b.2o2O11o. 1b.2o2O12o. 1b.2oO13o 1b.2oO14. 1a.2oO14o 1b.oO15. 1a.2oO14o. 1a.oO15o 1a.2oO15. 1a.2O15o 1b.oO15o 1boO16. 1a.O17 1a.O17. 1aoO16o 13", + " 5dc", + " 28e.3 30.2 bf.2 2e.oO2 2b.3o2.2 2b.3 94. 131", + " 7.O17. 19.O17. 19.O17. 19.O16o 1a.O16o 1a.O16o 1a.oO15. 1b.O15. 1boO15. 1boO15o.o3. 16oO1bo. 14oO1do 13.O1f. 12oO1fo. 10oO21o. c.oO21o2. 9.oO21o.2 8o3O21o2. boO21o. f.oO1do. 14.oO19.2 18.oO12o3. 1c.4oO9o2.2 26.O3o.3 2b.o. 119", + " 2dd.3 2fc", + " 5dc", + " 3c0.2 30.2 1e8", + " 59e.4 3a" + ], + [ + " 58b. 31.2 1d", + " 71.2 2d.6 1a. 10.9 29.5o2.3 26. 2.4o4.2 24.5o2.o6. 23.2o.2o4O4o. 24.o2.2o4O4o. 24.Oo.oO2oO5. 21.3 oOo.oO7o. 20.o2.2O2o2O7o.2 20oOo3Obo. 20.oOfo. 1f.O11. 1f.O11o. 1e.O12. 1e.oO11o. 1eO13. 1eoO12o 1eoO12o. 1dO14. 1doO13. 1d.O13o 1c.oO13o 1c.O14o 1c.O15. 1boO15o 1b.O15o. 14", + " 5dc", + " f2.3 321.oO2 2c.2oO3 2b.o3.2 2c.2 131", + " 7oO15. 1aoO16o 1aO17o 1a.O16o 14.2 4.O16o 1aoO16o 1aoO16o 1a.O16o 1a.O16o 1a.O16o 1b.O15o 1b.O15o.o4. 15.O1co. 13.O1eo. 12oO1f. 11.O21. 10oO21o. d.O20o3. a.2oO1eo2. 9. .2oO1fo. cO21o. fO1fo. 12oO1bo. 15.2oO15o.2 1a.oOo2Oco2. 24.4oO3o.2 2co3 b5", + " 5e.3 289.2 17.3 16.2 12b.3 190", + " 418. 31.2 1a.3 173", + " 5dc", + " 5c4.3 15" + ], + [ + " 583.3 56", + " 85.2 23.2 2d.2 .3 2b.7 2b.7 27.2 2.o.3o.4 23.4 .o.3o2.4 23.o. .Oo.oO2o2. 24.o.3O3oO2o2. 24.o2.2oO6o2 21.2 2oOo.oO7o. 20.o. .O2.2O7o. 20.oOo.oOo.oO6o.3 1f.O2o.O2o2O7.3 1e.2oOeo.3 1doO10o2.2 1d.O11o2. 1d.O11o2. 1doOoO10o. 1doO12o. 1d.O12o. 1c.o2O11o2. 1a.O2o2O11o. 1boO14o. 1boO15o. 19oO16o. 19oO16. 1aoO16. 13", + " 3b5. 226", + " 449o2O 2c.2oOo2 2b.o2.2 2c.2 100", + " 7oO16. 1a.O16. 19oO17. 19oO17. 1aoO16. 19.O17. 19oO17. 19.O17 1a.O16o 1a.O16o 1boO16 1boO16.2o3. 15oO1co. 13.O1eo. 12oO1f. 11.O20o. 10oO22. eoO20o2. coO1fo. d.oO1eo.2 b.3oO1eo. eOo3O1bo. 11.o2O1ao. 14oO18o.2 18.3O10o2. 1f.3 .3o.oO4o. 2c.o2. b4", + " 57.2 270. 17.3 17.3 111.2 30.3 198", + " 410.2 30.2 1a.2 17c", + " 5dc", + " 58a.2 31.2 1d" + ] +].map(frame => frame.map(s => { + let result = [], re = /([ .oO])([\da-f]*)/g, m + while (m = re.exec(s)) { + let v = " .oO".indexOf(m[1]), c = parseInt(m[2] || "1", 16) + for (let i = 0; i < c; i++) result.push(v) + } + return result +})) diff --git a/code/index.html b/code/index.html index b470928e3..6762d902d 100644 --- a/code/index.html +++ b/code/index.html @@ -2,28 +2,19 @@ - Eloquent JavaScript :: Code Sandbox - - - - - - + + + -
- Note: If you are reading the second edition of the book, - you'll want to go - to that - edition's sandbox instead! -
-

Code Sandbox
Eloquent JavaScript

@@ -39,8 +30,8 @@

Code Sandbox
- -
+
+
@@ -64,3 +55,5 @@

Code Sandbox
diff --git a/code/levels.js b/code/levels.js index 9ff491fab..cfe887b0c 100644 --- a/code/levels.js +++ b/code/levels.js @@ -86,7 +86,7 @@ var GAME_LEVELS = [` ++++#.#++++++#.........#+++++#.....................##++++++##..+.....................#######...................... ++++#.#++++++#.........#+++++##.......##############++++++##...+.................................................. ++++#.#++++++#.........#++++++#########++++++++++++++++++##....+.................................................. -++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+.................................................. +++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+++++++++++++++++++++++++++++++++++++++++++++++++++ `,` .............................................................................................................. .............................................................................................................. diff --git a/code/packages_chapter_10.js b/code/packages_chapter_10.js index 5d11cd7b0..b4e07bbb9 100644 --- a/code/packages_chapter_10.js +++ b/code/packages_chapter_10.js @@ -67,6 +67,20 @@ module.exports = { pm: 'PM' };`) +require.preload("./dayname.js", String.raw` +const names = ["Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"]; + +exports.dayName = function(number) { + return names[number]; +} +exports.dayNumber = function(name) { + return names.indexOf(name); +}`) + +require.preload("./seasonname.js", String.raw` +module.exports = ["Winter", "Spring", "Summer", "Autumn"];`) + /* ini 1.3.5: https://github.com/npm/ini The ISC License @@ -478,7 +492,7 @@ module.exports = function (arr) { return arr[Math.floor(Math.random() * arr.length)]; };`) -require.preload("./format-date", String.raw`const ordinal = require("ordinal"); +require.preload("./format-date.js", String.raw`const ordinal = require("ordinal"); const {days, months} = require("date-names"); exports.formatDate = function(date, format) { @@ -492,7 +506,7 @@ exports.formatDate = function(date, format) { }); };`) -require.preload("./graph", String.raw`exports.buildGraph = function(edges) { +require.preload("./graph.js", String.raw`exports.buildGraph = function(edges) { let graph = Object.create(null); function addEdge(from, to) { if (!(from in graph)) graph[from] = Object.create(null); @@ -504,4 +518,3 @@ require.preload("./graph", String.raw`exports.buildGraph = function(edges) { } return graph; };`) - diff --git a/code/skillsharing/package.json b/code/skillsharing/package.json index 03181f034..8851e3da4 100644 --- a/code/skillsharing/package.json +++ b/code/skillsharing/package.json @@ -4,7 +4,7 @@ "main": "skillsharing_server.js", "description": "Skill-sharing website example from Eloquent JavaScript", "dependencies": { - "ecstatic": "^3.1.0" + "serve-static": "^1.15.0" }, "license": "MIT", "bugs": "https://github.com/marijnh/Eloquent-JavaScript/issues", @@ -12,7 +12,7 @@ "maintainers": [ { "name": "Marijn Haverbeke", - "email": "marijnh@gmail.com", + "email": "marijn@haverbeke.berlin", "web": "https://marijnhaverbeke.nl/" } ], diff --git a/code/solutions/06_2_groups.js b/code/solutions/06_2_groups.js index e1c4cb3c9..2aa1f952a 100644 --- a/code/solutions/06_2_groups.js +++ b/code/solutions/06_2_groups.js @@ -1,20 +1,18 @@ class Group { - constructor() { - this.members = []; - } + #members = []; add(value) { if (!this.has(value)) { - this.members.push(value); + this.#members.push(value); } } delete(value) { - this.members = this.members.filter(v => v !== value); + this.#members = this.#members.filter(v => v !== value); } has(value) { - return this.members.includes(value); + return this.#members.includes(value); } static from(collection) { @@ -34,3 +32,4 @@ console.log(group.has(30)); group.add(10); group.delete(10); console.log(group.has(10)); +// → false diff --git a/code/solutions/06_3_iterable_groups.js b/code/solutions/06_3_iterable_groups.js index 202d98ebe..557a35fb2 100644 --- a/code/solutions/06_3_iterable_groups.js +++ b/code/solutions/06_3_iterable_groups.js @@ -1,20 +1,18 @@ class Group { - constructor() { - this.members = []; - } + #members = []; add(value) { if (!this.has(value)) { - this.members.push(value); + this.#members.push(value); } } delete(value) { - this.members = this.members.filter(v => v !== value); + this.#members = this.#members.filter(v => v !== value); } has(value) { - return this.members.includes(value); + return this.#members.includes(value); } static from(collection) { @@ -26,23 +24,26 @@ class Group { } [Symbol.iterator]() { - return new GroupIterator(this); + return new GroupIterator(this.#members); } } class GroupIterator { - constructor(group) { - this.group = group; - this.position = 0; + #members; + #position; + + constructor(members) { + this.#members = members; + this.#position = 0; } next() { - if (this.position >= this.group.members.length) { + if (this.#position >= this.#members.length) { return {done: true}; } else { - let result = {value: this.group.members[this.position], + let result = {value: this.#members[this.#position], done: false}; - this.position++; + this.#position++; return result; } } diff --git a/code/solutions/07_3_persistent_group.js b/code/solutions/07_3_persistent_group.js index 33020584c..31ec76c80 100644 --- a/code/solutions/07_3_persistent_group.js +++ b/code/solutions/07_3_persistent_group.js @@ -1,24 +1,25 @@ class PGroup { + #members; constructor(members) { - this.members = members; + this.#members = members; } add(value) { if (this.has(value)) return this; - return new PGroup(this.members.concat([value])); + return new PGroup(this.#members.concat([value])); } delete(value) { if (!this.has(value)) return this; - return new PGroup(this.members.filter(m => m !== value)); + return new PGroup(this.#members.filter(m => m !== value)); } has(value) { - return this.members.includes(value); + return this.#members.includes(value); } -} -PGroup.empty = new PGroup([]); + static empty = new PGroup([]); +} let a = PGroup.empty.add("a"); let ab = a.add("b"); diff --git a/code/solutions/08_2_the_locked_box.js b/code/solutions/08_2_the_locked_box.js index 6a36be3f4..b48665c01 100644 --- a/code/solutions/08_2_the_locked_box.js +++ b/code/solutions/08_2_the_locked_box.js @@ -1,34 +1,31 @@ -const box = { - locked: true, - unlock() { this.locked = false; }, - lock() { this.locked = true; }, - _content: [], +const box = new class { + locked = true; + #content = []; + + unlock() { this.locked = false; } + lock() { this.locked = true; } get content() { if (this.locked) throw new Error("Locked!"); - return this._content; + return this.#content; } }; function withBoxUnlocked(body) { let locked = box.locked; - if (!locked) { - return body(); - } - - box.unlock(); + if (locked) box.unlock(); try { return body(); } finally { - box.lock(); + if (locked) box.lock(); } } -withBoxUnlocked(function() { +withBoxUnlocked(() => { box.content.push("gold piece"); }); try { - withBoxUnlocked(function() { + withBoxUnlocked(() => { throw new Error("Pirates on the horizon! Abort!"); }); } catch (e) { diff --git a/code/solutions/09_1_regexp_golf.js b/code/solutions/09_1_regexp_golf.js index 402450353..ad84ccd4f 100644 --- a/code/solutions/09_1_regexp_golf.js +++ b/code/solutions/09_1_regexp_golf.js @@ -12,7 +12,7 @@ verify(/ferr(et|y|ari)/, ["ferret", "ferry", "ferrari"], ["ferrum", "transfer A"]); -verify(/ious\b/, +verify(/ious($|\P{L})/u, ["how delicious", "spacious room"], ["ruinous", "consciousness"]); @@ -20,13 +20,13 @@ verify(/\s[.,:;]/, ["bad punctuation ."], ["escape the dot"]); -verify(/\w{7}/, +verify(/\p{L}{7}/u, ["Siebentausenddreihundertzweiundzwanzig"], ["no", "three small words"]); -verify(/\b[^\We]+\b/i, +verify(/(^|\P{L})[^\P{L}e]+($|\P{L})/ui, ["red platypus", "wobbling nest"], - ["earth bed", "learning ape", "BEET"]); + ["earth bed", "bedrøvet abe", "BEET"]); function verify(regexp, yes, no) { diff --git a/code/solutions/09_2_quoting_style.js b/code/solutions/09_2_quoting_style.js index f5b0f8934..dcae1cddf 100644 --- a/code/solutions/09_2_quoting_style.js +++ b/code/solutions/09_2_quoting_style.js @@ -1,5 +1,5 @@ let text = "'I'm the cook,' he said, 'it's my job.'"; -console.log(text.replace(/(^|\W)'|'(\W|$)/g, '$1"$2')); +console.log(text.replace(/(^|\P{L})'|'(\P{L}|$)/gu, '$1"$2')); // → "I'm the cook," he said, "it's my job." diff --git a/code/solutions/10_2_roads_module.js b/code/solutions/10_2_roads_module.js index ec83e579b..af405b799 100644 --- a/code/solutions/10_2_roads_module.js +++ b/code/solutions/10_2_roads_module.js @@ -1,4 +1,4 @@ -const {buildGraph} = require("./graph"); +import {buildGraph} from "./graph"; const roads = [ "Alice's House-Bob's House", "Alice's House-Cabin", @@ -10,4 +10,4 @@ const roads = [ "Marketplace-Town Hall", "Shop-Town Hall" ]; -exports.roadGraph = buildGraph(roads.map(r => r.split("-"))); +export const roadGraph = buildGraph(roads.map(r => r.split("-"))); diff --git a/code/solutions/11_1_quiet_times.js b/code/solutions/11_1_quiet_times.js new file mode 100644 index 000000000..8d20b1fd2 --- /dev/null +++ b/code/solutions/11_1_quiet_times.js @@ -0,0 +1,20 @@ +async function activityTable(day) { + let table = []; + for (let i = 0; i < 24; i++) table[i] = 0; + + let logFileList = await textFile("camera_logs.txt"); + for (let filename of logFileList.split("\n")) { + let log = await textFile(filename); + for (let timestamp of log.split("\n")) { + let date = new Date(Number(timestamp)); + if (date.getDay() == day) { + table[date.getHours()]++; + } + } + } + + return table; +} + +activityTable(1) + .then(table => console.log(activityGraph(table))); diff --git a/code/solutions/11_2_real_promises.js b/code/solutions/11_2_real_promises.js new file mode 100644 index 000000000..1c263543c --- /dev/null +++ b/code/solutions/11_2_real_promises.js @@ -0,0 +1,20 @@ +function activityTable(day) { + let table = []; + for (let i = 0; i < 24; i++) table[i] = 0; + + return textFile("camera_logs.txt").then(files => { + return Promise.all(files.split("\n").map(name => { + return textFile(name).then(log => { + for (let timestamp of log.split("\n")) { + let date = new Date(Number(timestamp)); + if (date.getDay() == day) { + table[date.getHours()]++; + } + } + }); + })); + }).then(() => table); +} + +activityTable(6) + .then(table => console.log(activityGraph(table))); diff --git a/code/solutions/11_2_building_promiseall.js b/code/solutions/11_3_building_promiseall.js similarity index 100% rename from code/solutions/11_2_building_promiseall.js rename to code/solutions/11_3_building_promiseall.js diff --git a/code/solutions/12_4_fixing_scope.js b/code/solutions/12_4_fixing_scope.js index 1b4c6e723..fb75ba9f2 100644 --- a/code/solutions/12_4_fixing_scope.js +++ b/code/solutions/12_4_fixing_scope.js @@ -6,7 +6,7 @@ specialForms.set = (args, env) => { let value = evaluate(args[1], env); for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) { - if (Object.prototype.hasOwnProperty.call(scope, varName)) { + if (Object.hasOwn(scope, varName)) { scope[varName] = value; return value; } diff --git a/code/solutions/19_3_circles.html b/code/solutions/19_3_circles.html index 04d96bb19..4fcab5bf4 100644 --- a/code/solutions/19_3_circles.html +++ b/code/solutions/19_3_circles.html @@ -7,13 +7,13 @@ - - - <> - + <> + <>

@@ -28,8 +24,11 @@ <>

+ + diff --git a/src/chapter_info.js b/src/chapter_info.mjs similarity index 60% rename from src/chapter_info.js rename to src/chapter_info.mjs index 294570156..28347f920 100644 --- a/src/chapter_info.js +++ b/src/chapter_info.mjs @@ -2,13 +2,13 @@ // their starting code, and collects it into a big JSON object // together with the solution code. -const PJSON = require("./pseudo_json") -let fs = require("fs"); +import * as PJSON from "./pseudo_json.mjs" +import * as fs from "fs" +import * as path from "path" +import jszip from "jszip" let output = [], failed = false; -let allSolutions = fs.readdirSync("code/solutions/").filter(file => !/^(\.|2[012])/.test(file)); - for (let file of fs.readdirSync(".").sort()) { let match = /^((\d+).*).md$/.exec(file), chapNum = match && match[2]; if (!match || chapNum == 22) continue; @@ -29,10 +29,31 @@ for (let file of fs.readdirSync(".").sort()) { if (extraLinks || zip) chapter.links = (zip ? [zip] : []).concat(extraLinks || []); + function addSolution(name, file, type, num, startCode) { + let solution, extra + try { + solution = fs.readFileSync("code/solutions/" + file, "utf8"); + extra = /^\s*\s*(= 0 ? text.slice(exerciseSection) : ""; let header = /\n### (.*?)\n/g, nextHeader = /\n##+ \w/g; let num = 1; + while (match = header.exec(exerciseBlock)) { nextHeader.lastIndex = header.lastIndex let foundNext = nextHeader.exec(exerciseBlock) @@ -45,46 +66,43 @@ for (let file of fs.readdirSync(".").sort()) { if (!sourceBlock || sourceBlock[1].indexOf("null") > -1) continue; let type = sourceBlock[1].indexOf("html") > -1 ? "html" : "js"; let file = chapNum + "_" + num + "_" + match[1].toLowerCase().replace(/[^\-\s\w]/g, "").replace(/\s/g, "_") + "." + type; - let solution, extra - try { - solution = fs.readFileSync("code/solutions/" + file, "utf8"); - extra = /^\s*\s*( {\n if (next == current) return current;\n else return loop(next);\n });\n }\n return loop(nest.name);\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher's Shop\nlocateScalpel2(bigOak).then(console.log);\n// → Butcher's Shop", + goto: "https://eloquentjavascript.net/3rd_edition/code/#11.1" + }) if (chapter.number == 20) chapter.exercises = [ {name: "Search tool", - file: "code/solutions/20_1_search_tool.js", + file: "code/solutions/20_1_search_tool.mjs", number: 1, type: "js", code: nodeInfo, - solution: fs.readFileSync("code/solutions/20_1_search_tool.js", "utf8") + solution: fs.readFileSync("code/solutions/20_1_search_tool.mjs", "utf8") }, {name: "Directory creation", - file: "code/solutions/20_2_directory_creation.js", + file: "code/solutions/20_2_directory_creation.mjs", number: 2, type: "js", code: nodeInfo, - solution: fs.readFileSync("code/solutions/20_2_directory_creation.js", "utf8") + solution: fs.readFileSync("code/solutions/20_2_directory_creation.mjs", "utf8") }, {name: "A public space on the web", file: "code/solutions/20_3_a_public_space_on_the_web.zip", @@ -96,18 +114,18 @@ for (let file of fs.readdirSync(".").sort()) { ]; if (chapter.number == 21) chapter.exercises = [ {name: "Disk persistence", - file: "code/solutions/21_1_disk_persistence.js", + file: "code/solutions/21_1_disk_persistence.mjs", number: 1, type: "js", code: nodeInfo, - solution: fs.readFileSync("code/solutions/21_1_disk_persistence.js", "utf8") + solution: fs.readFileSync("code/solutions/21_1_disk_persistence.mjs", "utf8") }, {name: "Comment field resets", - file: "code/solutions/21_2_comment_field_resets.js", + file: "code/solutions/21_2_comment_field_resets.mjs", number: 2, type: "js", code: nodeInfo, - solution: fs.readFileSync("code/solutions/21_2_comment_field_resets.js", "utf8") + solution: fs.readFileSync("code/solutions/21_2_comment_field_resets.mjs", "utf8") } ]; @@ -117,35 +135,54 @@ for (let file of fs.readdirSync(".").sort()) { output.push({ title: "JavaScript and Performance", number: 22, - start_code: "\n\n\n", + start_code: "\n\n\n", include: ["code/draw_layout.js", "code/chapter/22_fast.js"], exercises: [ - {name: "Pathfinding", - file: "code/solutions/22_1_pathfinding.js", + {name: "Prime numbers", + file: "code/solutions/22_1_prime_numbers.js", number: 1, type: "js", + code: "function* primes() {\n for (let n = 2;; n++) {\n // ...\n }\n}\n\nfunction measurePrimes() {\n // ...\n}\n\nmeasurePrimes();\n", + solution: fs.readFileSync("code/solutions/22_1_prime_numbers.js", "utf8") + }, + {name: "Faster prime numbers", + file: "code/solutions/22_2_faster_prime_numbers.js", + number: 2, + type: "js", + code: "function* primes() {\n for (let n = 2;; n++) {\n // ...\n }\n}\n\nfunction measurePrimes() {\n // ...\n}\n\nmeasurePrimes();\n", + solution: fs.readFileSync("code/solutions/22_2_faster_prime_numbers.js", "utf8"), + }, + {name: "Pathfinding [3rd ed]", + file: "code/solutions/22_1_pathfinding.js", + number: "1[3]", + type: "js", code: "function findPath(a, b) {\n // Your code here...\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n", - solution: fs.readFileSync("code/solutions/22_1_pathfinding.js", "utf8") + solution: fs.readFileSync("code/solutions/22_1_pathfinding.js", "utf8"), + goto: "https://eloquentjavascript.net/3rd_edition/code/#22.1" }, - {name: "Timing", + {name: "Timing [3rd ed]", file: "code/solutions/22_2_timing.js", - number: 2, + number: "2[3]", type: "js", code: "", - solution: fs.readFileSync("code/solutions/22_2_timing.js", "utf8") + solution: fs.readFileSync("code/solutions/22_2_timing.js", "utf8"), + goto: "https://eloquentjavascript.net/3rd_edition/code/#22.2" }, - {name: "Optimizing", + {name: "Optimizing [3rd ed]", file: "code/solutions/22_3_optimizing.js", - number: 3, + number: "3[3]", type: "js", code: "", - solution: fs.readFileSync("code/solutions/22_3_optimizing.js", "utf8") + solution: fs.readFileSync("code/solutions/22_3_optimizing.js", "utf8"), + goto: "https://eloquentjavascript.net/3rd_edition/code/#22.3" } ] }); -if (allSolutions.length) { - console.error("Solution files " + allSolutions + " were not used."); +let usedSolutions = new Set() +for (let ch of output) for (let ex of ch.exercises) usedSolutions.add(path.basename(ex.file).replace(/\..*/, "")) +for (let file of fs.readdirSync("code/solutions/")) if (!usedSolutions.has(file.replace(/\..*/, ""))) { + console.error("Solution file " + file + " was not used."); failed = true; } @@ -186,12 +223,12 @@ function chapterZipFile(meta, chapter) { if (!chapter.start_code) throw new Error("zip but no start code"); let data = /(\S+)(?:\s+include=(.*))?/.exec(JSON.parse(spec[1])) let name = "code/chapter/" + chapter.id + ".zip"; - let files = (chapter.include || []).concat(data[2] ? JSON.parse(data[2]) : []); + let files = (chapter.include || []).concat(data[2] ? JSON.parse(data[2]) : []).filter(f => !/(^|\/)_/.test(f)); let exists = fs.existsSync(name) && fs.statSync(name).mtime; if (exists && files.every(file => fs.statSync("html/" + file).mtime < exists)) return name; - let zip = new (require("jszip")); + let zip = new jszip; for (let file of files) { zip.file(chapter.id + "/" + file, fs.readFileSync("html/" + file)); } @@ -207,6 +244,6 @@ function chapterZipFile(meta, chapter) { if (chapter.include) js = "// load dependencies\nrequire(\"./code/load\")(" + chapter.include.map(JSON.stringify).join(", ") + ");\n\n" + js; zip.file(chapter.id + "/run_with_node.js", js); } - fs.writeFileSync(name, zip.generate({type: "nodebuffer"})); + zip.generateAsync({type: "uint8array"}).then(content => fs.writeFileSync(name, content)); return name; } diff --git a/src/check_links.js b/src/check_links.mjs similarity index 93% rename from src/check_links.js rename to src/check_links.mjs index 5630714cc..2fb6dcc99 100644 --- a/src/check_links.js +++ b/src/check_links.mjs @@ -1,4 +1,4 @@ -let {readdirSync, readFileSync} = require("fs") +import {readdirSync, readFileSync} from "fs" let files = Object.create(null) for (let name of readdirSync(".")) { diff --git a/src/client/code.mjs b/src/client/code.mjs new file mode 100644 index 000000000..32fadfc93 --- /dev/null +++ b/src/client/code.mjs @@ -0,0 +1,245 @@ +import {EditorView, keymap} from "@codemirror/view" +import {language} from "@codemirror/language" +import {EditorState, Facet} from "@codemirror/state" +import {createState, updateLanguage} from "./editor.mjs" +import {Sandbox} from "./sandbox.mjs" + +const contextFacet = Facet.define({combine: vs => vs[0]}) + +function guessType(doc) { + let scan + if (typeof doc == "string") { + scan = doc + } else { + for (let i = 1; i <= doc.lines; i++) { + let line = doc.line(i).text + if (/\S/.test(line)) { scan = line; break } + } + } + return /^[\s\w]* { + let guessed + if (tr.docChanged && tr.startState.facet(contextFacet).type == null && + (guessed = guessType(tr.newDoc)) != tr.startState.facet(language).name) + return {effects: updateLanguage(guessed)} + return null +}) + +class CodeSandbox { + constructor() { + this.extensions = [ + keymap.of([ + {key: "Ctrl-Enter", run: () => { this.runCode(); return true }}, + {key: "Cmd-Enter", run: () => { this.runCode(); return true }} + ]), + modeGuesser + ] + this.editor = new EditorView({ + state: createState("", "javascript", [ + this.extensions, + contextFacet.of({include: [], mode: null}) + ]), + parent: document.querySelector("#editor") + }) + + this.output = new Sandbox.Output(document.querySelector(".sandbox-output")) + this.sandbox = null + + this.chapters = document.querySelector("#chapters") + chapterData.forEach(chapter => { + this.chapters.appendChild(opt(chapter.number, chapter.number + ". " + chapter.title)) + chapter.exercises.forEach(exercise => { + exercise.chapter = chapter + }) + }) + this.chapters.addEventListener("change", () => { + this.selectChapter(this.chapters.value) + document.location.hash = "#" + this.chapters.value + }) + + this.per = document.querySelector("#per_chapter") + this.per.addEventListener("change", () => { + this.selectContext(this.per.value) + document.location.hash = "#" + (this.per.value == "box" ? this.chapters.value : this.per.value) + }) + this.fileList = document.querySelector("#files") + this.fileInfo = document.querySelector("#fileInfo") + this.runLocally = document.querySelector("#runLocally") + this.localFileList = document.querySelector("#local-files") + + document.querySelector("#run").addEventListener("click", () => this.runCode()) + + document.querySelector("#solution").addEventListener("click", () => { + let context = this.editor.state.facet(contextFacet) + this.setEditorState(context.solution, context) + }) + + this.parseFragment() || this.selectChapter(0, "box") + addEventListener("hashchange", () => this.parseFragment()) + } + + setEditorState(code, context) { + this.editor.setState(createState(code, context.type || guessType(code), [this.extensions, contextFacet.of(context)])) + } + + selectContext(value) { + this.output.clear() + this.clearSandbox() + let chapter = getChapter(this.chapters.value), visible + if (value == "box") { + let code = (this.chapters.value < 20 || this.chapters.value > 21) + ? "Run code here in the context of Chapter " + chapter.number + : "Code from Node.js chapters can't be run in the browser" + let guessed = guessType(chapter.start_code) + if (guessed == "javascript") + code = "// " + code + else + code = "" + if (chapter.start_code) code += "\n\n" + chapter.start_code + this.setEditorState(code, {include: chapter.include}) + visible = "box" + } else { + let exercise = findExercise(value, chapter) + if (exercise.goto) { + document.location = exercise.goto + return + } + this.setEditorState(exercise.code, { + include: chapter.include, + solution: exercise.solution, + type: exercise.type + }) + visible = "exercise" + let link = document.querySelector("#download") + link.setAttribute("download", "solution" + value + ".js") + if (/\.zip$/.test(exercise.file)) + link.href = "../" + exercise.file + else + link.href = "data:text/plain;charset=UTF-8," + encodeURIComponent(exercise.solution) + } + ["box", "exercise"].forEach(id => { + document.querySelector("#" + id + "_info").style.display = + (id == visible ? "" : "none") + }) + } + + clearSandbox() { + if (this.sandbox) { + this.sandbox.frame.remove() + this.sandbox = null + } + } + + runCode() { + this.clearSandbox() + let val = this.editor.state.doc.toString(), type = this.editor.state.facet(language).name + let context = this.editor.state.facet(contextFacet) + Sandbox.create({ + loadFiles: hasIncludes(val, context.include) ? [] : context.include, + emptyPath: "../", + place: type == "html" && + function(node) { + let out = document.querySelector(".sandbox-output") + out.parentNode.insertBefore(node, out) + } + }).then(box => { + this.sandbox = box + this.output.clear() + if (type == "html") + box.setHTML(val, this.output) + else + box.run(val, this.output) + }) + } + + selectChapter(number, context) { + this.per.textContent = "" + let chapter = getChapter(number) + if (chapter.exercises.length) { + this.per.appendChild(opt("box", "Select an exercise")) + chapter.exercises.forEach(exercise => { + let num = chapter.number + "." + exercise.number + this.per.appendChild(opt(num, num + " " + exercise.name)) + }) + } else { + this.per.appendChild(opt("box", "This chapter has no exercises")) + } + this.fileInfo.style.display = this.runLocally.style.display = "none" + this.fileList.textContent = this.localFileList.textContent = "" + if (chapter.links) chapter.links.forEach((file, i) => { + if (!i) this.runLocally.style.display = "" + addItem(this.localFileList, file) + }) + if (chapter.include) chapter.include.forEach((file, i) => { + if (!i) this.fileInfo.style.display = "" + if (!/(^|\/)_/.test(file)) addItem(this.fileList, file) + }) + this.selectContext(context || "box") + } + + parseFragment() { + let hash = document.location.hash.slice(1) + let valid = /^(\d+)(?:\.(\d+.*))?$/.exec(hash) + let chapter, exercise + if (valid) { + chapter = getChapter(Number(valid[1])) + exercise = chapter && valid[2] && findExercise(hash, chapter) + if (!chapter || valid[2] && !exercise) valid = null + } + if (valid) { + let perValue = exercise ? hash : "box", setPer = false + if (this.chapters.value != valid[1]) { + this.chapters.value = valid[1] + this.selectChapter(Number(valid[1]), perValue) + setPer = true + } + if (this.per.value != perValue) { + this.per.value = perValue + if (!setPer) this.selectContext(perValue) + } + return true + } + } +} + +function hasIncludes(code, include) { + if (!include) return code + + let re = /(?:\s|)*