2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/code/_stop_keys.js:
--------------------------------------------------------------------------------
1 | window.addEventListener("keydown", e => {
2 | if (/Arrow|Home|End|Page/.test(e.key)) e.preventDefault()
3 | })
4 |
--------------------------------------------------------------------------------
/code/animatevillage.js:
--------------------------------------------------------------------------------
1 | // test: no
2 |
3 | (function() {
4 | "use strict"
5 |
6 | let active = null
7 |
8 | const places = {
9 | "Alice's House": {x: 279, y: 100},
10 | "Bob's House": {x: 295, y: 203},
11 | "Cabin": {x: 372, y: 67},
12 | "Daria's House": {x: 183, y: 285},
13 | "Ernie's House": {x: 50, y: 283},
14 | "Farm": {x: 36, y: 118},
15 | "Grete's House": {x: 35, y: 187},
16 | "Marketplace": {x: 162, y: 110},
17 | "Post Office": {x: 205, y: 57},
18 | "Shop": {x: 137, y: 212},
19 | "Town Hall": {x: 202, y: 213}
20 | }
21 | const placeKeys = Object.keys(places)
22 |
23 | const speed = 2
24 |
25 | class Animation {
26 | constructor(worldState, robot, robotState) {
27 | this.worldState = worldState
28 | this.robot = robot
29 | this.robotState = robotState
30 | this.turn = 0
31 |
32 | let outer = (window.__sandbox ? window.__sandbox.output.div : document.body), doc = outer.ownerDocument
33 | this.node = outer.appendChild(doc.createElement("div"))
34 | this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px"
35 | this.map = this.node.appendChild(doc.createElement("img"))
36 | this.imgPath = "img/"
37 | if (/\/code($|\/)/.test(outer.ownerDocument.defaultView.location)) this.imgPath = "../" + this.imgPath
38 | console.log(outer.ownerDocument.defaultView.location.toString(), /\/code($|\/)/.test(outer.ownerDocument.defaultView.localation), this.imgPath)
39 | this.map.src = this.imgPath + "village2x.png"
40 | this.map.style.cssText = "vertical-align: -8px"
41 | this.robotElt = this.node.appendChild(doc.createElement("div"))
42 | this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;`
43 | let robotPic = this.robotElt.appendChild(doc.createElement("img"))
44 | robotPic.src = this.imgPath + "robot_moving2x.gif"
45 | this.parcels = []
46 |
47 | this.text = this.node.appendChild(doc.createElement("span"))
48 | this.button = this.node.appendChild(doc.createElement("button"))
49 | this.button.style.cssText = "color: white; background: #28b; border: none; border-radius: 2px; padding: 2px 5px; line-height: 1.1; font-family: sans-serif; font-size: 80%"
50 | this.button.textContent = "Stop"
51 |
52 | this.button.addEventListener("click", () => this.clicked())
53 | this.schedule()
54 |
55 | this.updateView()
56 | this.updateParcels()
57 |
58 | this.robotElt.addEventListener("transitionend", () => this.updateParcels())
59 | }
60 |
61 |
62 | updateView() {
63 | let pos = places[this.worldState.place]
64 | this.robotElt.style.top = (pos.y - 38) + "px"
65 | this.robotElt.style.left = (pos.x - 16) + "px"
66 |
67 | this.text.textContent = ` Turn ${this.turn} `
68 | }
69 |
70 | updateParcels() {
71 | while (this.parcels.length) this.parcels.pop().remove()
72 | let heights = {}
73 | for (let {place, address} of this.worldState.parcels) {
74 | let height = heights[place] || (heights[place] = 0)
75 | heights[place] += 14
76 | let node = document.createElement("div")
77 | let offset = placeKeys.indexOf(address) * 16
78 | node.style.cssText = `position: absolute; height: 16px; width: 16px; background-image: url(${this.imgPath}parcel2x.png); background-position: 0 -${offset}px`;
79 | if (place == this.worldState.place) {
80 | node.style.left = "25px"
81 | node.style.bottom = (20 + height) + "px"
82 | this.robotElt.appendChild(node)
83 | } else {
84 | let pos = places[place]
85 | node.style.left = (pos.x - 5) + "px"
86 | node.style.top = (pos.y - 10 - height) + "px"
87 | this.node.appendChild(node)
88 | }
89 | this.parcels.push(node)
90 | }
91 | }
92 |
93 | tick() {
94 | let {direction, memory} = this.robot(this.worldState, this.robotState)
95 | this.worldState = this.worldState.move(direction)
96 | this.robotState = memory
97 | this.turn++
98 | this.updateView()
99 | if (this.worldState.parcels.length == 0) {
100 | this.button.remove()
101 | this.text.textContent = ` Finished after ${this.turn} turns`
102 | this.robotElt.firstChild.src = this.imgPath + "robot_idle2x.png"
103 | } else {
104 | this.schedule()
105 | }
106 | }
107 |
108 | schedule() {
109 | this.timeout = setTimeout(() => this.tick(), 1000 / speed)
110 | }
111 |
112 | clicked() {
113 | if (this.timeout == null) {
114 | this.schedule()
115 | this.button.textContent = "Stop"
116 | this.robotElt.firstChild.src = this.imgPath + "robot_moving2x.gif"
117 | } else {
118 | clearTimeout(this.timeout)
119 | this.timeout = null
120 | this.button.textContent = "Start"
121 | this.robotElt.firstChild.src = this.imgPath + "robot_idle2x.png"
122 | }
123 | }
124 | }
125 |
126 | window.runRobotAnimation = function(worldState, robot, robotState) {
127 | if (active && active.timeout != null)
128 | clearTimeout(active.timeout)
129 | active = new Animation(worldState, robot, robotState)
130 | }
131 | })()
132 |
--------------------------------------------------------------------------------
/code/chapter/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/code/chapter/.keep
--------------------------------------------------------------------------------
/code/chapter/22_fast.js:
--------------------------------------------------------------------------------
1 | var Graph = class Graph {
2 | #nodes = [];
3 |
4 | get size() {
5 | return this.#nodes.length;
6 | }
7 |
8 | addNode() {
9 | let id = this.#nodes.length;
10 | this.#nodes.push(new Set());
11 | return id;
12 | }
13 |
14 | addEdge(nodeA, nodeB) {
15 | this.#nodes[nodeA].add(nodeB);
16 | this.#nodes[nodeB].add(nodeA);
17 | }
18 |
19 | neighbors(node) {
20 | return this.#nodes[node];
21 | }
22 | }
23 |
24 | function randomLayout(graph) {
25 | let layout = [];
26 | for (let i = 0; i < graph.size; i++) {
27 | layout.push(new Vec(Math.random() * 1000,
28 | Math.random() * 1000));
29 | }
30 | return layout;
31 | }
32 |
33 | function gridGraph(size) {
34 | let grid = new Graph();
35 | for (let y = 0; y < size; y++) {
36 | for (let x = 0; x < size; x++) {
37 | let id = grid.addNode();
38 | if (x > 0) grid.addEdge(id, id - 1);
39 | if (y > 0) grid.addEdge(id, id - size);
40 | }
41 | }
42 | return grid;
43 | }
44 |
45 | var springLength = 20;
46 | var springStrength = 0.1;
47 | var repulsionStrength = 1500;
48 |
49 | function forceSize(distance, connected) {
50 | let repulse = -repulsionStrength / (distance * distance);
51 | let spring = 0;
52 | if (connected) {
53 | spring = (distance - springLength) * springStrength;
54 | }
55 | return spring + repulse;
56 | }
57 |
58 | function forceDirected_simple(layout, graph) {
59 | for (let a = 0; a < graph.size; a++) {
60 | for (let b = 0; b < graph.size; b++) {
61 | if (a == b) continue;
62 | let apart = layout[b].minus(layout[a]);
63 | let distance = Math.max(1, apart.length);
64 | let connected = graph.neighbors(a).has(b);
65 | let size = forceSize(distance, connected);
66 | let force = apart.times(1 / distance).times(size);
67 | layout[a] = layout[a].plus(force);
68 | }
69 | }
70 | }
71 |
72 | function pause() {
73 | return new Promise(done => setTimeout(done, 0))
74 | }
75 |
76 | async function runLayout(implementation, graph) {
77 | let time = 0, iterations = 0;
78 | let layout = randomLayout(graph);
79 | while (time < 3000) {
80 | let start = Date.now();
81 | for (let i = 0; i < 100; i++) {
82 | implementation(layout, graph);
83 | iterations++;
84 | }
85 | time += Date.now() - start;
86 | drawGraph(graph, layout);
87 | await pause();
88 | }
89 | let perSecond = Math.round(iterations / (time / 1000));
90 | console.log(`${perSecond} iterations per second`);
91 | }
92 |
93 | function forceDirected_noRepeat(layout, graph) {
94 | for (let a = 0; a < graph.size; a++) {
95 | for (let b = a + 1; b < graph.size; b++) {
96 | let apart = layout[b].minus(layout[a]);
97 | let distance = Math.max(1, apart.length);
98 | let connected = graph.neighbors(a).has(b);
99 | let size = forceSize(distance, connected);
100 | let force = apart.times(1 / distance).times(size);
101 | layout[a] = layout[a].plus(force);
102 | layout[b] = layout[b].minus(force);
103 | }
104 | }
105 | }
106 |
107 | var skipDistance = 175;
108 |
109 | function forceDirected_skip(layout, graph) {
110 | for (let a = 0; a < graph.size; a++) {
111 | for (let b = a + 1; b < graph.size; b++) {
112 | let apart = layout[b].minus(layout[a]);
113 | let distance = Math.max(1, apart.length);
114 | let connected = graph.neighbors(a).has(b);
115 | if (distance > skipDistance && !connected) continue;
116 | let size = forceSize(distance, connected);
117 | let force = apart.times(1 / distance).times(size);
118 | layout[a] = layout[a].plus(force);
119 | layout[b] = layout[b].minus(force);
120 | }
121 | }
122 | }
123 |
124 | function forceDirected_noVector(layout, graph) {
125 | for (let a = 0; a < graph.size; a++) {
126 | let posA = layout[a];
127 | for (let b = a + 1; b < graph.size; b++) {
128 | let posB = layout[b];
129 | let apartX = posB.x - posA.x
130 | let apartY = posB.y - posA.y;
131 | let distance = Math.sqrt(apartX * apartX +
132 | apartY * apartY);
133 | let connected = graph.neighbors(a).has(b);
134 | if (distance > skipDistance && !connected) continue;
135 | let size = forceSize(distance, connected);
136 | let forceX = (apartX / distance) * size;
137 | let forceY = (apartY / distance) * size;
138 | posA.x += forceX;
139 | posA.y += forceY;
140 | posB.x -= forceX;
141 | posB.y -= forceY;
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/code/draw_layout.js:
--------------------------------------------------------------------------------
1 | // The familiar Vec type.
2 |
3 | class Vec {
4 | constructor(x, y) {
5 | this.x = x; this.y = y;
6 | }
7 | plus(other) {
8 | return new Vec(this.x + other.x, this.y + other.y);
9 | }
10 | minus(other) {
11 | return new Vec(this.x - other.x, this.y - other.y);
12 | }
13 | times(factor) {
14 | return new Vec(this.x * factor, this.y * factor);
15 | }
16 | get length() {
17 | return Math.sqrt(this.x * this.x + this.y * this.y);
18 | }
19 | }
20 |
21 | // Since we will want to inspect the layouts our code produces, let's
22 | // first write code to draw a graph onto a canvas. Since we don't know
23 | // in advance how big the graph is, the `Scale` object computes a
24 | // scale and offset so that all nodes fit onto the given canvas.
25 |
26 | const nodeSize = 6;
27 |
28 | function drawGraph(graph, layout) {
29 | let parent = (window.__sandbox ? window.__sandbox.output.div : document.body);
30 | let canvas = parent.querySelector("canvas");
31 | if (!canvas) {
32 | canvas = parent.appendChild(document.createElement("canvas"));
33 | canvas.width = canvas.height = 400;
34 | }
35 | let cx = canvas.getContext("2d");
36 |
37 | cx.clearRect(0, 0, canvas.width, canvas.height);
38 | let scale = new Scale(layout, canvas.width, canvas.height);
39 |
40 | // Draw the edges.
41 | cx.strokeStyle = "orange";
42 | cx.lineWidth = 3;
43 | for (let i = 0; i < layout.length; i++) {
44 | let conn = graph.neighbors(i);
45 | for (let target of conn) {
46 | if (conn <= i) continue;
47 | cx.beginPath();
48 | cx.moveTo(scale.x(layout[i].x), scale.y(layout[i].y));
49 | cx.lineTo(scale.x(layout[target].x), scale.y(layout[target].y));
50 | cx.stroke();
51 | }
52 | }
53 |
54 | // Draw the nodes.
55 | cx.fillStyle = "purple";
56 | for (let pos of layout) {
57 | cx.beginPath();
58 | cx.arc(scale.x(pos.x), scale.y(pos.y), nodeSize, 0, 7);
59 | cx.fill();
60 | }
61 | }
62 |
63 | // The function starts by drawing the edges, so that they appear
64 | // behind the nodes. Since the nodes on _both_ side of an edge refer
65 | // to each other, and we don't want to draw every edge twice, edges
66 | // are only drawn then the target comes _after_ the current node in
67 | // the `graph` array.
68 |
69 | // When the edges have been drawn, the nodes are drawn on top of them
70 | // as purple discs. Remember that the last argument to `arc` gives the
71 | // rotation, and we have to pass something bigger than 2π to get a
72 | // full circle.
73 |
74 | // Finding a scale at which to draw the graph is done by finding the
75 | // top left and bottom right corners of the area taken up by the
76 | // nodes. The offset at which nodes are drawn is based on the top left
77 | // corner, and the scale is based on the size of the canvas divided by
78 | // the distance between those corners. The function reserves space
79 | // along the sides of the canvas based on the `nodeSize` variable, so
80 | // that the circles drawn around nodes’ center points don't get cut off.
81 |
82 | class Scale {
83 | constructor(layout, width, height) {
84 | let xs = layout.map(node => node.x);
85 | let ys = layout.map(node => node.y);
86 | let minX = Math.min(...xs);
87 | let minY = Math.min(...ys);
88 | let maxX = Math.max(...xs);
89 | let maxY = Math.max(...ys);
90 |
91 | this.offsetX = minX; this.offsetY = minY;
92 | this.scaleX = (width - 2 * nodeSize) / (maxX - minX);
93 | this.scaleY = (height - 2 * nodeSize) / (maxY - minY);
94 | }
95 |
96 | // The `x` and `y` methods convert from graph coordinates into
97 | // canvas coordinates.
98 | x(x) {
99 | return this.scaleX * (x - this.offsetX) + nodeSize;
100 | }
101 | y(y) {
102 | return this.scaleY * (y - this.offsetY) + nodeSize;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/code/hello.js:
--------------------------------------------------------------------------------
1 | alert("hello!");
2 |
--------------------------------------------------------------------------------
/code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Eloquent JavaScript :: Code Sandbox
6 |
7 |
8 |
9 |
12 |
13 |
16 |
17 |
18 |
19 | You can use this page to download source code and solutions to
22 | exercises for the book Eloquent JavaScript, and to directly run code
23 | in the context of chapters from that book, either to solve exercises
24 | to simply play around.
25 |
26 |
27 | Chapter:
28 |
29 | run code
30 |
31 |
32 |
36 |
37 |
38 | To run this chapter's code locally, use these files:
39 |
40 |
41 |
42 |
43 | These files contain this chapter’s project code:
44 |
45 |
46 |
47 | If you've solved the exercise and want to compare your code with
48 | mine, or you really tried, but can't get your code to work,
49 | you can look at the
50 | solution (or download it ).
51 |
52 |
53 | The base environment for this chapter (if any) is available in the
54 | sandbox above, allowing you to run the chapter's examples by
55 | simply pasting them into the editor.
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/code/intro.js:
--------------------------------------------------------------------------------
1 | function range(start, end, step) {
2 | if (step == null) step = 1;
3 | var array = [];
4 |
5 | if (step > 0) {
6 | for (var i = start; i <= end; i += step)
7 | array.push(i);
8 | } else {
9 | for (var i = start; i >= end; i += step)
10 | array.push(i);
11 | }
12 | return array;
13 | }
14 |
15 | function sum(array) {
16 | var total = 0;
17 | for (var i = 0; i < array.length; i++)
18 | total += array[i];
19 | return total;
20 | }
21 |
22 | function factorial(n) {
23 | if (n == 0) {
24 | return 1;
25 | } else {
26 | return factorial(n - 1) * n;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/code/load.js:
--------------------------------------------------------------------------------
1 | // Since the code for most chapter in Eloquent JavaScript isn't
2 | // written with node's module system in mind, this kludge is used to
3 | // load dependency files into the global namespace, so that the
4 | // examples can run on node.
5 |
6 | module.exports = function(...args) {
7 | for (let arg of args)
8 | (1,eval)(require("fs").readFileSync(__dirname + "/../" + arg, "utf8"))
9 | }
10 |
--------------------------------------------------------------------------------
/code/skillsharing/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/code/skillsharing/.keep
--------------------------------------------------------------------------------
/code/skillsharing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ejs-skillsharing",
3 | "version": "1.0.0",
4 | "main": "skillsharing_server.js",
5 | "description": "Skill-sharing website example from Eloquent JavaScript",
6 | "dependencies": {
7 | "serve-static": "^1.15.0"
8 | },
9 | "license": "MIT",
10 | "bugs": "https://github.com/marijnh/Eloquent-JavaScript/issues",
11 | "homepage": "https://eloquentjavascript.net/21_skillsharing.html",
12 | "maintainers": [
13 | {
14 | "name": "Marijn Haverbeke",
15 | "email": "marijn@haverbeke.berlin",
16 | "web": "https://marijnhaverbeke.nl/"
17 | }
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/marijnh/Eloquent-JavaScript.git"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/code/skillsharing/public/skillsharing.css:
--------------------------------------------------------------------------------
1 | .talk { margin: 40px 0; }
2 |
3 | .comment { font-style: italic; margin: 0; }
4 | .comment strong { font-style: normal; }
5 |
6 | .talk h2 { font-size: 130%; margin-bottom: 0; }
7 | .talk h2 button { vertical-align: bottom; }
8 |
9 | h1, h3 { margin-bottom: 0.33em; }
10 |
11 | label input { display: block; width: 30em; }
12 |
--------------------------------------------------------------------------------
/code/solutions/02_1_looping_a_triangle.js:
--------------------------------------------------------------------------------
1 | for (let line = "#"; line.length < 8; line += "#")
2 | console.log(line);
3 |
--------------------------------------------------------------------------------
/code/solutions/02_2_fizzbuzz.js:
--------------------------------------------------------------------------------
1 | for (let n = 1; n <= 100; n++) {
2 | let output = "";
3 | if (n % 3 == 0) output += "Fizz";
4 | if (n % 5 == 0) output += "Buzz";
5 | console.log(output || n);
6 | }
7 |
--------------------------------------------------------------------------------
/code/solutions/02_3_chessboard.js:
--------------------------------------------------------------------------------
1 | let size = 8;
2 |
3 | let board = "";
4 |
5 | for (let y = 0; y < size; y++) {
6 | for (let x = 0; x < size; x++) {
7 | if ((x + y) % 2 == 0) {
8 | board += " ";
9 | } else {
10 | board += "#";
11 | }
12 | }
13 | board += "\n";
14 | }
15 |
16 | console.log(board);
17 |
--------------------------------------------------------------------------------
/code/solutions/03_1_minimum.js:
--------------------------------------------------------------------------------
1 | function min(a, b) {
2 | if (a < b) return a;
3 | else return b;
4 | }
5 |
6 | console.log(min(0, 10));
7 | // → 0
8 | console.log(min(0, -10));
9 | // → -10
10 |
--------------------------------------------------------------------------------
/code/solutions/03_2_recursion.js:
--------------------------------------------------------------------------------
1 | function isEven(n) {
2 | if (n == 0) return true;
3 | else if (n == 1) return false;
4 | else if (n < 0) return isEven(-n);
5 | else return isEven(n - 2);
6 | }
7 |
8 | console.log(isEven(50));
9 | // → true
10 | console.log(isEven(75));
11 | // → false
12 | console.log(isEven(-1));
13 | // → false
14 |
--------------------------------------------------------------------------------
/code/solutions/03_3_bean_counting.js:
--------------------------------------------------------------------------------
1 | function countChar(string, ch) {
2 | let counted = 0;
3 | for (let i = 0; i < string.length; i++) {
4 | if (string[i] == ch) {
5 | counted += 1;
6 | }
7 | }
8 | return counted;
9 | }
10 |
11 | function countBs(string) {
12 | return countChar(string, "B");
13 | }
14 |
15 | console.log(countBs("BBC"));
16 | // → 2
17 | console.log(countChar("kakkerlak", "k"));
18 | // → 4
19 |
--------------------------------------------------------------------------------
/code/solutions/04_1_the_sum_of_a_range.js:
--------------------------------------------------------------------------------
1 | function range(start, end, step = start < end ? 1 : -1) {
2 | let array = [];
3 |
4 | if (step > 0) {
5 | for (let i = start; i <= end; i += step) array.push(i);
6 | } else {
7 | for (let i = start; i >= end; i += step) array.push(i);
8 | }
9 | return array;
10 | }
11 |
12 | function sum(array) {
13 | let total = 0;
14 | for (let value of array) {
15 | total += value;
16 | }
17 | return total;
18 | }
19 |
20 | console.log(range(1, 10))
21 | // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
22 | console.log(range(5, 2, -1));
23 | // → [5, 4, 3, 2]
24 | console.log(sum(range(1, 10)));
25 | // → 55
26 |
--------------------------------------------------------------------------------
/code/solutions/04_2_reversing_an_array.js:
--------------------------------------------------------------------------------
1 | function reverseArray(array) {
2 | let output = [];
3 | for (let i = array.length - 1; i >= 0; i--) {
4 | output.push(array[i]);
5 | }
6 | return output;
7 | }
8 |
9 | function reverseArrayInPlace(array) {
10 | for (let i = 0; i < Math.floor(array.length / 2); i++) {
11 | let old = array[i];
12 | array[i] = array[array.length - 1 - i];
13 | array[array.length - 1 - i] = old;
14 | }
15 | return array;
16 | }
17 |
18 | console.log(reverseArray(["A", "B", "C"]));
19 | // → ["C", "B", "A"];
20 | let arrayValue = [1, 2, 3, 4, 5];
21 | reverseArrayInPlace(arrayValue);
22 | console.log(arrayValue);
23 | // → [5, 4, 3, 2, 1]
24 |
--------------------------------------------------------------------------------
/code/solutions/04_3_a_list.js:
--------------------------------------------------------------------------------
1 | function arrayToList(array) {
2 | let list = null;
3 | for (let i = array.length - 1; i >= 0; i--) {
4 | list = {value: array[i], rest: list};
5 | }
6 | return list;
7 | }
8 |
9 | function listToArray(list) {
10 | let array = [];
11 | for (let node = list; node; node = node.rest) {
12 | array.push(node.value);
13 | }
14 | return array;
15 | }
16 |
17 | function prepend(value, list) {
18 | return {value, rest: list};
19 | }
20 |
21 | function nth(list, n) {
22 | if (!list) return undefined;
23 | else if (n == 0) return list.value;
24 | else return nth(list.rest, n - 1);
25 | }
26 |
27 | console.log(arrayToList([10, 20]));
28 | // → {value: 10, rest: {value: 20, rest: null}}
29 | console.log(listToArray(arrayToList([10, 20, 30])));
30 | // → [10, 20, 30]
31 | console.log(prepend(10, prepend(20, null)));
32 | // → {value: 10, rest: {value: 20, rest: null}}
33 | console.log(nth(arrayToList([10, 20, 30]), 1));
34 | // → 20
35 |
--------------------------------------------------------------------------------
/code/solutions/04_4_deep_comparison.js:
--------------------------------------------------------------------------------
1 | function deepEqual(a, b) {
2 | if (a === b) return true;
3 |
4 | if (a == null || typeof a != "object" ||
5 | b == null || typeof b != "object") return false;
6 |
7 | let keysA = Object.keys(a), keysB = Object.keys(b);
8 |
9 | if (keysA.length != keysB.length) return false;
10 |
11 | for (let key of keysA) {
12 | if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
13 | }
14 |
15 | return true;
16 | }
17 |
18 | let obj = {here: {is: "an"}, object: 2};
19 | console.log(deepEqual(obj, obj));
20 | // → true
21 | console.log(deepEqual(obj, {here: 1, object: 2}));
22 | // → false
23 | console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
24 | // → true
25 |
--------------------------------------------------------------------------------
/code/solutions/05_1_flattening.js:
--------------------------------------------------------------------------------
1 | let arrays = [[1, 2, 3], [4, 5], [6]];
2 |
3 | console.log(arrays.reduce((flat, current) => flat.concat(current), []));
4 | // → [1, 2, 3, 4, 5, 6]
5 |
--------------------------------------------------------------------------------
/code/solutions/05_2_your_own_loop.js:
--------------------------------------------------------------------------------
1 | function loop(start, test, update, body) {
2 | for (let value = start; test(value); value = update(value)) {
3 | body(value);
4 | }
5 | }
6 |
7 | loop(3, n => n > 0, n => n - 1, console.log);
8 | // → 3
9 | // → 2
10 | // → 1
11 |
--------------------------------------------------------------------------------
/code/solutions/05_3_everything.js:
--------------------------------------------------------------------------------
1 | function every(array, predicate) {
2 | for (let element of array) {
3 | if (!predicate(element)) return false;
4 | }
5 | return true;
6 | }
7 |
8 | function every2(array, predicate) {
9 | return !array.some(element => !predicate(element));
10 | }
11 |
12 | console.log(every([1, 3, 5], n => n < 10));
13 | // → true
14 | console.log(every([2, 4, 16], n => n < 10));
15 | // → false
16 | console.log(every([], n => n < 10));
17 | // → true
18 |
--------------------------------------------------------------------------------
/code/solutions/05_4_dominant_writing_direction.js:
--------------------------------------------------------------------------------
1 | function dominantDirection(text) {
2 | let counted = countBy(text, char => {
3 | let script = characterScript(char.codePointAt(0));
4 | return script ? script.direction : "none";
5 | }).filter(({name}) => name != "none");
6 |
7 | if (counted.length == 0) return "ltr";
8 |
9 | return counted.reduce((a, b) => a.count > b.count ? a : b).name;
10 | }
11 |
12 | console.log(dominantDirection("Hello!"));
13 | // → ltr
14 | console.log(dominantDirection("Hey, مساء الخير"));
15 | // → rtl
16 |
--------------------------------------------------------------------------------
/code/solutions/06_1_a_vector_type.js:
--------------------------------------------------------------------------------
1 | class Vec {
2 | constructor(x, y) {
3 | this.x = x;
4 | this.y = y;
5 | }
6 |
7 | plus(other) {
8 | return new Vec(this.x + other.x, this.y + other.y);
9 | }
10 |
11 | minus(other) {
12 | return new Vec(this.x - other.x, this.y - other.y);
13 | }
14 |
15 | get length() {
16 | return Math.sqrt(this.x * this.x + this.y * this.y);
17 | }
18 | }
19 |
20 | console.log(new Vec(1, 2).plus(new Vec(2, 3)));
21 | // → Vec{x: 3, y: 5}
22 | console.log(new Vec(1, 2).minus(new Vec(2, 3)));
23 | // → Vec{x: -1, y: -1}
24 | console.log(new Vec(3, 4).length);
25 | // → 5
26 |
--------------------------------------------------------------------------------
/code/solutions/06_2_groups.js:
--------------------------------------------------------------------------------
1 | class Group {
2 | #members = [];
3 |
4 | add(value) {
5 | if (!this.has(value)) {
6 | this.#members.push(value);
7 | }
8 | }
9 |
10 | delete(value) {
11 | this.#members = this.#members.filter(v => v !== value);
12 | }
13 |
14 | has(value) {
15 | return this.#members.includes(value);
16 | }
17 |
18 | static from(collection) {
19 | let group = new Group;
20 | for (let value of collection) {
21 | group.add(value);
22 | }
23 | return group;
24 | }
25 | }
26 |
27 | let group = Group.from([10, 20]);
28 | console.log(group.has(10));
29 | // → true
30 | console.log(group.has(30));
31 | // → false
32 | group.add(10);
33 | group.delete(10);
34 | console.log(group.has(10));
35 | // → false
36 |
--------------------------------------------------------------------------------
/code/solutions/06_3_iterable_groups.js:
--------------------------------------------------------------------------------
1 | class Group {
2 | #members = [];
3 |
4 | add(value) {
5 | if (!this.has(value)) {
6 | this.#members.push(value);
7 | }
8 | }
9 |
10 | delete(value) {
11 | this.#members = this.#members.filter(v => v !== value);
12 | }
13 |
14 | has(value) {
15 | return this.#members.includes(value);
16 | }
17 |
18 | static from(collection) {
19 | let group = new Group;
20 | for (let value of collection) {
21 | group.add(value);
22 | }
23 | return group;
24 | }
25 |
26 | [Symbol.iterator]() {
27 | return new GroupIterator(this.#members);
28 | }
29 | }
30 |
31 | class GroupIterator {
32 | #members;
33 | #position;
34 |
35 | constructor(members) {
36 | this.#members = members;
37 | this.#position = 0;
38 | }
39 |
40 | next() {
41 | if (this.#position >= this.#members.length) {
42 | return {done: true};
43 | } else {
44 | let result = {value: this.#members[this.#position],
45 | done: false};
46 | this.#position++;
47 | return result;
48 | }
49 | }
50 | }
51 |
52 | for (let value of Group.from(["a", "b", "c"])) {
53 | console.log(value);
54 | }
55 | // → a
56 | // → b
57 | // → c
58 |
--------------------------------------------------------------------------------
/code/solutions/06_4_borrowing_a_method.js:
--------------------------------------------------------------------------------
1 | let map = {one: true, two: true, hasOwnProperty: true};
2 |
3 | console.log(Object.prototype.hasOwnProperty.call(map, "one"));
4 | // → true
5 |
--------------------------------------------------------------------------------
/code/solutions/07_1_measuring_a_robot.js:
--------------------------------------------------------------------------------
1 | function countSteps(state, robot, memory) {
2 | for (let steps = 0;; steps++) {
3 | if (state.parcels.length == 0) return steps;
4 | let action = robot(state, memory);
5 | state = state.move(action.direction);
6 | memory = action.memory;
7 | }
8 | }
9 |
10 | function compareRobots(robot1, memory1, robot2, memory2) {
11 | let total1 = 0, total2 = 0;
12 | for (let i = 0; i < 100; i++) {
13 | let state = VillageState.random();
14 | total1 += countSteps(state, robot1, memory1);
15 | total2 += countSteps(state, robot2, memory2);
16 | }
17 | console.log(`Robot 1 needed ${total1 / 100} steps per task`)
18 | console.log(`Robot 2 needed ${total2 / 100}`)
19 | }
20 |
21 | compareRobots(routeRobot, [], goalOrientedRobot, []);
22 |
--------------------------------------------------------------------------------
/code/solutions/07_2_robot_efficiency.js:
--------------------------------------------------------------------------------
1 | function lazyRobot({place, parcels}, route) {
2 | if (route.length == 0) {
3 | // Describe a route for every parcel
4 | let routes = parcels.map(parcel => {
5 | if (parcel.place != place) {
6 | return {route: findRoute(roadGraph, place, parcel.place),
7 | pickUp: true};
8 | } else {
9 | return {route: findRoute(roadGraph, place, parcel.address),
10 | pickUp: false};
11 | }
12 | });
13 |
14 | // This determines the precedence a route gets when choosing.
15 | // Route length counts negatively, routes that pick up a package
16 | // get a small bonus.
17 | function score({route, pickUp}) {
18 | return (pickUp ? 0.5 : 0) - route.length;
19 | }
20 | route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route;
21 | }
22 |
23 | return {direction: route[0], memory: route.slice(1)};
24 | }
25 |
26 | runRobotAnimation(VillageState.random(), lazyRobot, []);
27 |
--------------------------------------------------------------------------------
/code/solutions/07_3_persistent_group.js:
--------------------------------------------------------------------------------
1 | class PGroup {
2 | #members;
3 | constructor(members) {
4 | this.#members = members;
5 | }
6 |
7 | add(value) {
8 | if (this.has(value)) return this;
9 | return new PGroup(this.#members.concat([value]));
10 | }
11 |
12 | delete(value) {
13 | if (!this.has(value)) return this;
14 | return new PGroup(this.#members.filter(m => m !== value));
15 | }
16 |
17 | has(value) {
18 | return this.#members.includes(value);
19 | }
20 |
21 | static empty = new PGroup([]);
22 | }
23 |
24 | let a = PGroup.empty.add("a");
25 | let ab = a.add("b");
26 | let b = ab.delete("a");
27 |
28 | console.log(b.has("b"));
29 | // → true
30 | console.log(a.has("b"));
31 | // → false
32 | console.log(b.has("a"));
33 | // → false
34 |
--------------------------------------------------------------------------------
/code/solutions/08_1_retry.js:
--------------------------------------------------------------------------------
1 | class MultiplicatorUnitFailure extends Error {}
2 |
3 | function primitiveMultiply(a, b) {
4 | if (Math.random() < 0.2) {
5 | return a * b;
6 | } else {
7 | throw new MultiplicatorUnitFailure("Klunk");
8 | }
9 | }
10 |
11 | function reliableMultiply(a, b) {
12 | for (;;) {
13 | try {
14 | return primitiveMultiply(a, b);
15 | } catch (e) {
16 | if (!(e instanceof MultiplicatorUnitFailure))
17 | throw e;
18 | }
19 | }
20 | }
21 |
22 | console.log(reliableMultiply(8, 8));
23 | // → 64
24 |
--------------------------------------------------------------------------------
/code/solutions/08_2_the_locked_box.js:
--------------------------------------------------------------------------------
1 | const box = new class {
2 | locked = true;
3 | #content = [];
4 |
5 | unlock() { this.locked = false; }
6 | lock() { this.locked = true; }
7 | get content() {
8 | if (this.locked) throw new Error("Locked!");
9 | return this.#content;
10 | }
11 | };
12 |
13 | function withBoxUnlocked(body) {
14 | let locked = box.locked;
15 | if (locked) box.unlock();
16 | try {
17 | return body();
18 | } finally {
19 | if (locked) box.lock();
20 | }
21 | }
22 |
23 | withBoxUnlocked(() => {
24 | box.content.push("gold piece");
25 | });
26 |
27 | try {
28 | withBoxUnlocked(() => {
29 | throw new Error("Pirates on the horizon! Abort!");
30 | });
31 | } catch (e) {
32 | console.log("Error raised:", e);
33 | }
34 |
35 | console.log(box.locked);
36 | // → true
37 |
--------------------------------------------------------------------------------
/code/solutions/09_1_regexp_golf.js:
--------------------------------------------------------------------------------
1 | // Fill in the regular expressions
2 |
3 | verify(/ca[rt]/,
4 | ["my car", "bad cats"],
5 | ["camper", "high art"]);
6 |
7 | verify(/pr?op/,
8 | ["pop culture", "mad props"],
9 | ["plop", "prrrop"]);
10 |
11 | verify(/ferr(et|y|ari)/,
12 | ["ferret", "ferry", "ferrari"],
13 | ["ferrum", "transfer A"]);
14 |
15 | verify(/ious($|\P{L})/u,
16 | ["how delicious", "spacious room"],
17 | ["ruinous", "consciousness"]);
18 |
19 | verify(/\s[.,:;]/,
20 | ["bad punctuation ."],
21 | ["escape the dot"]);
22 |
23 | verify(/\p{L}{7}/u,
24 | ["Siebentausenddreihundertzweiundzwanzig"],
25 | ["no", "three small words"]);
26 |
27 | verify(/(^|\P{L})[^\P{L}e]+($|\P{L})/ui,
28 | ["red platypus", "wobbling nest"],
29 | ["earth bed", "bedrøvet abe", "BEET"]);
30 |
31 |
32 | function verify(regexp, yes, no) {
33 | // Ignore unfinished exercises
34 | if (regexp.source == "...") return;
35 | for (let str of yes) if (!regexp.test(str)) {
36 | console.log(`Failure to match '${str}'`);
37 | }
38 | for (let str of no) if (regexp.test(str)) {
39 | console.log(`Unexpected match for '${str}'`);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/code/solutions/09_2_quoting_style.js:
--------------------------------------------------------------------------------
1 | let text = "'I'm the cook,' he said, 'it's my job.'";
2 |
3 | console.log(text.replace(/(^|\P{L})'|'(\P{L}|$)/gu, '$1"$2'));
4 | // → "I'm the cook," he said, "it's my job."
5 |
6 |
--------------------------------------------------------------------------------
/code/solutions/09_3_numbers_again.js:
--------------------------------------------------------------------------------
1 | // Fill in this regular expression.
2 | let number = /^[+\-]?(\d+(\.\d*)?|\.\d+)([eE][+\-]?\d+)?$/;
3 |
4 | // Tests:
5 | for (let str of ["1", "-1", "+15", "1.55", ".5", "5.",
6 | "1.3e2", "1E-4", "1e+12"]) {
7 | if (!number.test(str)) {
8 | console.log(`Failed to match '${str}'`);
9 | }
10 | }
11 | for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5",
12 | ".5.", "1f5", "."]) {
13 | if (number.test(str)) {
14 | console.log(`Incorrectly accepted '${str}'`);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/code/solutions/10_2_roads_module.js:
--------------------------------------------------------------------------------
1 | import {buildGraph} from "./graph";
2 |
3 | const roads = [
4 | "Alice's House-Bob's House", "Alice's House-Cabin",
5 | "Alice's House-Post Office", "Bob's House-Town Hall",
6 | "Daria's House-Ernie's House", "Daria's House-Town Hall",
7 | "Ernie's House-Grete's House", "Grete's House-Farm",
8 | "Grete's House-Shop", "Marketplace-Farm",
9 | "Marketplace-Post Office", "Marketplace-Shop",
10 | "Marketplace-Town Hall", "Shop-Town Hall"
11 | ];
12 |
13 | export const roadGraph = buildGraph(roads.map(r => r.split("-")));
14 |
--------------------------------------------------------------------------------
/code/solutions/11_1_quiet_times.js:
--------------------------------------------------------------------------------
1 | async function activityTable(day) {
2 | let table = [];
3 | for (let i = 0; i < 24; i++) table[i] = 0;
4 |
5 | let logFileList = await textFile("camera_logs.txt");
6 | for (let filename of logFileList.split("\n")) {
7 | let log = await textFile(filename);
8 | for (let timestamp of log.split("\n")) {
9 | let date = new Date(Number(timestamp));
10 | if (date.getDay() == day) {
11 | table[date.getHours()]++;
12 | }
13 | }
14 | }
15 |
16 | return table;
17 | }
18 |
19 | activityTable(1)
20 | .then(table => console.log(activityGraph(table)));
21 |
--------------------------------------------------------------------------------
/code/solutions/11_1_tracking_the_scalpel.js:
--------------------------------------------------------------------------------
1 | async function locateScalpel(nest) {
2 | let current = nest.name;
3 | for (;;) {
4 | let next = await anyStorage(nest, current, "scalpel");
5 | if (next == current) return current;
6 | current = next;
7 | }
8 | }
9 |
10 | function locateScalpel2(nest) {
11 | function loop(current) {
12 | return anyStorage(nest, current, "scalpel").then(next => {
13 | if (next == current) return current;
14 | else return loop(next);
15 | });
16 | }
17 | return loop(nest.name);
18 | }
19 |
20 | locateScalpel(bigOak).then(console.log);
21 | // → Butcher's Shop
22 | locateScalpel2(bigOak).then(console.log);
23 | // → Butcher's Shop
24 |
--------------------------------------------------------------------------------
/code/solutions/11_2_real_promises.js:
--------------------------------------------------------------------------------
1 | function activityTable(day) {
2 | let table = [];
3 | for (let i = 0; i < 24; i++) table[i] = 0;
4 |
5 | return textFile("camera_logs.txt").then(files => {
6 | return Promise.all(files.split("\n").map(name => {
7 | return textFile(name).then(log => {
8 | for (let timestamp of log.split("\n")) {
9 | let date = new Date(Number(timestamp));
10 | if (date.getDay() == day) {
11 | table[date.getHours()]++;
12 | }
13 | }
14 | });
15 | }));
16 | }).then(() => table);
17 | }
18 |
19 | activityTable(6)
20 | .then(table => console.log(activityGraph(table)));
21 |
--------------------------------------------------------------------------------
/code/solutions/11_3_building_promiseall.js:
--------------------------------------------------------------------------------
1 | function Promise_all(promises) {
2 | return new Promise((resolve, reject) => {
3 | let results = [];
4 | let pending = promises.length;
5 | for (let i = 0; i < promises.length; i++) {
6 | promises[i].then(result => {
7 | results[i] = result;
8 | pending--;
9 | if (pending == 0) resolve(results);
10 | }).catch(reject);
11 | }
12 | if (promises.length == 0) resolve(results);
13 | });
14 | }
15 |
16 | // Test code.
17 | Promise_all([]).then(array => {
18 | console.log("This should be []:", array);
19 | });
20 | function soon(val) {
21 | return new Promise(resolve => {
22 | setTimeout(() => resolve(val), Math.random() * 500);
23 | });
24 | }
25 | Promise_all([soon(1), soon(2), soon(3)]).then(array => {
26 | console.log("This should be [1, 2, 3]:", array);
27 | });
28 | Promise_all([soon(1), Promise.reject("X"), soon(3)]).then(array => {
29 | console.log("We should not get here");
30 | }).catch(error => {
31 | if (error != "X") {
32 | console.log("Unexpected failure:", error);
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/code/solutions/12_1_arrays.js:
--------------------------------------------------------------------------------
1 | topScope.array = (...values) => values;
2 |
3 | topScope.length = array => array.length;
4 |
5 | topScope.element = (array, i) => array[i];
6 |
7 | run(`
8 | do(define(sum, fun(array,
9 | do(define(i, 0),
10 | define(sum, 0),
11 | while(<(i, length(array)),
12 | do(define(sum, +(sum, element(array, i))),
13 | define(i, +(i, 1)))),
14 | sum))),
15 | print(sum(array(1, 2, 3))))
16 | `);
17 | // → 6
18 |
--------------------------------------------------------------------------------
/code/solutions/12_3_comments.js:
--------------------------------------------------------------------------------
1 | function skipSpace(string) {
2 | let skippable = string.match(/^(\s|#.*)*/);
3 | return string.slice(skippable[0].length);
4 | }
5 |
6 | console.log(parse("# hello\nx"));
7 | // → {type: "word", name: "x"}
8 |
9 | console.log(parse("a # one\n # two\n()"));
10 | // → {type: "apply",
11 | // operator: {type: "word", name: "a"},
12 | // args: []}
13 |
--------------------------------------------------------------------------------
/code/solutions/12_4_fixing_scope.js:
--------------------------------------------------------------------------------
1 | specialForms.set = (args, env) => {
2 | if (args.length != 2 || args[0].type != "word") {
3 | throw new SyntaxError("Bad use of set");
4 | }
5 | let varName = args[0].name;
6 | let value = evaluate(args[1], env);
7 |
8 | for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) {
9 | if (Object.hasOwn(scope, varName)) {
10 | scope[varName] = value;
11 | return value;
12 | }
13 | }
14 | throw new ReferenceError(`Setting undefined variable ${varName}`);
15 | };
16 |
17 | run(`
18 | do(define(x, 4),
19 | define(setx, fun(val, set(x, val))),
20 | setx(50),
21 | print(x))
22 | `);
23 | // → 50
24 | run(`set(quux, true)`);
25 | // → Some kind of ReferenceError
26 |
--------------------------------------------------------------------------------
/code/solutions/14_1_build_a_table.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mountains
5 |
6 |
7 |
8 |
50 |
--------------------------------------------------------------------------------
/code/solutions/14_2_elements_by_tag_name.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Heading with a span element.
4 | A paragraph with one , two
5 | spans.
6 |
7 |
34 |
--------------------------------------------------------------------------------
/code/solutions/14_3_the_cats_hat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
28 |
--------------------------------------------------------------------------------
/code/solutions/15_1_balloon.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 🎈
4 |
5 |
30 |
--------------------------------------------------------------------------------
/code/solutions/15_2_mouse_trail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
33 |
34 |
--------------------------------------------------------------------------------
/code/solutions/15_3_tabs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Tab one
5 | Tab two
6 | Tab three
7 |
8 |
34 |
--------------------------------------------------------------------------------
/code/solutions/16_1_game_over.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
29 |
30 |
--------------------------------------------------------------------------------
/code/solutions/16_2_pausing_the_game.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
93 |
94 |
--------------------------------------------------------------------------------
/code/solutions/16_3_a_monster.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
62 |
63 |
--------------------------------------------------------------------------------
/code/solutions/17_1_shapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/code/solutions/17_2_the_pie_chart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
41 |
--------------------------------------------------------------------------------
/code/solutions/17_3_a_bouncing_ball.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
37 |
--------------------------------------------------------------------------------
/code/solutions/18_1_content_negotiation.js:
--------------------------------------------------------------------------------
1 | const url = "https://eloquentjavascript.net/author";
2 | const types = ["text/plain",
3 | "text/html",
4 | "application/json",
5 | "application/rainbows+unicorns"];
6 |
7 | async function showTypes() {
8 | for (let type of types) {
9 | let resp = await fetch(url, {headers: {accept: type}});
10 | console.log(`${type}: ${await resp.text()}\n`);
11 | }
12 | }
13 |
14 | showTypes();
15 |
--------------------------------------------------------------------------------
/code/solutions/18_2_a_javascript_workbench.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Run
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/code/solutions/18_3_conways_game_of_life.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Next generation
5 | Auto run
6 |
7 |
90 |
--------------------------------------------------------------------------------
/code/solutions/19_1_keyboard_bindings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
53 |
--------------------------------------------------------------------------------
/code/solutions/19_2_efficient_drawing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
38 |
--------------------------------------------------------------------------------
/code/solutions/19_3_circles.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
35 |
--------------------------------------------------------------------------------
/code/solutions/19_4_proper_lines.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
50 |
--------------------------------------------------------------------------------
/code/solutions/20_1_search_tool.mjs:
--------------------------------------------------------------------------------
1 | import {statSync, readdirSync, readFileSync} from "node:fs";
2 |
3 | let searchTerm = new RegExp(process.argv[2]);
4 |
5 | for (let arg of process.argv.slice(3)) {
6 | search(arg);
7 | }
8 |
9 | function search(file) {
10 | let stats = statSync(file);
11 | if (stats.isDirectory()) {
12 | for (let f of readdirSync(file)) {
13 | search(file + "/" + f);
14 | }
15 | } else if (searchTerm.test(readFileSync(file, "utf8"))) {
16 | console.log(file);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/code/solutions/20_2_directory_creation.mjs:
--------------------------------------------------------------------------------
1 | // This code won't work on its own, but is also included in the
2 | // code/file_server.js file, which defines the whole system.
3 |
4 | import {mkdir} from "node:fs/promises";
5 |
6 | methods.MKCOL = async function(request) {
7 | let path = urlPath(request.url);
8 | let stats;
9 | try {
10 | stats = await stat(path);
11 | } catch (error) {
12 | if (error.code != "ENOENT") throw error;
13 | await mkdir(path);
14 | return {status: 204};
15 | }
16 | if (stats.isDirectory()) return {status: 204};
17 | else return {status: 400, body: "Not a directory"};
18 | };
19 |
--------------------------------------------------------------------------------
/code/solutions/20_3_a_public_space_on_the_web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A Public Space on the Web
5 |
6 | This is a self-editing website. Select a file, edit it, and save to
7 | update the website.
8 |
9 | Files:
10 |
11 |
12 |
13 |
14 | Save
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/code/solutions/20_3_a_public_space_on_the_web/other.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | This is another file
5 |
--------------------------------------------------------------------------------
/code/solutions/20_3_a_public_space_on_the_web/public_space.js:
--------------------------------------------------------------------------------
1 | // Get a reference to the DOM nodes we need
2 | let filelist = document.querySelector("#filelist");
3 | let textarea = document.querySelector("#file");
4 |
5 | // This loads the initial file list from the server
6 | fetch("/").then(resp => resp.text()).then(files => {
7 | for (let file of files.split("\n")) {
8 | let option = document.createElement("option");
9 | option.textContent = file;
10 | filelist.appendChild(option);
11 | }
12 | // Now that we have a list of files, make sure the textarea contains
13 | // the currently selected one.
14 | loadCurrentFile();
15 | });
16 |
17 | // Fetch a file from the server and put it in the textarea.
18 | function loadCurrentFile() {
19 | fetch(filelist.value).then(resp => resp.text()).then(file => {
20 | textarea.value = file;
21 | });
22 | }
23 |
24 | filelist.addEventListener("change", loadCurrentFile);
25 |
26 | // Called by the button on the page. Makes a request to save the
27 | // currently selected file.
28 | function saveFile() {
29 | fetch(filelist.value, {method: "PUT",
30 | body: textarea.value});
31 | }
32 |
--------------------------------------------------------------------------------
/code/solutions/21_1_disk_persistence.mjs:
--------------------------------------------------------------------------------
1 | // This isn't a stand-alone file, only a redefinition of a few
2 | // fragments from skillsharing/skillsharing_server.js
3 |
4 | import {readFileSync, writeFile} from "node:fs";
5 |
6 | const fileName = "./talks.json";
7 |
8 | SkillShareServer.prototype.updated = function() {
9 | this.version++;
10 | let response = this.talkResponse();
11 | this.waiting.forEach(resolve => resolve(response));
12 | this.waiting = [];
13 |
14 | writeFile(fileName, JSON.stringify(this.talks), e => {
15 | if (e) throw e;
16 | });
17 | };
18 |
19 | function loadTalks() {
20 | try {
21 | return JSON.parse(readFileSync(fileName, "utf8"));
22 | } catch (e) {
23 | return {};
24 | }
25 | }
26 |
27 | // The line that starts the server must be changed to
28 | new SkillShareServer(loadTalks()).start(8000);
29 |
--------------------------------------------------------------------------------
/code/solutions/21_2_comment_field_resets.mjs:
--------------------------------------------------------------------------------
1 | // This isn't a stand-alone file, only a redefinition of the main
2 | // component from skillsharing/public/skillsharing_client.js
3 |
4 | class Talk {
5 | constructor(talk, dispatch) {
6 | this.comments = elt("div");
7 | this.dom = elt(
8 | "section", {className: "talk"},
9 | elt("h2", null, talk.title, " ", elt("button", {
10 | type: "button",
11 | onclick: () => dispatch({type: "deleteTalk",
12 | talk: talk.title})
13 | }, "Delete")),
14 | elt("div", null, "by ",
15 | elt("strong", null, talk.presenter)),
16 | elt("p", null, talk.summary),
17 | this.comments,
18 | elt("form", {
19 | onsubmit(event) {
20 | event.preventDefault();
21 | let form = event.target;
22 | dispatch({type: "newComment",
23 | talk: talk.title,
24 | message: form.elements.comment.value});
25 | form.reset();
26 | }
27 | }, elt("input", {type: "text", name: "comment"}), " ",
28 | elt("button", {type: "submit"}, "Add comment")));
29 | this.syncState(talk);
30 | }
31 |
32 | syncState(talk) {
33 | this.talk = talk;
34 | this.comments.textContent = "";
35 | for (let comment of talk.comments) {
36 | this.comments.appendChild(renderComment(comment));
37 | }
38 | }
39 | }
40 |
41 | class SkillShareApp {
42 | constructor(state, dispatch) {
43 | this.dispatch = dispatch;
44 | this.talkDOM = elt("div", {className: "talks"});
45 | this.talkMap = Object.create(null);
46 | this.dom = elt("div", null,
47 | renderUserField(state.user, dispatch),
48 | this.talkDOM,
49 | renderTalkForm(dispatch));
50 | this.syncState(state);
51 | }
52 |
53 | syncState(state) {
54 | if (state.talks == this.talks) return;
55 | this.talks = state.talks;
56 |
57 | for (let talk of state.talks) {
58 | let found = this.talkMap[talk.title];
59 | if (found && found.talk.presenter == talk.presenter &&
60 | found.talk.summary == talk.summary) {
61 | found.syncState(talk);
62 | } else {
63 | if (found) found.dom.remove();
64 | found = new Talk(talk, this.dispatch);
65 | this.talkMap[talk.title] = found;
66 | this.talkDOM.appendChild(found.dom);
67 | }
68 | }
69 | for (let title of Object.keys(this.talkMap)) {
70 | if (!state.talks.some(talk => talk.title == title)) {
71 | this.talkMap[title].dom.remove();
72 | delete this.talkMap[title];
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/code/solutions/22_1_pathfinding.js:
--------------------------------------------------------------------------------
1 | function findPath(a, b) {
2 | let work = [[a]];
3 | for (let path of work) {
4 | let end = path[path.length - 1];
5 | if (end == b) return path;
6 | for (let next of end.edges) {
7 | if (!work.some(path => path[path.length - 1] == next)) {
8 | work.push(path.concat([next]));
9 | }
10 | }
11 | }
12 | }
13 |
14 | let graph = treeGraph(4, 4);
15 | let root = graph[0], leaf = graph[graph.length - 1];
16 | console.log(findPath(root, leaf).length);
17 | // → 4
18 |
19 | leaf.connect(root);
20 | console.log(findPath(root, leaf).length);
21 | // → 2
22 |
--------------------------------------------------------------------------------
/code/solutions/22_1_prime_numbers.js:
--------------------------------------------------------------------------------
1 | function* primes() {
2 | for (let n = 2;; n++) {
3 | let skip = false;
4 | for (let d = 2; d < n; d++) {
5 | if (n % d == 0) {
6 | skip = true;
7 | break;
8 | }
9 | }
10 | if (!skip) yield n;
11 | }
12 | }
13 |
14 | function measurePrimes() {
15 | let iter = primes(), t0 = Date.now();
16 | for (let i = 0; i < 10000; i++) {
17 | iter.next();
18 | }
19 | console.log(`Took ${Date.now() - t0}ms`);
20 | }
21 |
22 | measurePrimes();
23 |
--------------------------------------------------------------------------------
/code/solutions/22_2_faster_prime_numbers.js:
--------------------------------------------------------------------------------
1 | function* primes() {
2 | let found = [];
3 | for (let n = 2;; n++) {
4 | let skip = false, root = Math.sqrt(n);
5 | for (let prev of found) {
6 | if (prev > root) {
7 | break;
8 | } else if (n % prev == 0) {
9 | skip = true;
10 | break;
11 | }
12 | }
13 | if (!skip) {
14 | found.push(n);
15 | yield n;
16 | }
17 | }
18 | }
19 |
20 | function measurePrimes() {
21 | let iter = primes(), t0 = Date.now();
22 | for (let i = 0; i < 10000; i++) {
23 | iter.next();
24 | }
25 | console.log(`Took ${Date.now() - t0}ms`);
26 | }
27 |
28 | measurePrimes();
29 |
--------------------------------------------------------------------------------
/code/solutions/22_2_timing.js:
--------------------------------------------------------------------------------
1 | function findPath(a, b) {
2 | let work = [[a]];
3 | for (let path of work) {
4 | let end = path[path.length - 1];
5 | if (end == b) return path;
6 | for (let next of end.edges) {
7 | if (!work.some(path => path[path.length - 1] == next)) {
8 | work.push(path.concat([next]));
9 | }
10 | }
11 | }
12 | }
13 |
14 | function time(findPath) {
15 | let graph = treeGraph(6, 6);
16 | let startTime = Date.now();
17 | let result = findPath(graph[0], graph[graph.length - 1]);
18 | console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);
19 | }
20 | time(findPath);
21 |
--------------------------------------------------------------------------------
/code/solutions/22_3_optimizing.js:
--------------------------------------------------------------------------------
1 | function time(findPath) {
2 | let graph = treeGraph(6, 6);
3 | let startTime = Date.now();
4 | let result = findPath(graph[0], graph[graph.length - 1]);
5 | console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);
6 | }
7 |
8 | function findPath_set(a, b) {
9 | let work = [[a]];
10 | let reached = new Set([a]);
11 | for (let path of work) {
12 | let end = path[path.length - 1];
13 | if (end == b) return path;
14 | for (let next of end.edges) {
15 | if (!reached.has(next)) {
16 | reached.add(next);
17 | work.push(path.concat([next]));
18 | }
19 | }
20 | }
21 | }
22 |
23 | time(findPath_set);
24 |
25 | function pathToArray(path) {
26 | let result = [];
27 | for (; path; path = path.via) result.unshift(path.at);
28 | return result;
29 | }
30 |
31 | function findPath_list(a, b) {
32 | let work = [{at: a, via: null}];
33 | let reached = new Set([a]);
34 | for (let path of work) {
35 | if (path.at == b) return pathToArray(path);
36 | for (let next of path.at.edges) {
37 | if (!reached.has(next)) {
38 | reached.add(next);
39 | work.push({at: next, via: path});
40 | }
41 | }
42 | }
43 | }
44 |
45 | time(findPath_list);
46 |
--------------------------------------------------------------------------------
/code/squareworker.js:
--------------------------------------------------------------------------------
1 | addEventListener("message", event => {
2 | postMessage(event.data * event.data);
3 | });
4 |
--------------------------------------------------------------------------------
/epub/META-INF/container.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/epub/content.opf.src:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Eloquent JavaScript
5 | main
6 | Marijn Haverbeke
7 | HAVERBEKE, MARIJN
8 | aut
9 | net.eloquentjavascript
10 | en-US
11 | This work is shared with the public using the Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0) license.
12 |
13 | https://eloquentjavascript.net/
14 | 2018-02-25T22:07:00Z
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {{images}}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/epub/font/cinzel_bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/epub/font/cinzel_bold.otf
--------------------------------------------------------------------------------
/epub/font/pt_mono.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/epub/font/pt_mono.otf
--------------------------------------------------------------------------------
/epub/frontmatter.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Eloquent JavaScript
6 |
9 |
10 |
11 | Eloquent JavaScript
12 | 4th edition
13 | Written by Marijn Haverbeke.
14 |
15 | Licensed under
16 | a Creative
17 | Commons attribution-noncommercial license . All code in this book
18 | may also be considered licensed under
19 | an MIT license .
20 |
21 | Illustrations by various artists: Cover
22 | by Péchane Sumi-e. Chapter illustrations by Madalina Tantareanu.
23 | Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular
24 | expression diagrams in Chapter 9 generated
25 | with regexper.com by Jeff
26 | Avallone. Game concept for Chapter 16
27 | by Thomas Palef .
28 |
29 | A paper version of Eloquent JavaScript, including a bonus
30 | chapter, is being brought out
31 | by No Starch Press . They also
32 | sell a more polished EPUB version that includes the bonus
33 | chapter.
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/epub/mimetype:
--------------------------------------------------------------------------------
1 | application/epub+zip
--------------------------------------------------------------------------------
/epub/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Cinzel';
3 | font-style: normal;
4 | font-weight: 700;
5 | src: url(font/cinzel_bold.otf);
6 | }
7 |
8 | @font-face {
9 | font-family: 'PT Mono';
10 | font-style: normal;
11 | font-weight: 400;
12 | src: url(font/pt_mono.otf);
13 | }
14 |
15 | body {
16 | font-family: Georgia, 'Nimbus Roman No9 L', 'Century Schoolbook L', serif;
17 | font-size: 100%;
18 | line-height: 1.45;
19 | color: black;
20 | background: white;
21 | text-align: left;
22 | }
23 |
24 | article {
25 | padding: 2em 0 5em 0;
26 | }
27 |
28 | pre {
29 | padding: 5px 0 5px 15px;
30 | line-height: 1.35;
31 | margin: 1rem 0;
32 | position: relative;
33 | font-size: 90%;
34 | white-space: pre-wrap;
35 | overflow-wrap: break-word;
36 | }
37 |
38 | code, pre {
39 | font-family: 'PT Mono', monospace;
40 | }
41 |
42 | code {
43 | padding: 0 2px;
44 | }
45 |
46 | h1, h2, h3 {
47 | font-family: 'Cinzel', Georgia, serif;
48 | font-weight: 700;
49 | margin: 1rem 0;
50 | letter-spacing: 2px;
51 | }
52 |
53 | h1 {
54 | font-size: 130%;
55 | }
56 | h2 {
57 | font-size: 115%;
58 | }
59 | h3 {
60 | font-size: 100%;
61 | }
62 |
63 | div.chap_num {
64 | font-family: 'Cinzel', Georgia, serif;
65 | margin-bottom: -0.8rem;
66 | }
67 |
68 | blockquote {
69 | margin: 0 0 0 3em;
70 | padding: 0;
71 | font-size: 85%;
72 | }
73 |
74 | blockquote p {
75 | color: #333;
76 | }
77 |
78 | blockquote p:first-of-type:before {
79 | content: '“';
80 | }
81 |
82 | blockquote p:last-of-type:after {
83 | content: '”';
84 | }
85 |
86 | p + footer {
87 | margin-top: -.5em;
88 | }
89 |
90 | blockquote footer cite {
91 | font-style: italic;
92 | }
93 |
94 | blockquote footer:before {
95 | content: '—';
96 | }
97 |
98 | figure img {
99 | max-width: 80%;
100 | margin-left: 30px;
101 | }
102 |
103 | figure.chapter {
104 | text-align: center;
105 | margin: 3em 0 2em;
106 | }
107 |
108 | figure.chapter img {
109 | max-width: 75%;
110 | }
111 |
112 | figure.framed img {
113 | border-radius: 50%;
114 | border: 2px solid black;
115 | }
116 |
117 | span.keyname { font-variant: small-caps }
118 |
119 | td {
120 | vertical-align: top;
121 | }
122 |
123 | td + td {
124 | padding-left: 1em;
125 | }
126 |
127 | table {
128 | margin-left: 15px;
129 | }
130 |
131 | sub, sup {
132 | line-height: 1;
133 | }
134 |
135 | sub {
136 | font-size: 60%;
137 | }
138 | sup {
139 | font-size: 70%;
140 | }
141 |
142 | ol li p {
143 | margin: 0;
144 | }
145 |
146 | /* Syntax highlighting */
147 | .cm-keyword {color: #506;}
148 | .cm-atom {color: #106;}
149 | .cm-number {color: #042;}
150 | .cm-def {color: #009;}
151 | .cm-variable-2, .cm-attribute {color: #027;}
152 | .cm-variable-3 {color: #072;}
153 | .cm-comment {color: #740;}
154 | .cm-string {color: #700;}
155 | .cm-string-2 {color: #740;}
156 | .cm-tag {color: #170;}
157 | .cm-keyword {color: #708;}
158 | .cm-atom {color: #219;}
159 | .cm-number {color: #164;}
160 | .cm-def {color: #00f;}
161 |
--------------------------------------------------------------------------------
/epub/titlepage.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cover
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/epub/toc.xhtml.src:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Table of Contents
5 |
6 |
37 |
38 |
39 |
40 | Table of Contents
41 |
42 |
43 |
44 | Introduction
45 |
46 | (Part 1: Language) Values, Types, and Operators
47 |
48 | Program Structure
49 | Functions
50 | Data Structures: Objects and Arrays
51 | Higher-order Functions
52 | The Secret Life of Objects
53 | Project: A Robot
54 | Bugs and Errors
55 | Regular Expressions
56 | Modules
57 | Asynchronous Programming
58 | Project: A Programming Language
59 |
60 | (Part 2: Browser) JavaScript and the Browser
61 |
62 | The Document Object Model
63 | Handling Events
64 | Project: A Platform Game
65 | Drawing on Canvas
66 | HTTP and Forms
67 | Project: A Pixel Art Editor
68 |
69 | (Part 3: Node) Node.js
70 |
71 | Project: Skill-Sharing Website
72 | Hints to the exercises
73 |
74 |
75 |
76 |
77 |
78 | {{full_toc}}
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/html/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/.keep
--------------------------------------------------------------------------------
/html/Eloquent_JavaScript.epub:
--------------------------------------------------------------------------------
1 | ../book.epub
--------------------------------------------------------------------------------
/html/Eloquent_JavaScript.mobi:
--------------------------------------------------------------------------------
1 | ../book.mobi
--------------------------------------------------------------------------------
/html/Eloquent_JavaScript.pdf:
--------------------------------------------------------------------------------
1 | ../book.pdf
--------------------------------------------------------------------------------
/html/Eloquent_JavaScript_small.pdf:
--------------------------------------------------------------------------------
1 | ../book_mobile.pdf
--------------------------------------------------------------------------------
/html/author.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/html/author.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Marijn Haverbeke",
3 | "email": "marijn@haverbeke.nl",
4 | "website": "https://marijnhaverbeke.nl/"
5 | }
6 |
--------------------------------------------------------------------------------
/html/author.txt:
--------------------------------------------------------------------------------
1 | My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ .
2 |
--------------------------------------------------------------------------------
/html/code:
--------------------------------------------------------------------------------
1 | ../code
--------------------------------------------------------------------------------
/html/css/game.css:
--------------------------------------------------------------------------------
1 | .background { background: rgb(52, 166, 251);
2 | table-layout: fixed;
3 | border-spacing: 0; }
4 | .background td { padding: 0; }
5 | .lava { background: rgb(255, 100, 100); }
6 | .wall { background: white; }
7 |
8 | .actor { position: absolute; }
9 | .coin { background: rgb(241, 229, 89); }
10 | .player { background: rgb(64, 64, 64); }
11 |
12 | .game {
13 | overflow: hidden;
14 | max-width: 600px;
15 | max-height: 450px;
16 | position: relative;
17 | }
18 |
19 | .lost .player {
20 | background: rgb(160, 64, 64);
21 | }
22 | .won .player {
23 | box-shadow: -4px -7px 8px white, 4px -7px 8px white;
24 | }
25 |
--------------------------------------------------------------------------------
/html/css/paint.css:
--------------------------------------------------------------------------------
1 | .picturepanel {
2 | width: -webkit-fit-content;
3 | width: -moz-fit-content;
4 | width: -ms-fit-content;
5 | width: fit-content;
6 | max-width: 500px;
7 | max-height: 300px;
8 | border: 2px solid silver;
9 | overflow: auto;
10 | position: relative;
11 | }
12 | .picturepanel canvas { display: block; }
13 | .toolbar > * { margin-right: 5px; }
14 |
--------------------------------------------------------------------------------
/html/empty.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/html/errata.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Eloquent JavaScript :: Errata
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 | These are the known mistakes in the fourth edition
17 | of the book. For errata in other editions, see these pages: first , second , third . To report a problem that is not listed
18 | here, send me an email .
19 |
20 | No issues found yet.
21 |
22 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/html/example/bert.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Bert",
3 | "spouse": "example/suzie.json"
4 | }
5 |
--------------------------------------------------------------------------------
/html/example/data.txt:
--------------------------------------------------------------------------------
1 | This is the content of data.txt
2 |
--------------------------------------------------------------------------------
/html/example/fruit.json:
--------------------------------------------------------------------------------
1 | {"banana": "yellow",
2 | "lemon": "yellow",
3 | "cherry": "red"}
4 |
--------------------------------------------------------------------------------
/html/example/fruit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/html/example/message.html:
--------------------------------------------------------------------------------
1 |
2 | Example form target
3 |
4 |
5 |
6 |
7 |
Hello , we've received your message:
8 |
9 |
10 |
11 |
No message received.
12 |
13 |
14 | (Note that this page is just an illustration. No actual message was delivered anywhere.)
15 |
16 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/html/example/muriel.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Muriel"
3 | }
4 |
--------------------------------------------------------------------------------
/html/example/submit.html:
--------------------------------------------------------------------------------
1 |
2 | Example form target
3 |
4 |
5 |
6 | You submitted...
7 |
8 |
9 |
10 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/html/example/suzie.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Suzie",
3 | "spouse": "example/bert.json",
4 | "mother": "example/muriel.json"
5 | }
6 |
--------------------------------------------------------------------------------
/html/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/favicon.ico
--------------------------------------------------------------------------------
/html/font/cinzel_bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/font/cinzel_bold.woff
--------------------------------------------------------------------------------
/html/font/pt_mono.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/font/pt_mono.woff
--------------------------------------------------------------------------------
/html/img:
--------------------------------------------------------------------------------
1 | ../img/
--------------------------------------------------------------------------------
/img/Hieres-sur-Amby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/Hieres-sur-Amby.png
--------------------------------------------------------------------------------
/img/blockquote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/blockquote.png
--------------------------------------------------------------------------------
/img/boxed-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/boxed-in.png
--------------------------------------------------------------------------------
/img/button_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/button_disabled.png
--------------------------------------------------------------------------------
/img/canvas_beziercurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_beziercurve.png
--------------------------------------------------------------------------------
/img/canvas_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_circle.png
--------------------------------------------------------------------------------
/img/canvas_fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_fill.png
--------------------------------------------------------------------------------
/img/canvas_game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_game.png
--------------------------------------------------------------------------------
/img/canvas_path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_path.png
--------------------------------------------------------------------------------
/img/canvas_pie_chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_pie_chart.png
--------------------------------------------------------------------------------
/img/canvas_quadraticcurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_quadraticcurve.png
--------------------------------------------------------------------------------
/img/canvas_scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_scale.png
--------------------------------------------------------------------------------
/img/canvas_stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_stroke.png
--------------------------------------------------------------------------------
/img/canvas_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_tree.png
--------------------------------------------------------------------------------
/img/canvas_triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_triangle.png
--------------------------------------------------------------------------------
/img/cat-animation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/cat-animation.png
--------------------------------------------------------------------------------
/img/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/cat.png
--------------------------------------------------------------------------------
/img/chapter_picture_00.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_00.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_1.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_10.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_11.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_12.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_13.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_14.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_15.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_16.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_17.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_18.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_19.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_2.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_20.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_21.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_3.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_4.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_5.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_6.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_7.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_8.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_9.jpg
--------------------------------------------------------------------------------
/img/color-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/color-field.png
--------------------------------------------------------------------------------
/img/colored-links.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/colored-links.png
--------------------------------------------------------------------------------
/img/control-io.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 | synchronous, single thread of control synchronous, two threads of control asynchronous
11 |
--------------------------------------------------------------------------------
/img/controlflow-if.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
11 |
13 |
15 |
16 |
--------------------------------------------------------------------------------
/img/controlflow-loop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
12 |
14 |
15 |
--------------------------------------------------------------------------------
/img/controlflow-nested-if.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
11 |
13 |
15 |
17 |
19 |
21 |
--------------------------------------------------------------------------------
/img/controlflow-straight.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
--------------------------------------------------------------------------------
/img/cos_sin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | cos(¼π)
24 | sin(¼π)
25 |
26 |
27 |
28 |
29 |
30 | cos(-⅔π)
31 | sin(-⅔π)
32 | sin(-⅔π)
33 |
34 |
--------------------------------------------------------------------------------
/img/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/cover.jpg
--------------------------------------------------------------------------------
/img/darkblue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/darkblue.png
--------------------------------------------------------------------------------
/img/display.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/display.png
--------------------------------------------------------------------------------
/img/drag-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/drag-bar.png
--------------------------------------------------------------------------------
/img/exercise_shapes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/exercise_shapes.png
--------------------------------------------------------------------------------
/img/flood-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/form_fields.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/form_fields.png
--------------------------------------------------------------------------------
/img/form_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/form_select.png
--------------------------------------------------------------------------------
/img/game-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/game_simpleLevel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/game_simpleLevel.png
--------------------------------------------------------------------------------
/img/generated/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/generated/.keep
--------------------------------------------------------------------------------
/img/hat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/hat.png
--------------------------------------------------------------------------------
/img/help-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/help-field.png
--------------------------------------------------------------------------------
/img/highlighted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/highlighted.png
--------------------------------------------------------------------------------
/img/holberton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/holberton.png
--------------------------------------------------------------------------------
/img/home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/home-page.png
--------------------------------------------------------------------------------
/img/html-boxes.svg:
--------------------------------------------------------------------------------
1 |
2 |
23 | here a . I also wrote a book! Read it p Hello, I am Marijn and this is... p My home page h1 body My home page title head html
24 |
--------------------------------------------------------------------------------
/img/html-links.svg:
--------------------------------------------------------------------------------
1 |
2 |
25 | I also wrote a book! ...
26 | p
27 |
28 | Hello, I am Marijn...
29 | p
30 |
31 | My home page
32 | h1
33 |
34 | body
35 |
36 | 0
37 | 1
38 | 2
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | childNodes
49 |
50 |
51 | firstChild
52 |
53 |
54 | lastChild
55 |
56 |
57 | previousSibling
58 |
59 |
60 | nextSibling
61 |
62 |
63 | parentNode
64 |
65 |
--------------------------------------------------------------------------------
/img/html-tree.svg:
--------------------------------------------------------------------------------
1 |
2 |
26 | html head title My home page body h1 My home page p Hello! I am... p I also wrote... here a .
27 |
--------------------------------------------------------------------------------
/img/line-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/linked-list.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 | value: 1 rest: value: 2 rest: value: 3 rest: null
14 |
--------------------------------------------------------------------------------
/img/middle_east_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/middle_east_graph.png
--------------------------------------------------------------------------------
/img/middle_east_graph_random.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/middle_east_graph_random.png
--------------------------------------------------------------------------------
/img/mirror.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 | mirror 1 2 3 4
12 |
--------------------------------------------------------------------------------
/img/nextjournal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/nextjournal.png
--------------------------------------------------------------------------------
/img/object.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/object.jpg
--------------------------------------------------------------------------------
/img/object_full.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/object_full.jpg
--------------------------------------------------------------------------------
/img/ostrich.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/ostrich.png
--------------------------------------------------------------------------------
/img/parcel2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/parcel2x.png
--------------------------------------------------------------------------------
/img/pixel_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/pixel_editor.png
--------------------------------------------------------------------------------
/img/pizza-squirrel.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | No squirrel, no pizza 76 Squirrel, no pizza 4 No squirrel, pizza 9 Squirrel, pizza 1
--------------------------------------------------------------------------------
/img/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/player.png
--------------------------------------------------------------------------------
/img/player_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/player_big.png
--------------------------------------------------------------------------------
/img/prompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/prompt.png
--------------------------------------------------------------------------------
/img/rabbits.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 | toString: <function> ... teeth: "small" speak: <function> killerRabbit teeth: "long, sharp, ..." type: "killer" Rabbit prototype Object create: <function> prototype ...
14 |
--------------------------------------------------------------------------------
/img/re_number.svg:
--------------------------------------------------------------------------------
1 | Start of line group #1 One of: “ 0 ” “ 1 ” “ b ” One of: digit - “ a ” “ f ” “ h ” digit End of line
16 |
--------------------------------------------------------------------------------
/img/re_pigchickens.svg:
--------------------------------------------------------------------------------
1 | digit “ ” group #1 “ pig ” “ cow ” “ chicken ” “ s ”
18 |
--------------------------------------------------------------------------------
/img/robot_idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_idle.png
--------------------------------------------------------------------------------
/img/robot_idle2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_idle2x.png
--------------------------------------------------------------------------------
/img/robot_moving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_moving.gif
--------------------------------------------------------------------------------
/img/robot_moving2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_moving2x.gif
--------------------------------------------------------------------------------
/img/skillsharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/skillsharing.png
--------------------------------------------------------------------------------
/img/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/sprites.png
--------------------------------------------------------------------------------
/img/sprites_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/sprites_big.png
--------------------------------------------------------------------------------
/img/svg-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/svg-demo.png
--------------------------------------------------------------------------------
/img/syntax_tree.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 | do define x 10 if > x 5 print "large" print "small"
--------------------------------------------------------------------------------
/img/tamil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/tamil.png
--------------------------------------------------------------------------------
/img/transform.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 | translate(50, 50) rotate(0.1*Math.PI) rotate(0.1*Math.PI) translate(50, 50)
12 |
--------------------------------------------------------------------------------
/img/tree_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/tree_graph.png
--------------------------------------------------------------------------------
/img/village.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/village.png
--------------------------------------------------------------------------------
/img/village2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/village2x.png
--------------------------------------------------------------------------------
/img/weresquirrel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/weresquirrel.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Eloquent-JavaScript",
3 | "license": "CC BY-NC 3.0",
4 | "version": "0.1.0",
5 | "author": "Marijn Haverbeke ",
6 | "description": "Sources for the book Eloquent JavaScript",
7 | "repository": {
8 | "type": "git",
9 | "url": "git://github.com/marijnh/Eloquent-JavaScript.git"
10 | },
11 | "dependencies": {
12 | "rollup": "^3.28.0",
13 | "@rollup/plugin-node-resolve": "^15.0.0",
14 | "@rollup/plugin-terser": "^0.4.4",
15 | "acorn": "^8.0.0",
16 | "acorn-walk": "^8.0.0",
17 | "@codemirror/view": "^6.20.0",
18 | "@codemirror/state": "^6.2.0",
19 | "@codemirror/language": "^6.9.0",
20 | "@codemirror/lang-css": "^6.2.0",
21 | "@codemirror/lang-html": "^6.4.0",
22 | "@codemirror/lang-javascript": "^6.2.0",
23 | "@codemirror/legacy-modes": "^6.3.0",
24 | "codemirror": "^6.0.0",
25 | "@lezer/common": "^1.1.0",
26 | "@lezer/highlight": "^1.1.0",
27 | "jszip": "^3.10.0",
28 | "markdown-it": "^14.0.0",
29 | "markdown-it-sub": "^2.0.0",
30 | "markdown-it-sup": "^2.0.0",
31 | "mime": "^2.3.1",
32 | "mold-template": "^2.0.1"
33 | },
34 | "devDependencies": {
35 | "jsdom": "^20.0.0",
36 | "promise": "^8.0.1"
37 | },
38 | "scripts": {
39 | "test": "make test"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pdf/book.tex:
--------------------------------------------------------------------------------
1 | \documentclass[fontsize=13pt,oneside,headings=small,chapterprefix]{scrbook}
2 | \usepackage{listings}
3 | \usepackage{graphicx}
4 | \PassOptionsToPackage{hyphens}{url}\usepackage{hyperref}
5 | \usepackage{natbib}
6 | \usepackage{scrhack}
7 | \usepackage{charter}
8 | \usepackage{bookmark}
9 | \usepackage{ucharclasses}
10 | \usepackage{fontspec}
11 | \usepackage{xcolor}
12 | \usepackage{pdfpages}
13 | \usepackage{arabxetex}
14 | \usepackage{makeidx}
15 |
16 | % epigraph is used to style chapter quotes
17 | \usepackage{epigraph}
18 | \setlength{\epigraphwidth}{.8\textwidth}
19 | \setlength{\epigraphrule}{0pt}
20 |
21 | \lstset{basicstyle=\ttfamily,xleftmargin=0.8em,breaklines=true,lineskip=-0.2em,aboveskip=0.8em,belowskip=0.8em}
22 | \renewcommand*{\chapterheadstartvskip}{\vspace*{3cm}}
23 | \date{}
24 |
25 | \lstset{escapeinside={$<}{>$}}
26 |
27 | \makeatletter
28 | \lst@InputCatcodes
29 | \def\lst@DefEC{%
30 | \lst@CCECUse \lst@ProcessLetter
31 | ^^80^^81^^82^^83^^84^^85^^86^^87^^88^^89^^8a^^8b^^8c^^8d^^8e^^8f%
32 | ^^90^^91^^92^^93^^94^^95^^96^^97^^98^^99^^9a^^9b^^9c^^9d^^9e^^9f%
33 | ^^a0^^a1^^a2^^a3^^a4^^a5^^a6^^a7^^a8^^a9^^aa^^ab^^ac^^ad^^ae^^af%
34 | ^^b0^^b1^^b2^^b3^^b4^^b5^^b6^^b7^^b8^^b9^^ba^^bb^^bc^^bd^^be^^bf%
35 | ^^c0^^c1^^c2^^c3^^c4^^c5^^c6^^c7^^c8^^c9^^ca^^cb^^cc^^cd^^ce^^cf%
36 | ^^d0^^d1^^d2^^d3^^d4^^d5^^d6^^d7^^d8^^d9^^da^^db^^dc^^dd^^de^^df%
37 | ^^e0^^e1^^e2^^e3^^e4^^e5^^e6^^e7^^e8^^e9^^ea^^eb^^ec^^ed^^ee^^ef%
38 | ^^f0^^f1^^f2^^f3^^f4^^f5^^f6^^f7^^f8^^f9^^fa^^fb^^fc^^fd^^fe^^ff%
39 | тявΧαίρετΑΊΡΤΕ“”
40 | ^^00}
41 | \lst@RestoreCatcodes
42 | \makeatother
43 |
44 | \setcounter{secnumdepth}{0}
45 | \setcounter{tocdepth}{1}
46 | \setmonofont[Scale=0.8]{Inconsolata LGC}
47 | \defaultfontfeatures[\emojifont]{Scale=1.15}
48 | \newfontface{\emojifont}{Symbola_hint.ttf}
49 | \newfontfamily{\cjkfont}{TW-Sung}
50 | \setTransitionsFor{MiscellaneousSymbolsAndPictographs}{\emojifont}{\normalfont}{}
51 |
52 | \newfontfamily{\cinzel}{Cinzel}
53 | \setkomafont{disposition}{\bfseries\cinzel}
54 | \definecolor{silver-chalice}{HTML}{AAAAAA}
55 | \setkomafont{chapterprefix}{\small\color{silver-chalice}}
56 | \RedeclareSectionCommand[innerskip=0pt]{chapter}
57 |
58 | \pagestyle{plain}
59 |
60 | \usepackage{newunicodechar}
61 | \newunicodechar{π}{$\pi$}
62 | \newunicodechar{ϕ}{$\varphi$}
63 | \newunicodechar{≈}{$\approx$}
64 | \newunicodechar{β}{\ss}
65 | \newunicodechar{⮪}{\emojifont{⮪}}
66 |
67 | \graphicspath{{../}}
68 | \definecolor{coveryellow}{rgb}{0.997,0.840,0.122}
69 | \definecolor{blue-bayoux}{rgb}{0.267,0.4,0.467}
70 | \hypersetup{colorlinks,linkcolor=blue-bayoux,urlcolor=black}
71 |
72 | \makeindex
73 |
74 | \begin{document}
75 |
76 | \pagecolor{coveryellow}
77 | \includepdf{../img/cover.jpg}
78 | \pagecolor{white}
79 |
80 | \author{Marijn Haverbeke}
81 |
82 | \title{Eloquent JavaScript}
83 |
84 | \subtitle{4th edition}
85 |
86 | \maketitle
87 |
88 | \frontmatter
89 |
90 | \noindent Copyright \textcopyright{} 2024 by Marijn Haverbeke
91 |
92 | \vskip 1em
93 |
94 | \noindent This work is licensed under a Creative Commons
95 | attribution-noncommercial license
96 | (\url{http://creativecommons.org/licenses/by-nc/3.0/}). All code in
97 | the book may also be considered licensed under an MIT license
98 | (\url{https://eloquentjavascript.net/code/LICENSE}).
99 |
100 | The illustrations are contributed by various artists: Cover by
101 | Péchane Sumi-e. Chapter illustrations by Madalina Tantareanu. Pixel
102 | art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular
103 | expression diagrams in Chapter 9 generated with
104 | \href{http://regexper.com}{regexper.com} by Jeff Avallone. Game
105 | concept for Chapter 16 by \href{http://lessmilk.com}{Thomas Palef}.
106 |
107 | \vskip 1em
108 |
109 | \noindent You can buy a print version of this book, with an extra
110 | bonus chapter included, printed by No Starch Press at
111 | \url{http://a-fwd.com/com=marijhaver-20&asin-com=1593279507}.
112 |
113 | {
114 | \hypersetup{hidelinks}
115 | \tableofcontents
116 | }
117 |
118 | \mainmatter
119 |
120 | \input{00_intro.tex}
121 |
122 | \input{01_values.tex}
123 |
124 | \input{02_program_structure.tex}
125 |
126 | \input{03_functions.tex}
127 |
128 | \input{04_data.tex}
129 |
130 | \input{05_higher_order.tex}
131 |
132 | \input{06_object.tex}
133 |
134 | \input{07_robot.tex}
135 |
136 | \input{08_error.tex}
137 |
138 | \input{09_regexp.tex}
139 |
140 | \input{10_modules.tex}
141 |
142 | \input{11_async.tex}
143 |
144 | \input{12_language.tex}
145 |
146 | \input{13_browser.tex}
147 |
148 | \input{14_dom.tex}
149 |
150 | \input{15_event.tex}
151 |
152 | \input{16_game.tex}
153 |
154 | \input{17_canvas.tex}
155 |
156 | \input{18_http.tex}
157 |
158 | \input{19_paint.tex}
159 |
160 | \input{20_node.tex}
161 |
162 | \input{21_skillsharing.tex}
163 |
164 | \input{hints.tex}
165 |
166 | \backmatter
167 |
168 | {
169 | \hypersetup{hidelinks}
170 | \printindex
171 | }
172 |
173 | \end{document}
174 |
--------------------------------------------------------------------------------
/pdf/build.sh:
--------------------------------------------------------------------------------
1 | xelatex $1.tex
2 | xelatex $1.tex
3 | makeindex -o $1.ind $1.idx
4 | makeindex -o $1.ind $1.idx
5 | xelatex $1.tex
6 | while ( grep -q '^LaTeX Warning: Label(s) may have changed' $1.log) \
7 | do xelatex $1.tex; done
8 |
--------------------------------------------------------------------------------
/src/add_images_to_epub.mjs:
--------------------------------------------------------------------------------
1 | import {readdirSync, lstatSync, readFileSync, writeFileSync} from "fs"
2 | import * as path from "path"
3 |
4 | let images = []
5 | function scanDir(dir) {
6 | for (let file of readdirSync(dir)) {
7 | let full = path.resolve(dir, file), type
8 | if (lstatSync(full).isDirectory())
9 | return scanDir(full)
10 | if (/\.svg$/.test(file))
11 | type = "image/svg+xml"
12 | else if (/\.png$/.test(file))
13 | type = "image/png"
14 | else if (/\.jpg$/.test(file))
15 | type = "image/jpeg"
16 | else
17 | throw new Error("Unknown image type: " + full)
18 | let local = full.slice(full.indexOf("/img/") + 1)
19 | images.push(` `)
20 | }
21 | }
22 | scanDir("epub/img")
23 |
24 | let out = readFileSync("epub/content.opf.src", "utf8").replace("{{images}}", images.join("\n"))
25 | writeFileSync("epub/content.opf", out)
26 |
--------------------------------------------------------------------------------
/src/build_code.mjs:
--------------------------------------------------------------------------------
1 | import * as fs from "fs"
2 | import * as PJSON from "./pseudo_json.mjs"
3 | import varify from "./varify.mjs"
4 |
5 | let file = process.argv[2]
6 | let input = fs.readFileSync(file, "utf8")
7 |
8 | let included = /\n```(.*?\bincludeCode:.*)\n([^]*?\n)```/g, m
9 | let files = Object.create(null)
10 | let defaultFile = "code/chapter/" + file.replace(".md", ".js")
11 |
12 | while (m = included.exec(input)) {
13 | let [_, params, snippet] = m, directive = String(PJSON.parse(params).includeCode)
14 | let file = defaultFile
15 | if (m = directive.match(/(?:\s|^)>(\S+)/))
16 | file = m[1]
17 | snippet = snippet.replace(/(\n|^)\s*\/\/ →.*\n/g, "$1")
18 | if (!/\.mjs$/.test(file)) snippet = varify(snippet)
19 | if (directive.indexOf("strip_log") > -1)
20 | snippet = snippet.replace(/(\n|^)\s*console\.log\(.*\);\n/g, "$1")
21 | if (m = directive.match(/top_lines:\s*(\d+)/))
22 | snippet = snippet.split("\n").slice(0, Number(m[1])).join("\n") + "\n"
23 | if (file in files)
24 | files[file].push(snippet)
25 | else
26 | files[file] = [snippet]
27 | }
28 |
29 | for (let file in files)
30 | fs.writeFileSync(file, files[file].join("\n"), "utf8")
31 |
--------------------------------------------------------------------------------
/src/chapter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <> :: Eloquent JavaScript
6 |
7 | <>
8 |
11 | < >
12 |
13 |
14 |
15 |
16 | <>◂ < >
17 | ●
18 | <> ▸ < >
19 | ?
20 |
21 |
22 | > id="<>"<>><>Chapter <> < ><>
23 |
24 | <>
25 |
26 |
27 | <>◂ < >
28 | ●
29 | <> ▸ < >
30 | ?
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/check_links.mjs:
--------------------------------------------------------------------------------
1 | import {readdirSync, readFileSync} from "fs"
2 |
3 | let files = Object.create(null)
4 | for (let name of readdirSync(".")) {
5 | let m = /^\d\d_(.*?)\.md$/.exec(name)
6 | if (m) files[m[1]] = readFileSync(name, "utf8")
7 | }
8 | files.fast = files.hints = ""
9 |
10 | let fail = 0
11 | function error(file, msg) {
12 | console.error(file + ": " + msg)
13 | fail = 1
14 | }
15 |
16 | let link = /\]\(([\w_]+)(?:#([\w_]+))?\)/g, m
17 | for (let file in files) {
18 | while (m = link.exec(files[file])) {
19 | let [_, file, anchor] = m
20 | let target = files[file]
21 | if (target == null)
22 | error(file, "Unknown target file: " + file)
23 | else if (anchor && target.indexOf("{{id " + anchor + "}}") == -1)
24 | error(file, "Non-existing anchor: " + file + "#" + anchor)
25 | }
26 | }
27 |
28 | process.exit(fail)
29 |
--------------------------------------------------------------------------------
/src/client/editor.mjs:
--------------------------------------------------------------------------------
1 | import {EditorView, keymap, lineNumbers} from "@codemirror/view"
2 | import {EditorState, Compartment} from "@codemirror/state"
3 | import {minimalSetup} from "codemirror"
4 | import {html} from "@codemirror/lang-html"
5 | import {javascript} from "@codemirror/lang-javascript"
6 | import {bracketMatching, syntaxHighlighting} from "@codemirror/language"
7 | import {classHighlighter} from "@lezer/highlight"
8 |
9 | let modeCompartment = new Compartment
10 |
11 | export function createState(code, mode, extensions = []) {
12 | return EditorState.create({
13 | doc: code,
14 | extensions: [
15 | extensions,
16 | modeCompartment.of(mode == "html" ? html() : javascript()),
17 | minimalSetup,
18 | syntaxHighlighting(classHighlighter),
19 | bracketMatching(),
20 | lineNumbers(),
21 | EditorView.contentAttributes.of({"aria-label": "Code editor"})
22 | ]
23 | })
24 | }
25 |
26 | export function updateLanguage(mode) {
27 | return modeCompartment.reconfigure(mode == "html" ? html() : javascript())
28 | }
29 |
--------------------------------------------------------------------------------
/src/client/index.mjs:
--------------------------------------------------------------------------------
1 | import "./ejs.mjs"
2 | import "./code.mjs"
3 |
--------------------------------------------------------------------------------
/src/client/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from "@rollup/plugin-node-resolve"
2 | import terser from "@rollup/plugin-terser"
3 |
4 | export default {
5 | input: "src/client/index.mjs",
6 | output: {
7 | file: "html/ejs.js",
8 | format: "umd"
9 | },
10 | plugins: [nodeResolve(), terser()]
11 | }
12 |
--------------------------------------------------------------------------------
/src/epub_chapter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <>
5 |
6 |
7 |
8 |
9 | <>Chapter <>
< >
10 | <> id="<>"< ><>
11 | <>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/extract_hints.mjs:
--------------------------------------------------------------------------------
1 | import {readdirSync, readFileSync} from "fs"
2 |
3 | process.stdout.write("# Exercise Hints\n\nThe hints below might help when you are stuck with one of the exercises in this book. They don't give away the entire solution, but rather try to help you find it yourself.\n\n");
4 |
5 | for (let name of readdirSync(".")) {
6 | if (!/^\d\d.*\.md$/.test(name)) continue
7 |
8 | let file = readFileSync(name, "utf8")
9 | let title = file.match(/(?:\n|^)# (.*?)\n/)[1], titleWritten = false
10 |
11 | let curSubsection
12 | let re = /\n### (.*?)\n|\{\{hint\n([^]+?)\nhint\}\}/g, m
13 | while (m = re.exec(file)) {
14 | if (m[1]) {
15 | curSubsection = m[1]
16 | } else {
17 | if (!titleWritten) {
18 | process.stdout.write(`## ${title}\n\n`)
19 | titleWritten = true
20 | }
21 | process.stdout.write(`### ${curSubsection}\n\n${m[2].trim()}\n\n`)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/generate_epub_toc.mjs:
--------------------------------------------------------------------------------
1 | import {readFileSync} from "fs"
2 | import {basename} from "path"
3 |
4 | let [template, ...chapters] = process.argv.slice(2)
5 |
6 | function esc(str) {
7 | return str.replace(/[<>&"]/g, ch => ch == "<" ? "<" : ch == ">" ? ">" : ch == "&" ? "&" : """)
8 | }
9 |
10 | let toc = ""
11 | const section = /