Rating: 8.6/10.
Eloquent JavaScript, 3rd Edition: A Modern Introduction to Programming by Marijn Haverbeke
A book that teaches the JavaScript programming language: the first half is about the core parts of the language itself, introducing basic control structures then more advanced topics like modules, objects, and promises. The second half is about how JS interacts with the browser: topics like interacting with the DOM and canvas, making HTTP requests, and a bit of back-end programming with node.js. There are no frameworks involved; everything uses vanilla JS.
Though it starts with basic concepts such as variables and control flow, this book isn’t really aimed at beginners: some of the examples are quite complex, especially in the sections involving web pages. It uses vanilla JS throughout, while keeping a modular architecture, and essentially ends up rolling a small framework with utilities to manage the DOM, dispatch functions to manage global state. Overall I enjoyed it, since it provides a solid foundational understanding of the language.
Chapter 1. Basic types, arithmetic and logical operators.
Chapter 2. Variable assignment, expressions, and control flow structures.
Recommend using let
in all places instead of var
, the latter has unintuitive scoping rules (var
is visible in the whole scope whereas let
makes it only visible after it’s declared); const
makes the reference immutable but the value can still change (eg: if it’s a mutable object).
Chapter 3. Functions, declaring and using them, variable arguments, closures and scope, recursion. Functions are values, can be passed around, declared with const and let. The function declaration notation moves it to the top of the scope, similar to var.
Chapter 4. Lists and objects (which are basically dictionaries). Be aware that equality comparison checks for reference, there is no deep comparison. Can loop over array using syntax for(let v of arr)
.
Rest parameters look like max(...nums)
and puts the nums into a list. Destructuring or pattern matching lets you extract list or object into values like let {name} = {name: "x"}
.
Chapter 5. Can pass functions into others (higher-order functions), and declare inline functions using syntax like x => console.log(x)
; pass into functional utilities like map, filter, reduce. Strings are UTF-16 which means indexing and length are unreliable with emojis, but iterating works.
Chapter 6. Classes and objects are based on the prototype model: a constructor is a function, and methods are defined by modifying the function’s prototype. This is different from most OOP languages; ES6 introduced a more familiar syntax but it still uses prototypes underneath. The syntax also supports inheritance, polymorphism, and static methods.
Chapter 7. Example of OOP implementation of a robot that delivers parcels on a graph, the parcels are randomly distributed over the graph and the robot can either move randomly or using a shortest path algorithm.
Chapter 8. Strict mode is useful for avoiding common errors, like accidentally writing to a global variable. Types make your program more readable (though the book doesn’t go into TypeScript). Exceptions are useful for handling special control flow; you can selectively catch exceptions and clean up with a finally block.
Chapter 9. Regular expressions are useful for parsing things like dates, advanced features like backtracking (though this can sometimes lead to exploding complexity). There are groups and the option to replace multiple at a time using regex. Special options are available for handling Unicode characters properly.
Chapter 10. Modules are useful for isolating pieces of a program. Before ES6, people improvised a hacky way to simulate modules using functions to limit scope. CommonJS modules provide a require
function to load modules and cache them if they’re used multiple times. ES6 modules introduced true modules with the import
keyword, and now they sort of coexist with older CommonJS modules.
Chapter 11. Asynchronous programming is when the program runs a function on a separate thread and continues immediately, instead of waiting for it to complete before continuing. This is primarily done using callbacks, but callbacks are prone to errors. Instead, a promise represents a value that will exist in the future after calling a resolve function. Inside a promise, the code can either call resolve to report back a successful result or reject with an error object, which goes to the catch handler in the caller. The Promise.all
function waits for an array of promises to resolve before continuing.
An easier way to use promises is with async functions. This still uses promises under the hood but allows the code to read as if it was synchronous. A function marked as async returns a promise, and the await keyword resolves a promise. This has the advantage of handling exceptions correctly with the usual try-catch mechanism since, by default, promises run in a different event loop, so errors won’t propagate to its caller’s catch function.
Generators are functions that are marked with the asterisk symbol. They return an iterator, but this is cleaner to write than calling the next function and manually managing the iteration state.
Chapter 12: A project of building a toy programming language. First, the string is parsed into an object structure, then evaluates the structure into a value. Supports global vs function scope, and gives a view of how programming languages are implemented.
Chapter 13: the basics of HTTP and HTML markup.
Chapter 14: The Document Object Model (DOM) is a tree structure that represents web documents, and JavaScript can manipulate the DOM. Direct manipulation is uncommon nowadays since it’s so cumbersome, and frameworks are used instead, but this is how it works under the hood.
DOM elements contain links to their parent, children, and siblings. You can retrieve DOM elements using functions that fetch them by their ID or class name. Additionally, nodes can be created and then attached to the DOM.
Styling can be achieved with inline CSS or cascading styles. Simple animations can be created by altering the position of elements and calling the requestAnimationFrame
function to initiate the next animation phase.
Chapter 15. Handling events: Events are managed by listeners. You can register one event listener on an element like with onclick
, or add multiple listeners using addEventListener
, the latter allows you to unregister them later in JavaScript. The listener function is passed an event object, which contains properties like which button was pressed, and also allows you to prevent further propagation up the DOM tree and stop the default action.
Some common events are: key up and down, mouse clicks, mouse movements, touch screen interactions, elements gaining and losing focus, and page loading. For some events, they might trigger multiple times, so it’s important to not overload the process: if an event does heavy tasks, it can slow down the UI. For lengthy tasks, web workers are recommended, as they run on a separate thread. A common technique to handle multiple triggers is debouncing, where you set a timeout and clear that timeout, so that the event only triggers once after all rapid successive events.
Chapter 16. A project on creating a simple platformer game using HTML and JavaScript: The game loads level data from a string and displays game states using HTML tables and divs positioned absolutely based on their in-game locations, then there’s basic collision detection and key input handling.
Chapter 17. Beyond DOM elements, two other methods for graphics are SVG and canvas. SVG are vector graphics so they don’t lose quality when scaled up or down. Canvas offers commands for drawing that produce pixels, this is better for numerous small objects to produce pixels instead of vector graphics. Canvas provides various drawing capabilities, such as creating lines, filling shapes, drawing curves like bezier curves, rendering parts of circles, text, images, etc.
Transformations in canvas enable you to rotate, translate, and scale elements. These transformations are structured in a stack , where each transformation impacts all subsequent elements; you can save and restore the stack’s state to control these effects. This chapter upgrades the game from the previous chapter to use canvas graphics and sprites instead of colored rectangles.
Chapter 18. HTML pages may include forms that allow users to input information and send it to the server. This can be with a GET or a POST request. With a GET request, the information is sent in the query string, whereas with a POST , it’s in the form body; POST is recommended for anything that changes something on the server. The fetch function can be used to send GET and POST requests; this is a relatively new feature so it uses promises.
Security features: by default, the browser doesn’t allow scripts to make requests to other domains unless the server has set a flag to disable this security feature. HTTPS prevents others from impersonating or reading your communication.
Different form fields include text, radio buttons, checkboxes, select buttons, and file upload (which allows the script to read a file from the user’s computer). Individual input elements can have focus or be disabled. When the form is submitted, the information in the fields is encoded and submitted to the URL. You can disable this default behavior if you don’t want it to navigate away from the page.
Another feature is storing data on the browser: there’s a bit of storage available, sandboxed to the domain; this is called local storage. There’s also session storage which is cleared at the end of each session.
Chapter 19. This builds a pixel art editor using canvas and DOM manipulation. The state is stored as pixels and is immutable; when there’s an update, we replace the state with a new array. The PictureCanvas class is responsible for drawing the picture and dispatching events to different tool classes. Throughout the application, there’s a global dispatch function passed to each class that handles updating the global state. Several drawing tools change the state in different ways in response to mouse events, such as rectangle and flood fill.
It also supports saving the picture to download and loading it from a file, but there are several tricks needed to get this to work. The undo stack pushes states, checking if the difference is more than one second to avoid pushing too many states onto the stack. Overall, this project is intricate and showcases the complexity of building such an application using plain vanilla JavaScript (modern frameworks make this easier).
Chapter 20: Node.js allows you to run JS from the command line instead of the browser. By default it uses CommonJS modules; NPM is handy for installing packages, which are saved locally under the node_modules
folder. Examples modules for interacting with the file system, or the HTTP module that handles incoming requests and functions as a web server.
The web server listens to a port and can return a response immediately or establish a writable stream. This is useful for maintaining prolonged connections to the client, and has callbacks for when the server receives data or when the connection is closed.
Chapter 21: builds a skill-sharing website where visitors can make posts or comment on existing posts. The site is automatically updates with new comments without refreshing, using long polling to keep a connection to the server. REST endpoints are employed to fetch talks or add comments. On the server side, the router module attempts to resolve requests, and if unsuccessful, the file service returns the file’s contents as a response. The client sets a flag of whether to long-poll and for how long.
Chapter 22: Performance in JS, exemplified by a graph physics simulation that is computationally demanding and requires a few thousand iterations. First of all are algorithmic improvements, like avoiding updates to node positions when the difference is small. Profiling is then used to pinpoint slow-performing functions. The browser engine is good at compiling often-run functions to make them faster, but to help it out, it’s advisable to avoid certain dynamic object modifications, and to avoid creating lots of objects that require garbage collection.