├── .gitignore
├── 00_intro.md
├── 01_values.md
├── 02_program_structure.md
├── 03_functions.md
├── 04_data.md
├── 05_higher_order.md
├── 06_object.md
├── 07_robot.md
├── 08_error.md
├── 09_regexp.md
├── 10_modules.md
├── 11_async.md
├── 12_language.md
├── 13_browser.md
├── 14_dom.md
├── 15_event.md
├── 16_game.md
├── 17_canvas.md
├── 18_http.md
├── 19_paint.md
├── 20_node.md
├── 21_skillsharing.md
├── Makefile
├── README.md
├── book.epub
├── book.mobi
├── book.pdf
├── book_mobile.pdf
├── code
├── LICENSE
├── animatevillage.js
├── chapter
│ ├── .keep
│ └── 22_fast.js
├── draw_layout.js
├── hangar2.js
├── hello.js
├── index.html
├── intro.js
├── journal.js
├── levels.js
├── load.js
├── packages_chapter_10.js
├── scripts.js
├── skillsharing
│ ├── .keep
│ ├── package.json
│ └── public
│ │ └── skillsharing.css
├── solutions
│ ├── 02_1_looping_a_triangle.js
│ ├── 02_2_fizzbuzz.js
│ ├── 02_3_chessboard.js
│ ├── 03_1_minimum.js
│ ├── 03_2_recursion.js
│ ├── 03_3_bean_counting.js
│ ├── 04_1_the_sum_of_a_range.js
│ ├── 04_2_reversing_an_array.js
│ ├── 04_3_a_list.js
│ ├── 04_4_deep_comparison.js
│ ├── 05_1_flattening.js
│ ├── 05_2_your_own_loop.js
│ ├── 05_3_everything.js
│ ├── 05_4_dominant_writing_direction.js
│ ├── 06_1_a_vector_type.js
│ ├── 06_2_groups.js
│ ├── 06_3_iterable_groups.js
│ ├── 06_4_borrowing_a_method.js
│ ├── 07_1_measuring_a_robot.js
│ ├── 07_2_robot_efficiency.js
│ ├── 07_3_persistent_group.js
│ ├── 08_1_retry.js
│ ├── 08_2_the_locked_box.js
│ ├── 09_1_regexp_golf.js
│ ├── 09_2_quoting_style.js
│ ├── 09_3_numbers_again.js
│ ├── 10_2_roads_module.js
│ ├── 11_1_quiet_times.js
│ ├── 11_1_tracking_the_scalpel.js
│ ├── 11_2_real_promises.js
│ ├── 11_3_building_promiseall.js
│ ├── 12_1_arrays.js
│ ├── 12_3_comments.js
│ ├── 12_4_fixing_scope.js
│ ├── 14_1_build_a_table.html
│ ├── 14_2_elements_by_tag_name.html
│ ├── 14_3_the_cats_hat.html
│ ├── 15_1_balloon.html
│ ├── 15_2_mouse_trail.html
│ ├── 15_3_tabs.html
│ ├── 16_1_game_over.html
│ ├── 16_2_pausing_the_game.html
│ ├── 16_3_a_monster.html
│ ├── 17_1_shapes.html
│ ├── 17_2_the_pie_chart.html
│ ├── 17_3_a_bouncing_ball.html
│ ├── 18_1_content_negotiation.js
│ ├── 18_2_a_javascript_workbench.html
│ ├── 18_3_conways_game_of_life.html
│ ├── 19_1_keyboard_bindings.html
│ ├── 19_2_efficient_drawing.html
│ ├── 19_3_circles.html
│ ├── 19_4_proper_lines.html
│ ├── 20_1_search_tool.mjs
│ ├── 20_2_directory_creation.mjs
│ ├── 20_3_a_public_space_on_the_web
│ │ ├── index.html
│ │ ├── other.html
│ │ └── public_space.js
│ ├── 21_1_disk_persistence.mjs
│ ├── 21_2_comment_field_resets.mjs
│ ├── 22_1_pathfinding.js
│ ├── 22_1_prime_numbers.js
│ ├── 22_2_faster_prime_numbers.js
│ ├── 22_2_timing.js
│ └── 22_3_optimizing.js
├── squareworker.js
└── stop_keys.js
├── epub
├── META-INF
│ └── container.xml
├── content.opf.src
├── font
│ ├── cinzel_bold.otf
│ └── pt_mono.otf
├── frontmatter.xhtml
├── mimetype
├── style.css
├── titlepage.xhtml
└── toc.xhtml.src
├── html
├── .keep
├── 00_intro.html
├── 01_values.html
├── 02_program_structure.html
├── 03_functions.html
├── 04_data.html
├── 05_higher_order.html
├── 06_object.html
├── 07_robot.html
├── 08_error.html
├── 09_regexp.html
├── 10_modules.html
├── 11_async.html
├── 12_language.html
├── 13_browser.html
├── 14_dom.html
├── 15_event.html
├── 16_game.html
├── 17_canvas.html
├── 18_http.html
├── 19_paint.html
├── 20_node.html
├── 21_skillsharing.html
├── author.html
├── author.json
├── author.txt
├── backers3.html
├── code
├── css
│ ├── ejs.css
│ ├── game.css
│ └── paint.css
├── ejs.js
├── empty.html
├── errata.html
├── example
│ ├── bert.json
│ ├── data.txt
│ ├── fruit.json
│ ├── fruit.xml
│ ├── message.html
│ ├── muriel.json
│ ├── submit.html
│ └── suzie.json
├── favicon.ico
├── font
│ ├── cinzel_bold.woff
│ └── pt_mono.woff
├── img
├── index.html
└── og.jpg
├── img
├── Hieres-sur-Amby.png
├── blockquote.png
├── boxed-in.png
├── button_disabled.png
├── canvas_beziercurve.png
├── canvas_circle.png
├── canvas_fill.png
├── canvas_game.png
├── canvas_path.png
├── canvas_pie_chart.png
├── canvas_quadraticcurve.png
├── canvas_scale.png
├── canvas_stroke.png
├── canvas_tree.png
├── canvas_triangle.png
├── cat-animation.png
├── cat.png
├── chapter_picture_00.jpg
├── chapter_picture_1.jpg
├── chapter_picture_10.jpg
├── chapter_picture_11.jpg
├── chapter_picture_12.jpg
├── chapter_picture_13.jpg
├── chapter_picture_14.jpg
├── chapter_picture_15.jpg
├── chapter_picture_16.jpg
├── chapter_picture_17.jpg
├── chapter_picture_18.jpg
├── chapter_picture_19.jpg
├── chapter_picture_2.jpg
├── chapter_picture_20.jpg
├── chapter_picture_21.jpg
├── chapter_picture_3.jpg
├── chapter_picture_4.jpg
├── chapter_picture_5.jpg
├── chapter_picture_6.jpg
├── chapter_picture_7.jpg
├── chapter_picture_8.jpg
├── chapter_picture_9.jpg
├── color-field.png
├── colored-links.png
├── control-io.svg
├── controlflow-else.svg
├── controlflow-if.svg
├── controlflow-loop.svg
├── controlflow-nested-if.svg
├── controlflow-straight.svg
├── cos_sin.svg
├── cover.jpg
├── darkblue.png
├── display.png
├── drag-bar.png
├── exercise_shapes.png
├── flood-grid.svg
├── form_fields.png
├── form_select.png
├── game-grid.svg
├── game_simpleLevel.png
├── hat.png
├── help-field.png
├── highlighted.png
├── holberton.png
├── home-page.png
├── html-boxes.svg
├── html-links.svg
├── html-tree.svg
├── line-grid.svg
├── linked-list.svg
├── middle_east_graph.png
├── middle_east_graph_random.png
├── mirror.svg
├── nextjournal.png
├── object.jpg
├── object_full.jpg
├── ostrich.png
├── parcel2x.png
├── pixel_editor.png
├── pizza-squirrel.svg
├── player.png
├── player_big.png
├── prompt.png
├── rabbits.svg
├── re_number.svg
├── re_pigchickens.svg
├── re_slow.svg
├── robot_idle.png
├── robot_idle2x.png
├── robot_moving.gif
├── robot_moving2x.gif
├── skillsharing.png
├── sprites.png
├── sprites_big.png
├── svg-demo.png
├── syntax_tree.svg
├── tamil.png
├── transform.svg
├── tree_graph.png
├── unicycle.svg
├── village.png
├── village2x.png
├── weresquirrel.png
└── weresquirrel.svg
├── original
├── 00_intro.md
├── 01_values.md
├── 02_program_structure.md
├── 03_functions.md
├── 04_data.md
├── 05_higher_order.md
├── 06_object.md
├── 07_robot.md
├── 08_error.md
├── 09_regexp.md
├── 10_modules.md
├── 11_async.md
├── 12_language.md
├── 13_browser.md
├── 14_dom.md
├── 15_event.md
├── 16_game.md
├── 17_canvas.md
├── 18_http.md
├── 19_paint.md
├── 20_node.md
└── 21_skillsharing.md
├── package.json
├── pdf
├── book.tex
└── build.sh
└── src
├── add_images_to_epub.mjs
├── build_code.mjs
├── chapter.html
├── chapter_info.mjs
├── check_links.mjs
├── client
├── code.mjs
├── editor.mjs
├── ejs.mjs
├── index.mjs
├── rollup.config.mjs
└── sandbox.mjs
├── epub_chapter.html
├── extract_hints.mjs
├── generate_epub_toc.mjs
├── markdown.mjs
├── pseudo_json.mjs
├── render_html.mjs
├── render_latex.mjs
├── require.js
├── run_tests.mjs
├── transform.mjs
└── varify.mjs
/.gitignore:
--------------------------------------------------------------------------------
1 | /nostarch/[012]*.tex
2 | /nostarch/hints.tex
3 | /nostarch/book.*
4 | /nostarch.pdf
5 | /pdf/[012]*.tex
6 | /pdf/hints.tex
7 | /pdf/book.aux
8 | /pdf/book.idx
9 | /pdf/book.ilg
10 | /pdf/book.ind
11 | /pdf/book.log
12 | /pdf/book.toc
13 | /pdf/book_mobile.*
14 | /pdf/*.log
15 | /code/chapter/*
16 | /code/chapter_info.js
17 | /code/file_server.mjs
18 | /code/skillsharing.zip
19 | /code/solutions/20_3_a_public_space_on_the_web.zip
20 | /code/skillsharing/*
21 | /node_modules
22 | .tern-port
23 | /toc.txt
24 | /img/cover.xcf
25 | /img/generated/*
26 | /epub/[012]*.xhtml
27 | /epub/hints.xhtml
28 | /epub/img/*
29 | /epub/content.opf
30 | /epub/toc.xhtml
31 | /scripts
32 | .DS_Store
33 | package-lock.json
34 | pnpm-lock.yaml
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CHAPTERS := $(basename $(shell ls [0-9][0-9]_*.md) .md)
2 |
3 | SVGS := $(wildcard img/*.svg)
4 |
5 | all: html book.pdf book_mobile.pdf book.epub book.mobi
6 |
7 | html: $(foreach CHAP,$(CHAPTERS),html/$(CHAP).html) html/ejs.js \
8 | code/skillsharing.zip code/solutions/20_3_a_public_space_on_the_web.zip html/code/chapter_info.js
9 |
10 | html/%.html: %.md src/render_html.mjs src/chapter.html
11 | node src/render_html.mjs $< > $@
12 | node src/build_code.mjs $<
13 |
14 | html/code/chapter_info.js: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) code/solutions/* src/chapter_info.mjs
15 | node src/chapter_info.mjs > html/code/chapter_info.js
16 |
17 | html/ejs.js: node_modules/codemirror/dist/index.js \
18 | node_modules/@codemirror/view/dist/index.js \
19 | node_modules/@codemirror/state/dist/index.js \
20 | node_modules/@codemirror/language/dist/index.js \
21 | node_modules/@codemirror/lang-html/dist/index.js \
22 | node_modules/@codemirror/lang-javascript/dist/index.js \
23 | node_modules/acorn/dist/acorn.js \
24 | node_modules/acorn-walk/dist/walk.js \
25 | src/client/*.mjs
26 | node_modules/.bin/rollup -c src/client/rollup.config.mjs
27 |
28 | code/skillsharing.zip: html/21_skillsharing.html code/skillsharing/package.json
29 | rm -f $@
30 | cd code; zip skillsharing.zip skillsharing/*.mjs skillsharing/package.json skillsharing/public/*.*
31 |
32 | code/solutions/20_3_a_public_space_on_the_web.zip: $(wildcard code/solutions/20_3_a_public_space_on_the_web/*)
33 | rm -f $@
34 | cd code/solutions; zip 20_3_a_public_space_on_the_web.zip 20_3_a_public_space_on_the_web/*
35 |
36 | test: html
37 | @for F in $(CHAPTERS); do echo Testing $$F:; node src/run_tests.mjs $$F.md; done
38 | @node src/check_links.mjs
39 | @echo Done.
40 |
41 | tex: $(foreach CHAP,$(CHAPTERS),pdf/$(CHAP).tex) pdf/hints.tex $(patsubst img/%.svg,img/generated/%.pdf,$(SVGS))
42 |
43 | book.pdf: tex pdf/book.tex
44 | cd pdf && sh build.sh book
45 | mv pdf/book.pdf .
46 |
47 | pdf/book_mobile.tex: pdf/book.tex
48 | cat pdf/book.tex | sed -e 's/natbib}/natbib}\n\\usepackage[a5paper, left=5mm, right=5mm]{geometry}/' | sed -e 's/setmonofont.Scale=0.8./setmonofont[Scale=0.75]/' > pdf/book_mobile.tex
49 |
50 | book_mobile.pdf: pdf/book_mobile.tex tex
51 | cd pdf && sh build.sh book_mobile
52 | mv pdf/book_mobile.pdf .
53 |
54 | pdf/hints.tex: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.mjs
55 | node src/extract_hints.mjs | node src/render_latex.mjs - > $@
56 |
57 | img/generated/%.pdf: img/%.svg
58 | inkscape --export-pdf=$@ $<
59 |
60 | pdf/%.tex: %.md
61 | node src/render_latex.mjs $< > $@
62 |
63 | book.epub: epub/titlepage.xhtml epub/toc.xhtml epub/hints.xhtml $(foreach CHAP,$(CHAPTERS),epub/$(CHAP).xhtml) \
64 | epub/content.opf.src epub/style.css src/add_images_to_epub.mjs
65 | rm -f $@
66 | grep ' $@
73 |
74 | epub/%.xhtml: %.md src/render_html.mjs
75 | node src/render_html.mjs --epub $< > $@
76 |
77 | epub/hints.xhtml: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.mjs src/render_html.mjs
78 | node src/extract_hints.mjs | node src/render_html.mjs --epub - > $@
79 |
80 | epubcheck: book.epub
81 | epubcheck book.epub 2>&1 | grep -v 'img/.*\.svg'
82 |
83 | book.mobi: book.epub img/cover.jpg
84 | ebook-convert book.epub book.mobi --output-profile=kindle --cover=img/cover.jpg --remove-first-image
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Eloquent JavaScript en Español
2 |
3 |
4 |
5 |
6 |
7 | Traducción de la cuarta edición de [Eloquent JavaScript](https://eloquentjavascript.net/) al español por [midudev](https://twitch.tv/midudev).
8 |
9 | [Repositorio original](https://github.com/marijnh/Eloquent-JavaScript)
10 |
11 |
20 |
21 | ## ¿Encontraste un error? ¡Colabora!
22 |
23 | **¡No edites los archivos HTML! Estos se generan automáticamente a partir de los archivos Markdown.**
24 |
25 | 1. Haz un fork de este repositorio.
26 | 2. Clona el repositorio en tu máquina local.
27 | 3. Edita los archivos Markdown con tu editor de texto favorito.
28 |
29 | ## Atribución
30 |
31 | Este libro trata de JavaScript, programación y los maravillosos mundos digitales. Puedes leerlo online aquí, o
32 | comprar tu propia copia en papel (3ª edición y en inglés).
33 |
34 | Escrito por Marijn Haverbeke.
35 |
36 |
--------------------------------------------------------------------------------
/book.epub:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/book.epub
--------------------------------------------------------------------------------
/book.mobi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/book.mobi
--------------------------------------------------------------------------------
/book.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/book.pdf
--------------------------------------------------------------------------------
/book_mobile.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/book_mobile.pdf
--------------------------------------------------------------------------------
/code/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2008-2024 by Marijn Haverbeke
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/animatevillage.js:
--------------------------------------------------------------------------------
1 | // test: no
2 |
3 | (function() {
4 | "use strict"
5 |
6 | let active = null
7 |
8 | const places = {
9 | "Casa de Alice": { x: 279, y: 100 },
10 | "Casa de Bob": { x: 295, y: 203 },
11 | "Cabaña": { x: 372, y: 67 },
12 | "Casa de Daria": { x: 183, y: 285 },
13 | "Casa de Ernie": { x: 50, y: 283 },
14 | "Granja": { x: 36, y: 118 },
15 | "Casa de Grete": { x: 35, y: 187 },
16 | "Plaza de Mercado": { x: 162, y: 110 },
17 | "Oficina de Correos": { x: 205, y: 57 },
18 | "Tienda": { x: 137, y: 212 },
19 | "Ayuntamiento": { 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.map.src = "img/village2x.png"
37 | this.map.style.cssText = "vertical-align: -8px"
38 | this.robotElt = this.node.appendChild(doc.createElement("div"))
39 | this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;`
40 | let robotPic = this.robotElt.appendChild(doc.createElement("img"))
41 | robotPic.src = "img/robot_moving2x.gif"
42 | this.parcels = []
43 |
44 | this.text = this.node.appendChild(doc.createElement("span"))
45 | this.button = this.node.appendChild(doc.createElement("button"))
46 | 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%"
47 | this.button.textContent = "Stop"
48 |
49 | this.button.addEventListener("click", () => this.clicked())
50 | this.schedule()
51 |
52 | this.updateView()
53 | this.updateParcels()
54 |
55 | this.robotElt.addEventListener("transitionend", () => this.updateParcels())
56 | }
57 |
58 |
59 | updateView() {
60 | let pos = places[this.worldState.place]
61 | this.robotElt.style.top = (pos.y - 38) + "px"
62 | this.robotElt.style.left = (pos.x - 16) + "px"
63 |
64 | this.text.textContent = ` Turn ${this.turn} `
65 | }
66 |
67 | updateParcels() {
68 | while (this.parcels.length) this.parcels.pop().remove()
69 | let heights = {}
70 | for (let {place, address} of this.worldState.parcels) {
71 | let height = heights[place] || (heights[place] = 0)
72 | heights[place] += 14
73 | let node = document.createElement("div")
74 | let offset = placeKeys.indexOf(address) * 16
75 | node.style.cssText = "position: absolute; height: 16px; width: 16px; background-image: url(img/parcel2x.png); background-position: 0 -" + offset + "px";
76 | if (place == this.worldState.place) {
77 | node.style.left = "25px"
78 | node.style.bottom = (20 + height) + "px"
79 | this.robotElt.appendChild(node)
80 | } else {
81 | let pos = places[place]
82 | node.style.left = (pos.x - 5) + "px"
83 | node.style.top = (pos.y - 10 - height) + "px"
84 | this.node.appendChild(node)
85 | }
86 | this.parcels.push(node)
87 | }
88 | }
89 |
90 | tick() {
91 | let {direction, memory} = this.robot(this.worldState, this.robotState)
92 | this.worldState = this.worldState.move(direction)
93 | this.robotState = memory
94 | this.turn++
95 | this.updateView()
96 | if (this.worldState.parcels.length == 0) {
97 | this.button.remove()
98 | this.text.textContent = ` Finished after ${this.turn} turns`
99 | this.robotElt.firstChild.src = "img/robot_idle2x.png"
100 | } else {
101 | this.schedule()
102 | }
103 | }
104 |
105 | schedule() {
106 | this.timeout = setTimeout(() => this.tick(), 1000 / speed)
107 | }
108 |
109 | clicked() {
110 | if (this.timeout == null) {
111 | this.schedule()
112 | this.button.textContent = "Stop"
113 | this.robotElt.firstChild.src = "img/robot_moving2x.gif"
114 | } else {
115 | clearTimeout(this.timeout)
116 | this.timeout = null
117 | this.button.textContent = "Start"
118 | this.robotElt.firstChild.src = "img/robot_idle2x.png"
119 | }
120 | }
121 | }
122 |
123 | window.runRobotAnimation = function(worldState, robot, robotState) {
124 | if (active && active.timeout != null)
125 | clearTimeout(active.timeout)
126 | active = new Animation(worldState, robot, robotState)
127 | }
128 | })()
129 |
--------------------------------------------------------------------------------
/code/chapter/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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 |
6 | Eloquent JavaScript :: Code Sandbox
7 |
8 |
9 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
25 |
26 | Puedes usar esta página para descargar el código fuente y soluciones a
27 | ejercicios para el libro Eloquent JavaScript, y para ejecutar código directamente
28 | en el contexto de los capítulos de ese libro, ya sea para resolver ejercicios o simplemente para experimentar.
29 |
30 |
31 | Capítulo:
32 |
33 | ejecutar código
34 |
35 |
36 |
40 |
41 |
42 | Para ejecutar el código de este capítulo localmente, utiliza estos archivos:
43 |
44 |
45 |
46 |
47 | Estos archivos contienen el código del proyecto de este capítulo:
48 |
49 |
50 |
51 | Si has resuelto el ejercicio y quieres comparar tu código con
52 | el mío, o si realmente lo intentaste, pero no puedes hacer que tu código funcione,
53 | puedes ver la
54 | solución (o descargarla ).
55 |
56 |
57 | El entorno base para este capítulo (si existe) está disponible en la
58 | caja de arena arriba, permitiéndote ejecutar los ejemplos del capítulo simplemente
59 | pegándolos en el editor.
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/code/intro.js:
--------------------------------------------------------------------------------
1 | function rango(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 suma(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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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 | constructor(members) {
33 | this.#members = members;
34 | this.#position = 0;
35 | }
36 |
37 | next() {
38 | if (this.#position >= this.#members.length) {
39 | return {done: true};
40 | } else {
41 | let result = {value: this.#members[this.#position],
42 | done: false};
43 | this.#position++;
44 | return result;
45 | }
46 | }
47 | }
48 |
49 | for (let value of Group.from(["a", "b", "c"])) {
50 | console.log(value);
51 | }
52 | // → a
53 | // → b
54 | // → c
55 |
--------------------------------------------------------------------------------
/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})/i,
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 | const {buildGraph} = require("./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 | exports.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 |
--------------------------------------------------------------------------------
/code/stop_keys.js:
--------------------------------------------------------------------------------
1 | window.addEventListener("keydown", e => {
2 | if (/Arrow|Home|End|Page/.test(e.key)) e.preventDefault()
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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/epub/font/cinzel_bold.otf
--------------------------------------------------------------------------------
/epub/font/pt_mono.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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. Village photograph in Chapter 11 by Fabrice Creuzot.
27 | Game concept for Chapter 16
28 | by Thomas Palef .
29 |
30 | A paper version of Eloquent JavaScript, including a bonus
31 | chapter, is being brought out
32 | by No Starch Press . They also
33 | sell a more polished EPUB version that includes the bonus
34 | chapter.
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/html/.keep
--------------------------------------------------------------------------------
/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 |
6 | Eloquent JavaScript :: Erratas
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
18 |
19 | Estos son los errores conocidos en la cuarta edición
20 | del libro. Para ver las erratas de otras ediciones, consulta estas páginas: primera , segunda , tercera . Para reportar un problema que no esté
24 | listado
25 | aquí, envíame un correo electrónico .
26 |
27 | Aún no se han encontrado problemas.
28 |
29 |
30 |
42 |
43 |
--------------------------------------------------------------------------------
/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/html/favicon.ico
--------------------------------------------------------------------------------
/html/font/cinzel_bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/html/font/cinzel_bold.woff
--------------------------------------------------------------------------------
/html/font/pt_mono.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/html/font/pt_mono.woff
--------------------------------------------------------------------------------
/html/img:
--------------------------------------------------------------------------------
1 | ../img/
--------------------------------------------------------------------------------
/html/og.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/html/og.jpg
--------------------------------------------------------------------------------
/img/Hieres-sur-Amby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/Hieres-sur-Amby.png
--------------------------------------------------------------------------------
/img/blockquote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/blockquote.png
--------------------------------------------------------------------------------
/img/boxed-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/boxed-in.png
--------------------------------------------------------------------------------
/img/button_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/button_disabled.png
--------------------------------------------------------------------------------
/img/canvas_beziercurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_beziercurve.png
--------------------------------------------------------------------------------
/img/canvas_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_circle.png
--------------------------------------------------------------------------------
/img/canvas_fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_fill.png
--------------------------------------------------------------------------------
/img/canvas_game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_game.png
--------------------------------------------------------------------------------
/img/canvas_path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_path.png
--------------------------------------------------------------------------------
/img/canvas_pie_chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_pie_chart.png
--------------------------------------------------------------------------------
/img/canvas_quadraticcurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_quadraticcurve.png
--------------------------------------------------------------------------------
/img/canvas_scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_scale.png
--------------------------------------------------------------------------------
/img/canvas_stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_stroke.png
--------------------------------------------------------------------------------
/img/canvas_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_tree.png
--------------------------------------------------------------------------------
/img/canvas_triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/canvas_triangle.png
--------------------------------------------------------------------------------
/img/cat-animation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/cat-animation.png
--------------------------------------------------------------------------------
/img/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/cat.png
--------------------------------------------------------------------------------
/img/chapter_picture_00.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_00.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_1.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_10.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_11.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_12.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_13.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_14.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_15.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_16.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_17.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_18.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_19.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_2.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_20.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_21.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_3.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_4.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_5.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_6.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_7.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_8.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/chapter_picture_9.jpg
--------------------------------------------------------------------------------
/img/color-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/color-field.png
--------------------------------------------------------------------------------
/img/colored-links.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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-loop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
20 |
27 |
33 |
34 |
41 |
47 |
48 |
55 |
61 |
62 |
69 |
75 |
76 |
77 |
101 |
103 |
104 |
106 | image/svg+xml
107 |
109 |
110 |
111 |
112 |
113 |
118 |
124 |
130 |
136 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/img/controlflow-straight.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
20 |
27 |
33 |
34 |
35 |
57 |
59 |
60 |
62 | image/svg+xml
63 |
65 |
66 |
67 |
68 |
69 |
74 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/cover.jpg
--------------------------------------------------------------------------------
/img/darkblue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/darkblue.png
--------------------------------------------------------------------------------
/img/display.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/display.png
--------------------------------------------------------------------------------
/img/drag-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/drag-bar.png
--------------------------------------------------------------------------------
/img/exercise_shapes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/exercise_shapes.png
--------------------------------------------------------------------------------
/img/flood-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/form_fields.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/form_fields.png
--------------------------------------------------------------------------------
/img/form_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/form_select.png
--------------------------------------------------------------------------------
/img/game-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/game_simpleLevel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/game_simpleLevel.png
--------------------------------------------------------------------------------
/img/hat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/hat.png
--------------------------------------------------------------------------------
/img/help-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/help-field.png
--------------------------------------------------------------------------------
/img/highlighted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/highlighted.png
--------------------------------------------------------------------------------
/img/holberton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/holberton.png
--------------------------------------------------------------------------------
/img/home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/middle_east_graph.png
--------------------------------------------------------------------------------
/img/middle_east_graph_random.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/nextjournal.png
--------------------------------------------------------------------------------
/img/object.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/object.jpg
--------------------------------------------------------------------------------
/img/object_full.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/object_full.jpg
--------------------------------------------------------------------------------
/img/ostrich.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/ostrich.png
--------------------------------------------------------------------------------
/img/parcel2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/parcel2x.png
--------------------------------------------------------------------------------
/img/pixel_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/player.png
--------------------------------------------------------------------------------
/img/player_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/player_big.png
--------------------------------------------------------------------------------
/img/prompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/robot_idle.png
--------------------------------------------------------------------------------
/img/robot_idle2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/robot_idle2x.png
--------------------------------------------------------------------------------
/img/robot_moving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/robot_moving.gif
--------------------------------------------------------------------------------
/img/robot_moving2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/robot_moving2x.gif
--------------------------------------------------------------------------------
/img/skillsharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/skillsharing.png
--------------------------------------------------------------------------------
/img/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/sprites.png
--------------------------------------------------------------------------------
/img/sprites_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/sprites_big.png
--------------------------------------------------------------------------------
/img/svg-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/tree_graph.png
--------------------------------------------------------------------------------
/img/village.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/village.png
--------------------------------------------------------------------------------
/img/village2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/img/village2x.png
--------------------------------------------------------------------------------
/img/weresquirrel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/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 | "@codemirror/lang-css": "^6.2.0",
13 | "@codemirror/lang-html": "^6.4.0",
14 | "@codemirror/lang-javascript": "^6.2.0",
15 | "@codemirror/language": "^6.9.0",
16 | "@codemirror/legacy-modes": "^6.3.0",
17 | "@codemirror/state": "^6.2.0",
18 | "@codemirror/view": "^6.20.0",
19 | "@lezer/common": "^1.1.0",
20 | "@lezer/highlight": "^1.1.0",
21 | "@rollup/plugin-node-resolve": "^15.0.0",
22 | "@rollup/plugin-terser": "^0.4.4",
23 | "acorn": "^8.0.0",
24 | "acorn-walk": "^8.0.0",
25 | "codemirror": "^6.0.0",
26 | "jszip": "^3.10.0",
27 | "markdown-it": "^14.0.0",
28 | "markdown-it-sub": "^2.0.0",
29 | "markdown-it-sup": "^2.0.0",
30 | "mime": "^2.3.1",
31 | "mold-template": "^2.0.1",
32 | "rollup": "^3.28.0"
33 | },
34 | "devDependencies": {
35 | "jsdom": "^20.0.0",
36 | "promise": "^8.0.1",
37 | "standard": "^17.1.0"
38 | },
39 | "scripts": {
40 | "test": "make test"
41 | },
42 | "eslintConfig": {
43 | "extends": "standard"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/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}
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{Cuarta Edición}
85 |
86 | \maketitle
87 |
88 | \frontmatter
89 |
90 | \noindent Copyright \textcopyright{} 2024 por Marijn Haverbeke
91 |
92 | \vskip 1em
93 |
94 | \noindent Este trabajo está licenciado bajo la licencia Creative Commons
95 | attribution-noncommercial
96 | (\url{http://creativecommons.org/licenses/by-nc/3.0/}). Todo el código de este libro puede ser considerado bajo la licencia MIT
97 | (\url{https://eloquentjavascript.net/code/LICENSE}).
98 |
99 | Las ilustraciones son contribuciones de varios artistas: Portada por Péchane Sumi-e. Ilustraciones de capítulos por Madalina Tantareanu. Arte de píxeles en los capítulos 7 y 16 por Antonio Perdomo Pastor. Diagramas de expresiones regulares en el capítulo 9 generados con \href{http://regexper.com}{regexper.com} por Jeff Avallone. Fotografía del pueblo en el capítulo 11 por Fabrice Creuzot. Concepto de juego para el capítulo 16 por \href{http://lessmilk.com}{Thomas Palef}.
100 |
101 | Traducción al español por \href{http://twitch.tv/midudev}{midudev}.
102 |
103 | \vskip 1em
104 |
105 | \noindent Puedes comprar una versión impresa de este libro, con un capítulo extra incluido, impreso por No Starch Press en \url{http://a-fwd.com/com=marijhaver-20&asin-com=1593279507}.
106 |
107 | {
108 | \hypersetup{hidelinks}
109 | \tableofcontents
110 | }
111 |
112 | \mainmatter
113 |
114 | \input{00_intro.tex}
115 |
116 | \input{01_values.tex}
117 |
118 | \input{02_program_structure.tex}
119 |
120 | \input{03_functions.tex}
121 |
122 | \input{04_data.tex}
123 |
124 | \input{05_higher_order.tex}
125 |
126 | \input{06_object.tex}
127 |
128 | \input{07_robot.tex}
129 |
130 | \input{08_error.tex}
131 |
132 | \input{09_regexp.tex}
133 |
134 | \input{10_modules.tex}
135 |
136 | \input{11_async.tex}
137 |
138 | \input{12_language.tex}
139 |
140 | \input{13_browser.tex}
141 |
142 | \input{14_dom.tex}
143 |
144 | \input{15_event.tex}
145 |
146 | \input{16_game.tex}
147 |
148 | \input{17_canvas.tex}
149 |
150 | \input{18_http.tex}
151 |
152 | \input{19_paint.tex}
153 |
154 | \input{20_node.tex}
155 |
156 | \input{21_skillsharing.tex}
157 |
158 | \input{hints.tex}
159 |
160 | \backmatter
161 |
162 | {
163 | \hypersetup{hidelinks}
164 | \printindex
165 | }
166 |
167 | \end{document}
168 |
--------------------------------------------------------------------------------
/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 | const file = process.argv[2]
6 | const input = fs.readFileSync(file, 'utf8')
7 |
8 | const included = /\n```(.*?\bincludeCode:.*)\n([^]*?\n)```/g; let m
9 | const files = Object.create(null)
10 | const defaultFile = 'code/chapter/' + file.replace('.md', '.js')
11 |
12 | while (m = included.exec(input)) {
13 | let [_, params, snippet] = m; const directive = String(PJSON.parse(params).includeCode)
14 | let file = defaultFile
15 | if (m = directive.match(/(?:\s|^)>(\S+)/)) { file = m[1] }
16 | snippet = snippet.replace(/(\n|^)\s*\/\/ →.*\n/g, '$1')
17 | if (!/\.mjs$/.test(file)) snippet = varify(snippet)
18 | if (directive.indexOf('strip_log') > -1) { snippet = snippet.replace(/(\n|^)\s*console\.log\(.*\);\n/g, '$1') }
19 | if (m = directive.match(/top_lines:\s*(\d+)/)) { snippet = snippet.split('\n').slice(0, Number(m[1])).join('\n') + '\n' }
20 | if (file in files) { files[file].push(snippet) } else { files[file] = [snippet] }
21 | }
22 |
23 | for (const file in files) { fs.writeFileSync(file, files[file].join('\n'), 'utf8') }
24 |
--------------------------------------------------------------------------------
/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 = /