Eloquent JavaScript PDF - 3rd Edition

Summary

Eloquent JavaScript, 3rd edition, by Marijn Haverbeke, is a comprehensive textbook on the JavaScript programming language. The book covers various aspects of JavaScript, including values, types, operators, program structure, functions, data structures, higher-order functions, and more. It provides a clear explanation of the language concepts with examples and exercises.

Full Transcript

Eloquent JavaScript 3rd edition Marijn Haverbeke Copyright © 2018 by Marijn Haverbeke This work is licensed under a Creative Commons attribution-noncommercial license (http://creativecommons.org/licenses/by-nc/3.0/). All code in the book may also be considered licensed under an MIT l...

Eloquent JavaScript 3rd edition Marijn Haverbeke Copyright © 2018 by Marijn Haverbeke This work is licensed under a Creative Commons attribution-noncommercial license (http://creativecommons.org/licenses/by-nc/3.0/). All code in the book may also be considered licensed under an MIT license (https://eloquentjavascript. net/code/LICENSE). The illustrations are contributed by various artists: Cover and chapter illus- trations by Madalina Tantareanu. Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular expression diagrams in Chapter 9 generated with regexper.com by Jeff Avallone. Village photograph in Chapter 11 by Fabrice Creuzot. Game concept for Chapter 16 by Thomas Palef. The third edition of Eloquent JavaScript was made possible by 325 financial backers. You can buy a print version of this book, with an extra bonus chapter included, printed by No Starch Press at http://a-fwd.com/com=marijhaver-20&asin- com=1593279507. i Contents Introduction 1 On programming.............................. 2 Why language matters........................... 3 What is JavaScript?............................. 6 Code, and what to do with it....................... 7 Overview of this book............................ 8 Typographic conventions.......................... 9 1 Values, Types, and Operators 10 Values..................................... 10 Numbers................................... 11 Strings.................................... 13 Unary operators............................... 15 Boolean values................................ 16 Empty values................................. 19 Automatic type conversion......................... 19 Summary................................... 21 2 Program Structure 22 Expressions and statements........................ 22 Bindings................................... 23 Binding names................................ 25 The environment.............................. 25 Functions................................... 26 The console.log function.......................... 26 Return values................................ 27 Control flow................................. 27 Conditional execution............................ 28 while and do loops.............................. 30 Indenting Code............................... 32 for loops................................... 33 Breaking Out of a Loop.......................... 33 ii Updating bindings succinctly....................... 34 Dispatching on a value with switch.................... 35 Capitalization................................ 36 Comments.................................. 36 Summary................................... 37 Exercises................................... 38 3 Functions 40 Defining a function............................. 40 Bindings and scopes............................. 41 Functions as values............................. 43 Declaration notation............................ 44 Arrow functions............................... 45 The call stack................................ 46 Optional Arguments............................. 47 Closure.................................... 49 Recursion................................... 50 Growing functions.............................. 53 Functions and side effects......................... 55 Summary................................... 56 Exercises................................... 57 4 Data Structures: Objects and Arrays 59 The weresquirrel............................... 59 Data sets................................... 60 Properties.................................. 61 Methods................................... 62 Objects.................................... 63 Mutability.................................. 65 The lycanthrope’s log............................ 67 Computing correlation........................... 69 Array loops.................................. 70 The final analysis.............................. 71 Further arrayology.............................. 73 Strings and their properties........................ 74 Rest parameters............................... 76 The Math object............................... 77 Destructuring................................ 79 JSON..................................... 80 Summary................................... 81 iii Exercises................................... 82 5 Higher-Order Functions 85 Abstraction.................................. 86 Abstracting repetition........................... 86 Higher-order functions........................... 88 Script data set................................ 89 Filtering arrays............................... 90 Transforming with map........................... 91 Summarizing with reduce.......................... 92 Composability................................ 93 Strings and character codes........................ 95 Recognizing text............................... 97 Summary................................... 98 Exercises................................... 99 6 The Secret Life of Objects 100 Encapsulation................................ 100 Methods................................... 101 Prototypes.................................. 102 Classes.................................... 104 Class notation................................ 105 Overriding derived properties....................... 106 Maps..................................... 108 Polymorphism................................ 110 Symbols.................................... 110 The iterator interface............................ 112 Getters, setters, and statics........................ 114 Inheritance.................................. 116 The instanceof operator........................... 117 Summary................................... 118 Exercises................................... 119 7 Project: A Robot 121 Meadowfield................................. 121 The task................................... 123 Persistent data................................ 125 Simulation.................................. 126 The mail truck’s route........................... 128 Pathfinding.................................. 128 iv Exercises................................... 131 8 Bugs and Errors 132 Language................................... 132 Strict mode.................................. 133 Types..................................... 134 Testing.................................... 135 Debugging.................................. 136 Error propagation.............................. 138 Exceptions.................................. 139 Cleaning up after exceptions........................ 141 Selective catching.............................. 143 Assertions.................................. 145 Summary................................... 146 Exercises................................... 146 9 Regular Expressions 148 Creating a regular expression....................... 148 Testing for matches............................. 149 Sets of characters.............................. 149 Repeating parts of a pattern........................ 151 Grouping subexpressions.......................... 152 Matches and groups............................. 152 The Date class................................ 154 Word and string boundaries........................ 155 Choice patterns............................... 156 The mechanics of matching........................ 156 Backtracking................................. 157 The replace method............................. 159 Greed..................................... 161 Dynamically creating RegExp objects.................. 162 The search method............................. 163 The lastIndex property........................... 164 Parsing an INI file.............................. 166 International characters........................... 168 Summary................................... 169 Exercises................................... 171 10 Modules 173 Modules.................................... 173 v Packages................................... 174 Improvised modules............................. 175 Evaluating data as code.......................... 176 CommonJS.................................. 177 ECMAScript modules............................ 179 Building and bundling........................... 181 Module design................................ 182 Summary................................... 184 Exercises................................... 184 11 Asynchronous Programming 186 Asynchronicity................................ 186 Crow tech................................... 188 Callbacks................................... 189 Promises................................... 191 Failure.................................... 192 Networks are hard.............................. 194 Collections of promises........................... 196 Network flooding.............................. 197 Message routing............................... 198 Async functions............................... 201 Generators.................................. 203 The event loop................................ 204 Asynchronous bugs............................. 205 Summary................................... 207 Exercises................................... 207 12 Project: A Programming Language 209 Parsing.................................... 209 The evaluator................................ 214 Special forms................................. 215 The environment.............................. 217 Functions................................... 218 Compilation................................. 220 Cheating................................... 220 Exercises................................... 221 13 JavaScript and the Browser 224 Networks and the Internet......................... 224 The Web................................... 226 vi HTML.................................... 226 HTML and JavaScript........................... 229 In the sandbox................................ 230 Compatibility and the browser wars................... 231 14 The Document Object Model 232 Document structure............................. 232 Trees..................................... 233 The standard................................. 234 Moving through the tree.......................... 235 Finding elements.............................. 236 Changing the document.......................... 237 Creating nodes................................ 238 Attributes.................................. 240 Layout.................................... 241 Styling.................................... 243 Cascading styles............................... 244 Query selectors................................ 246 Positioning and animating......................... 247 Summary................................... 249 Exercises................................... 249 15 Handling Events 252 Event handlers................................ 252 Events and DOM nodes.......................... 253 Event objects................................. 254 Propagation................................. 254 Default actions................................ 256 Key events.................................. 257 Pointer events................................ 258 Scroll events................................. 262 Focus events................................. 263 Load event.................................. 264 Events and the event loop......................... 265 Timers.................................... 266 Debouncing.................................. 267 Summary................................... 268 Exercises................................... 269 vii 16 Project: A Platform Game 271 The game................................... 271 The technology............................... 272 Levels..................................... 272 Reading a level................................ 273 Actors..................................... 275 Encapsulation as a burden......................... 279 Drawing.................................... 279 Motion and collision............................. 285 Actor updates................................ 288 Tracking keys................................ 290 Running the game.............................. 291 Exercises................................... 293 17 Drawing on Canvas 295 SVG...................................... 295 The canvas element............................. 296 Lines and surfaces.............................. 297 Paths..................................... 298 Curves.................................... 300 Drawing a pie chart............................. 302 Text...................................... 303 Images.................................... 304 Transformation............................... 306 Storing and clearing transformations................... 308 Back to the game.............................. 310 Choosing a graphics interface....................... 315 Summary................................... 316 Exercises................................... 317 18 HTTP and Forms 319 The protocol................................. 319 Browsers and HTTP............................ 321 Fetch..................................... 323 HTTP sandboxing.............................. 325 Appreciating HTTP............................. 325 Security and HTTPS............................ 326 Form fields.................................. 326 Focus..................................... 328 Disabled fields................................ 329 viii The form as a whole............................ 330 Text fields.................................. 331 Checkboxes and radio buttons....................... 333 Select fields.................................. 334 File fields................................... 335 Storing data client-side........................... 337 Summary................................... 339 Exercises................................... 340 19 Project: A Pixel Art Editor 342 Components................................. 342 The state................................... 344 DOM building................................ 346 The canvas.................................. 346 The application............................... 349 Drawing tools................................ 352 Saving and loading............................. 354 Undo history................................. 357 Let’s draw.................................. 359 Why is this so hard?............................ 360 Exercises................................... 360 20 Node.js 363 Background.................................. 363 The node command............................. 364 Modules.................................... 365 Installing with NPM............................ 366 The file system module........................... 369 The HTTP module............................. 370 Streams.................................... 372 A file server................................. 374 Summary................................... 380 Exercises................................... 380 21 Project: Skill-Sharing Website 382 Design..................................... 382 Long polling................................. 383 HTTP interface............................... 384 The server.................................. 386 The client................................... 394 ix Exercises................................... 400 Exercise Hints 402 Program Structure............................. 402 Functions................................... 403 Data Structures: Objects and Arrays................... 404 Higher-Order Functions........................... 406 The Secret Life of Objects......................... 407 Project: A Robot.............................. 408 Bugs and Errors............................... 409 Regular Expressions............................. 409 Modules.................................... 410 Asynchronous Programming........................ 412 Project: A Programming Language.................... 413 The Document Object Model....................... 414 Handling Events............................... 414 Project: A Platform Game......................... 416 Drawing on Canvas............................. 416 HTTP and Forms.............................. 418 Project: A Pixel Art Editor........................ 420 Node.js.................................... 422 Project: Skill-Sharing Website....................... 423 x “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.” —Ellen Ullman, Close to the Machine: Technophilia and its Discontents Introduction This is a book about instructing computers. 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. That is where programming may come in. Programming is the act of con- structing 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. 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 languages. 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. 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. This book will try to make you familiar enough with this language to do useful and amusing things with it. 1 On programming Besides explaining JavaScript, I will introduce the basic principles of program- ming. 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. 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. When action grows unprofitable, gather information; when infor- mation grows unprofitable, sleep. —Ursula K. Le Guin, The Left Hand of Darkness 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 a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands. 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 2 art of programming is the skill of controlling complexity. The great program is subdued—made simple in its complexity. 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. 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. Why language matters In the beginning, at the birth of computing, there were no programming lan- guages. Programs looked something like this: 00110001 00000000 00000000 00110001 00000001 00000001 00110011 00000001 00000010 01010001 00001011 00000010 00100010 00000010 00001000 01000011 00000001 00000000 01000001 00000001 00000001 00010000 00000010 00000000 01100010 00000000 00000000 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. 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. Each line of the previous program contains a single instruction. It could be 3 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 instruc- tion 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. 9. Output the value of memory location 0. 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. 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]. [end] Output “total”. 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 4 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. Here is the same program in JavaScript: let total = 0, count = 1; while (count 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. console.log("Aardvark" < "Zoroaster") 16 // → true 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. Other similar operators are >= (greater than or equal to), , ==, 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: 1 + 1 == 2 && 10 * 10 > 50 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: console.log(true ? 1 : 2); // → 1 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. 18 Empty values 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. 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 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: console.log(8 * null) // → 0 console.log("5" - 1) // → 4 console.log("5" + 1) // → 51 console.log("five" * 2) // → NaN console.log(false == 0) // → true 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 concate- nation before numeric addition, so the 1 is converted to "1" (from number to string). 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 19 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 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. console.log(null == undefined); // → true 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. 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. 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 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 || 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 20 for values of other types. console.log(null || "user") // → user console.log("Agnes" || "user") // → Agnes 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 "!?". 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. 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 will start tying these expressions together into basic programs. 21 “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!” —_why, Why’s (Poignant) Guide to Ruby Chapter 2 Program Structure In this chapter, we will start to do things that can actually be called program- ming. 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 In Chapter 1, 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. 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. 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. If an expression corresponds to a sentence fragment, a JavaScript statement corresponds to a full sentence. A program is a list of statements. 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 22 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 effects. 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. 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 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: let caught = 5 * 5; 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. 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. 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; console.log(ten * ten); // → 100 23 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"; console.log(mood); // → light mood = "dark"; console.log(mood); // → dark 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. 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 luigisDebt = 140; luigisDebt = luigisDebt - 35; console.log(luigisDebt); // → 105 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. A single let statement may define multiple bindings. The definitions must be separated by commas. let one = 1, two = 2; console.log(one + two); // → 3 The words var and const can also be used to create bindings, in a way similar to let. 24 var name = "Ayda"; const greeting = "Hello "; console.log(greeting + name); // → Hello Ayda 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. 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 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. Binding names 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. Words with a special meaning, such as let, are keywords, 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. 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 new package private protected public return static super switch this throw true try typeof var void while with yield 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. The 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 25 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 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: prompt("Enter passcode"); 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 arguments. 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 console.log function In the examples, I used console.log to output values. Most JavaScript sys- tems (including all modern web browsers and Node.js) provide a console.log 26 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-option-I. If that does not work, search through the menus for an item named Developer Tools or similar. Though binding names cannot contain period characters, 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 4. Return values 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. console.log(Math.max(2, 4)); // → 4 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: console.log(Math.min(2, 4) + 100); // → 102 The next chapter explains how to write your own functions. 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. 27 let theNumber = Number(prompt("Pick a number")); console.log("Your number is the square root of " + theNumber * theNumber); 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: 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. 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. let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); } With this modification, if you enter “parrot”, no output is shown. 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. 28 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 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 state- ment, 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 state- ment 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"); // → It's true 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. let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); } else { console.log("Hey. Why didn't you give me a number?"); } 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")); if (num < 10) { console.log("Small"); } else if (num < 100) { console.log("Medium"); } else { console.log("Large"); 29 } 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: while and do loops Consider a program that outputs all even numbers from 0 to 12. One way to write this is as follows: console.log(0); console.log(2); console.log(4); console.log(6); console.log(8); console.log(10); console.log(12); 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. 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: 30 let number = 0; while (number { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; }; The arrow comes after the list of parameters and is followed by the function’s body. It expresses something like “this input (the parameters) produces this result (the body)”. 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; }; const square2 = x => x * x; When an arrow function has no parameters at all, its parameter list is just an empty set of parentheses. const horn = () => { console.log("Toot"); }; There’s no deep reason to have both arrow functions and function expres- 45 sions in the language. Apart from a minor detail, which we’ll discuss in Chapter 6, 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 5. The call stack 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) { console.log("Hello " + who); } greet("Harry"); console.log("Bye"); 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. We could show the flow of control schematically like this: not in function in greet in console.log in greet not in function in console.log not in function Because a function has to jump back to the place that called it when it re- turns, 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. 46 When a function returns, it removes the top context from the stack and uses that context to continue execution. 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”. function chicken() { return egg(); } function egg() { return chicken(); } console.log(chicken() + " came first."); // → ?? Optional Arguments The following code is allowed and executes without any problem: function square(x) { return x * x; } 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. 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. The downside of this is that it is possible—likely, even—that you’ll acciden- tally 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 47 imitate the - operator by acting on either one or two arguments: function minus(a, b) { if (b === undefined) return -a; else return a - b; } console.log(minus(10)); // → -10 console.log(minus(10, 5)); // → 5 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 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. function power(base, exponent = 2) { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; } console.log(power(4)); // → 16 console.log(power(2, 6)); // → 64 In the next chapter, 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. console.log("C", "O", 2); // → C O 2 48 Closure 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. function wrapValue(n) { let local = n; return () => local; } let wrap1 = wrapValue(1); let wrap2 = wrapValue(2); console.log(wrap1()); // → 1 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 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. 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) { return number => number * factor; } let twice = multiplier(2); console.log(twice(5)); 49 // → 10 The explicit local binding from the wrapValue example isn’t really needed since a parameter is itself a local binding. 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. Recursion 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: function power(base, exponent) { if (exponent == 0) { return 1; } else { return base * power(base, exponent - 1); } } console.log(power(2, 3)); // → 8 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. But this implementation has one problem: in typical JavaScript implementa- tions, it’s about three times slower than the looping version. Running through a simple loop is generally cheaper than calling a function multiple times. 50 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. Al- most any program can be made faster by making it bigger and more convoluted. The programmer has to decide on 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 straightfor- ward is helpful. 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. Therefore, always start by writing something that’s correct and easy to un- derstand. 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. Recursion is not always just an inefficient alternative to looping. Some prob- lems 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. 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: function findSolution(target) { function find(current, history) { if (current == target) { return history; } else if (current > target) { return null; } else { return find(current + 5, `(${history} + 5)`) || find(current * 3, `(${history} * 3)`); } } return find(1, "1"); } 51 console.log(findSolution(24)); // → (((1 * 3) + 5) * 3) Note that this program doesn’t necessarily find the shortest sequence of op- erations. 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 arguments: 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. 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. 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. find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5) + 5)") too big find(33, "(((1 + 5) + 5) * 3)") too big find(18, "((1 + 5) * 3)") too big find(3, "(1 * 3)") find(8, "((1 * 3) + 5)") find(13, "(((1 * 3) + 5) + 5)") found! The indentation indicates the depth of the call stack. The first time find is 52 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. Growing functions There are two more or less natural ways for functions to be introduced into programs. 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 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. 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. 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. 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. function printFarmInventory(cows, chickens) { 53 let cowString = String(cows); while (cowString.length < 3) { cowString = "0" + cowString; } console.log(`${cowString} Cows`); let chickenString = String(chickens); while (chickenString.length < 3) { chickenString = "0" + chickenString; } console.log(`${chickenString} Chickens`); } printFarmInventory(7, 11); 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? 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) { let numberString = String(number); while (numberString.length < 3) { numberString = "0" + numberString; } console.log(`${numberString} ${label}`); } function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Cows"); printZeroPaddedWithLabel(chickens, "Chickens"); printZeroPaddedWithLabel(pigs, "Pigs"); } printFarmInventory(7, 11, 3); It works! But that name, printZeroPaddedWithLabel, is a little awkward. It conflates three things—printing, zero-padding, and adding a label—into a 54 single function. Instead of lifting out the repeated part of our program wholesale, let’s try to pick out a single concept. function zeroPad(number, width) { let string = String(number); while (string.length < width) { string = "0" + string; } return string; } function printFarmInventory(cows, chickens, pigs) { console.log(`${zeroPad(cows, 3)} Cows`); console.log(`${zeroPad(chickens, 3)} Chickens`); console.log(`${zeroPad(pigs, 3)} Pigs`); } printFarmInventory(7, 16, 3); 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. 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 charac- ters 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 “frameworks” 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. Functions and side effects 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.) The first helper function in the farm example, printZeroPaddedWithLabel, 55 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. 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. 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. Summary This chapter taught you how to write your own functions. The function key- word, 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 const f = function(a) { console.log(a + 2); }; // Declare g to be a function function g(a, b) { return a * b * 3.5; } // A less verbose function value let h = a => a % 3; 56 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. Separating the tasks your program performs into different functions is help- ful. 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 Minimum The previous chapter 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. Recursion 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: Zero is even. One is 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. Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this? Bean counting 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. 57 Write a function countBs that takes a string as its only argument and returns a number that indicates how many uppercase “B” characters there are in the string. Next, write a function called countChar that behaves like countBs, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase “B” characters). Rewrite countBs to make use of this new function. 58 “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.” —Charles Babbage, Passages from the Life of a Philosopher (1864) Chapter 4 Data Structures: Objects and Arrays Numbers, Booleans, and strings are the atoms that data structures are built from. Many types of information require more than one atom, though. Ob- jects 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 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 online coding sandbox for the book (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 weresquirrel 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 59 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. 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 transforma- tions. The first thing he needs is a data structure to store this information. Data sets 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. 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. 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]; console.log(listOfNumbers); // → 5 console.log(listOfNumbers); // → 2 console.log(listOfNumbers[2 - 1]); // → 3 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 first index of an array is zero, not one. So the first element is retrieved with listOfNumbers. 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. 60 Properties 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). 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. null.length; // → TypeError: null has no properties 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 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 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"]. 61 Methods Both string and array values contain, in addition to the length property, a number of properties that hold function values. let doh = "Doh"; console.log(typeof doh.toUpperCase); // → function console.log(doh.toUpperCase()); // → DOH 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. Interestingly, even though the call to toUpperCase does not pass any argu- ments, the function somehow has access to the string "Doh", the value whose property we called. How this works is described in Chapter 6. Properties that contain functions are generally called methods of the value they belong to, as in “toUpperCase is a method of a string”. This example demonstrates two methods you can use to manipulate arrays: let sequence = [1, 2, 3]; sequence.push(4); sequence.push(5); console.log(sequence); // → [1, 2, 3, 4, 5] console.log(sequence.pop()); // → 5 console.log(sequence); // → [1, 2, 3, 4] 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. 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, which is an instance of the same idea. 62 Objects 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. 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 = { squirrel: false, events: ["work", "touched tree", "pizza", "running"] }; console.log(day1.squirrel); // → false console.log(day1.wolf); // → undefined day1.wolf = false; console.log(day1.wolf); // → false 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. let descriptions = { work: "Went to work", "touched tree": "Touched a tree" }; 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. Reading a property that doesn’t exist will give you the value undefined. 63 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. To briefly return to our tentacle model of bindings—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. 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}; console.log(anObject.left); // → 1 delete anObject.left; console.log(anObject.left); // → undefined console.log("left" in anObject); // → false console.log("right" in anObject); // → true 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. 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. 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. 64 let objectA = {a: 1, b: 2}; Object.assign(objectA, {b: 3, c: 4}); console.log(objectA); // → {a: 1, b: 3, c: 4} 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. We will represent the journal that Jacques keeps as an array of objects. let journal = [ {events: ["work", "touched tree", "pizza", "running", "television"], squirrel: false}, {events: ["work", "ice cream", "cauliflower", "lasagna", "touched tree", "brushed teeth"], squirrel: false}, {events: ["weekend", "cycling", "break", "peanuts", "beer"], squirrel: true}, ]; Mutability We will get to actual programming real soon now. First there’s one more piece of theory to understand. 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. 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 65 and having two different objects that contain the same properties. Consider the following code: let object1 = {value: 10}; let object2 = object1; let object3 = {value: 10}; console.log(object1 == object2); // → true console.log(object1 == object3); // → false object1.value = 15; console.log(object2.value); // → 15 console.log(object3.value); // → 10 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. 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. const score = {visitors: 0, home: 0}; // This is okay score.visitors = 1; // This isn't allowed score = {visitors: 1, home: 1}; 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 prop- erties. There is no “deep” comparison operation built into JavaScript, which compares objects by contents, but it is possible to write it yourself (which is 66 one of the exercises at the end of this chapter). The lycanthrope's log So, Jacques starts up his JavaScript interpreter and sets up the environment he needs to keep his journal. let journal = []; function addEntry(events, squirrel) { journal.push({events, squirrel}); } 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. 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. addEntry(["work", "touched tree", "pizza", "running", "television"], false); addEntry(["work", "ice cream", "cauliflower", "lasagna", "touched tree", "brushed teeth"], false); 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. Correlation is a measure of dependence between statistical variables. A sta- tistical 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, 67 the other is false. 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: No squirrel, no pizza76 No squirrel, pizza 9 Squirrel, no pizza 4 Squirrel, pizza 1 If we call that table n, we can compute φ using the following formula: n11 n00 − n10 n01 φ= √ (4.1) n1 n0 n 1 n 0 (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 n01 indicates the number of measurements where the first vari- able (squirrelness) is false (0) and the second variable (pizza) is true (1). In the pizza table, n01 is 9. The value n1 refers to the sum of all measurements where the first variable is true, which is 5 in the example table. Likewise, n 0 refers to the sum of the measurements where the second variable is false. 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. This comes out to φ ≈ 0.069, which is tiny. Eating pizza does not appear to have influence on the transformations. 68 Computing correlation 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 con- taining 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 expres- sions that access the table pleasantly short. We’ll interpret the indices to the array as two-bit binary numbers, 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 oc- cur. This happened four times. And since binary 10 is 2 in decimal notation, we will store this number at index 2 of the array. This is the function that computes the φ coefficient from such an array: function phi(table) { return (table * table - table * table) / Math.sqrt((table + table) * (table + table) * (table + table) * (table + table)); } console.log(phi([76, 9, 4, 1])); // → 0.068599434 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 n1 because the sums of rows or columns are not stored directly in our data structure. Jacques kept his journal for three months. The resulting data set is available in the coding sandbox for this chapter (https://eloquentjavascript.net/code#4), where it is stored in the JOURNAL binding and in a downloadable file. 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. function tableFor(event, journal) { 69 let table = [0, 0, 0, 0]; for (let i = 0; i < journal.length; i++) { let entry = journal[i], index = 0; if (entry.events.includes(event)) index += 1; if (entry.squirrel) index += 2; table[index] += 1; } return table; } console.log(tableFor("pizza", JOURNAL)); // → [76, 9, 4, 1] 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. 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 correlations. The only step remaining is to find a correlation for every type of event that was recorded and see whether anything stands out. Array loops In the tableFor function, there’s a loop like this: for (let i = 0; i < JOURNAL.length; i++) { let entry = JOURNAL[i]; // Do something with entry } 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. for (let entry of JOURNAL) { 70 console.log(`${entry.events.length} events.`); } 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 6. The final analysis 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. function journalEvents(journal) { let events = []; for (let entry of journal) { for (let event of entry.events) { if (!events.includes(event)) { events.push(event); } } } return events; } 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. Using that, we can see all the correlations. for (let event of journalEvents(JOURNAL)) { console.log(event + ":", phi(tableFor(event, JOURNAL))); } // → carrot: 0.0140970969 // → exercise: 0.0685994341 // → weekend: 0.1371988681 // → bread: -0.0757554019 // → pudding: -0.0648203724 71 // 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 some- what more often on weekends. Let’s filter the results to show only correlations greate

Use Quizgecko on...
Browser
Browser