├── .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 | Portada del libro 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 |
37 |

Licenciado bajo 38 | una Licencia de Atribución-NoComercial de Creative 39 | Commons. Todo el código en este libro 40 | puede también considerarse licenciado bajo 41 | una licencia MIT. 42 |

43 | 44 |

Ilustraciones de varios artistas: Portada 45 | por Péchane Sumi-e. Ilustraciones de capítulos por Madalina 46 | Tantareanu. Arte pixel en los Capítulos 7 y 16 por Antonio Perdomo 47 | Pastor. Diagramas de expresiones regulares en el Capítulo 9 generados 48 | con regexper.com por Jeff 49 | Avallone. Fotografía del pueblo en el Capítulo 11 por Fabrice Creuzot. Concepto de juego para el Capítulo 16 50 | por Thomas 51 | Palef.

52 |
-------------------------------------------------------------------------------- /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 |

Caja de Arena de Código
Eloquent 23 | JavaScript
24 |

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 | 34 |

35 | 36 |
37 |
38 |
39 |
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 (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 | 5 |
      
       6 | 
       7 | 
      19 | 
      
      
      --------------------------------------------------------------------------------
      /code/solutions/18_3_conways_game_of_life.html:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
       3 | 
      4 | 5 | 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 | 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 | Eloquent JavaScript 3rd edition 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 |
      1. Introduction
      2. 45 |
      3. 46 | (Part 1: Language)Values, Types, and Operators 47 |
      4. 48 |
      5. Program Structure
      6. 49 |
      7. Functions
      8. 50 |
      9. Data Structures: Objects and Arrays
      10. 51 |
      11. Higher-order Functions
      12. 52 |
      13. The Secret Life of Objects
      14. 53 |
      15. Project: A Robot
      16. 54 |
      17. Bugs and Errors
      18. 55 |
      19. Regular Expressions
      20. 56 |
      21. Modules
      22. 57 |
      23. Asynchronous Programming
      24. 58 |
      25. Project: A Programming Language
      26. 59 |
      27. 60 | (Part 2: Browser)JavaScript and the Browser 61 |
      28. 62 |
      29. The Document Object Model
      30. 63 |
      31. Handling Events
      32. 64 |
      33. Project: A Platform Game
      34. 65 |
      35. Drawing on Canvas
      36. 66 |
      37. HTTP and Forms
      38. 67 |
      39. Project: A Pixel Art Editor
      40. 68 |
      41. 69 | (Part 3: Node)Node.js 70 |
      42. 71 |
      43. Project: Skill-Sharing Website
      44. 72 |
      45. Hints to the exercises
      46. 73 |
      74 | 75 | 76 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /html/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/eloquent-javascript-es/68e378c3930492c0e2b8db2ad3d9a642f8740e09/html/.keep -------------------------------------------------------------------------------- /html/author.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
      4 |

      Marijn Haverbeke, Programmer

      6 | 7 |

      You can reach me at marijn@haverbeke.nl, or visit my web page, marijnhaverbeke.nl.

      10 |
      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 |

      Erratas
      Eloquent JavaScript, 4ª Edición 16 |
      17 |

      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 | 10 | 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 controlsynchronous, two threads of controlasynchronous
      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 | herea.I also wrote a book! Read itpHello, I am Marijn and this is...pMy home pageh1bodyMy home pagetitleheadhtml
      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: 1rest:value: 2rest:value: 3rest: 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 | mirror1234
      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 pizza76Squirrel, no pizza4No squirrel, pizza9Squirrel, pizza1
      
      
      --------------------------------------------------------------------------------
      /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>killerRabbitteeth: "long, sharp, ..."type: "killer"RabbitprototypeObjectcreate: <function>prototype...
      14 | 
      
      
      --------------------------------------------------------------------------------
      /img/re_number.svg:
      --------------------------------------------------------------------------------
       1 | Start of linegroup #1One of:01bOne of:digit-afhdigitEnd of line
      16 | 
      
      
      --------------------------------------------------------------------------------
      /img/re_pigchickens.svg:
      --------------------------------------------------------------------------------
       1 | digit group #1pigcowchickens
      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 | dodefinex10if>x5print"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 |   <<t $in.title>> :: Eloquent JavaScript
       6 |   
       7 |   <>
       8 |     
      11 |   <>
      12 | 
      13 | 
      14 | 
      15 | 21 | 22 | > id="<>"<>><>Chapter <><><> 23 | 24 | <> 25 | 26 | 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 | <<t $in.title>> 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 = /]*>]*?id="(h_.*?)".*?><\/a>(.*?)<\/h2>/g 12 | 13 | for (let chapter of chapters) { 14 | let text = readFileSync(chapter, "utf8") 15 | let base = basename(chapter) 16 | let title = /(.*?)<\/h1>/.exec(text)[1] 17 | toc += `
    • ${esc(title)} 18 |
        \n` 19 | for (let match; match = section.exec(text);) 20 | toc += `
      1. ${esc(match[2])}
      2. \n` 21 | toc += `
      22 |
    • \n` 23 | } 24 | 25 | console.log(readFileSync(template, "utf8").replace("{{full_toc}}", toc)) 26 | -------------------------------------------------------------------------------- /src/pseudo_json.mjs: -------------------------------------------------------------------------------- 1 | class Stream { 2 | constructor(str) { 3 | this.str = str 4 | this.pos = 0 5 | } 6 | 7 | err(msg) { 8 | throw new SyntaxError(msg + " at " + this.pos + " in " + JSON.stringify(this.str)) 9 | } 10 | 11 | space() { 12 | for (;;) { 13 | let next = this.next 14 | if (next == 32 || next == 9 || next == 10 || next == 13) this.pos++ 15 | else break 16 | } 17 | } 18 | 19 | get next() { 20 | return this.str.charCodeAt(this.pos) 21 | } 22 | 23 | ahead(n) { 24 | this.pos += n 25 | this.space() 26 | } 27 | } 28 | 29 | export function parse(str) { 30 | let stream = new Stream(str) 31 | stream.space() 32 | let value = parseValue(stream) 33 | if (stream.pos != stream.str.length) stream.err("Extra characters at end of input") 34 | return value 35 | } 36 | 37 | function parseValue(stream) { 38 | let next = stream.next 39 | if (next == 123) return parseObj(stream) 40 | if (next == 91) return parseArr(stream) 41 | if (next == 34) return parseStr(stream) 42 | return parseWord(stream) 43 | } 44 | 45 | function parseObj(stream) { 46 | stream.ahead(1) 47 | let obj = {} 48 | for (;;) { 49 | if (stream.next == 125) break 50 | let prop = parseWord(stream, true) 51 | if (stream.next != 58) stream.err("Expected ':'") 52 | stream.ahead(1) 53 | obj[prop] = parseValue(stream) 54 | if (stream.next == 44) stream.ahead(1) 55 | } 56 | stream.ahead(1) 57 | return obj 58 | } 59 | 60 | function parseArr(stream) { 61 | stream.ahead(1) 62 | let arr = [] 63 | for (;;) { 64 | if (stream.next == 93) break 65 | arr.push(parseValue(stream)) 66 | if (stream.next == 44) stream.ahead(1) 67 | } 68 | stream.ahead(1) 69 | return arr 70 | } 71 | 72 | function parseStr(stream) { 73 | let start = stream.pos 74 | stream.pos++ 75 | for (let escaped = false;;) { 76 | let next = stream.next 77 | stream.pos++ 78 | if (next == 34 && !escaped) break 79 | else if (isNaN(next)) stream.err("Unterminated string") 80 | escaped = next == 92 81 | } 82 | stream.space() 83 | return JSON.parse(stream.str.slice(start, stream.pos)) 84 | } 85 | 86 | function parseWord(stream, prop) { 87 | let start = stream.pos 88 | for (;;) { 89 | let next = stream.next 90 | if ((next >= 97 && next <= 122) || (next >= 65 && next <= 90) || next == 95 || (next >= 48 && next <= 57)) stream.pos++ 91 | else break 92 | } 93 | let word = stream.str.slice(start, stream.pos) 94 | if (!word) stream.err("Expected word") 95 | stream.space() 96 | if (/^(?:0x[\da-f]+|\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?)$/i.test(word)) return JSON.parse(word) 97 | if (!prop) { 98 | if (word == "true") return true 99 | if (word == "false") return false 100 | } 101 | return word 102 | } 103 | -------------------------------------------------------------------------------- /src/require.js: -------------------------------------------------------------------------------- 1 | module.exports = require 2 | -------------------------------------------------------------------------------- /src/transform.mjs: -------------------------------------------------------------------------------- 1 | function childrenText(token) { 2 | let text = "" 3 | for (let i = 0; i < token.children.length; i++) { 4 | let child = token.children[i]; 5 | if (child.type == "text" || child.type == "code_inlin") text += child.content 6 | } 7 | return text 8 | } 9 | 10 | import {createHash} from "crypto" 11 | 12 | function hash(text) { 13 | let sum = createHash("sha1") 14 | sum.update(text) 15 | return sum.digest("base64").slice(0, 10) 16 | } 17 | 18 | function startAndEnd(text) { 19 | var words = text.split(/\W+/); 20 | if (!words[0]) words.shift(); 21 | if (!words[words.length - 1]) words.pop(); 22 | if (words.length <= 6) return words.join(" "); 23 | return words.slice(0, 3).concat(words.slice(words.length - 3)).join(" "); 24 | } 25 | 26 | function tokenText(token) { 27 | if (token.type == "text" || token.type == "code_inline") return token.content 28 | else if (token.type == "softbreak") return " " 29 | else return "" 30 | } 31 | 32 | function smartQuotes(tokens, i, tex, moveQuotes) { 33 | let text = tokens[i].content, from = 0 34 | for (let j = i - 1, tt; j >= 0; j--) if (tt = tokenText(tokens[j])) { 35 | text = tt + text 36 | from = tt.length 37 | break 38 | } 39 | let to = text.length 40 | for (let j = i + 1, tt; j < tokens.length; j++) if (tt = tokenText(tokens[j])) { 41 | text += tt 42 | break 43 | } 44 | 45 | let quoted = text 46 | .replace(/([\w\.,!?\)])'/g, "$1’") 47 | .replace(/'(\w|\(\()/g, "‘$1") 48 | .replace(/([\w\.,!?\)])"/g, "$1”") 49 | .replace(/"(\w|\(\()/g, "“$1") 50 | .slice(from, to) 51 | if (moveQuotes) quoted = quoted.replace(/”([.!?:;,]+)/g, "$1”") 52 | return tex ? quoted.replace(/‘/g, "`").replace(/’/g, "'").replace(/“/g, "``").replace(/”/g, "''") : quoted 53 | } 54 | 55 | function handleIf(tokens, i, options) { 56 | let tag = tokens[i].args[0] 57 | if (options.defined.indexOf(tag) > -1) return i 58 | for (let j = i + 1; j < tokens.length; j++) if (tokens[j].type == "meta_if_close" && tokens[j].args[0] == tag) 59 | return j 60 | } 61 | 62 | const titleCaseSmallWords = "a an the at by for in of on to up and as but with or nor if console.log".split(" "); 63 | 64 | function capitalizeTitle(text) { 65 | return text.split(" ") 66 | .map(word => titleCaseSmallWords.includes(word) ? word : word[0].toUpperCase() + word.slice(1)) 67 | .join(" ") 68 | } 69 | 70 | function transformInline(tokens, options, prevType) { 71 | let capitalize = options.capitalizeTitles && prevType == "heading_open" 72 | let result = [] 73 | for (let i = 0; i < tokens.length; i++) { 74 | let tok = tokens[i], type = tok.type 75 | if (type == "meta_if_close" || (options.index === false && type == "meta_index")) { 76 | // Drop 77 | } else if (type == "meta_if_open") { 78 | i = handleIf(tokens, i, options) 79 | } else { 80 | if (type == "text" && /[\'\"]/.test(tok.content)) tok.content = smartQuotes(tokens, i, options.texQuotes, options.moveQuotes) 81 | if (capitalize) tok.content = capitalizeTitle(tok.content) 82 | result.push(tok) 83 | } 84 | } 85 | return result 86 | } 87 | 88 | function nextTag(tokens, i) { 89 | for (let j = i + 1; j < tokens.length; j++) if (tokens[j].tag) return tokens[j]; 90 | } 91 | 92 | export function transformTokens(tokens, options) { 93 | let meta = {}, result = [] 94 | for (let i = 0; i < tokens.length; i++) { 95 | let tok = tokens[i], type = tok.type 96 | if (type == "meta_meta") { 97 | for (let prop in tok.args[0]) meta[prop] = tok.args[0][prop] 98 | } else if (type == "meta_id") { 99 | let next = nextTag(tokens, i) 100 | ;(next.attrs || (next.attrs = [])).push(["id", tok.args[0]]) 101 | } else if (type == "meta_table") { 102 | nextTag(tokens, i).tableData = tok.args[0] 103 | } else if (type == "meta_if_open") { 104 | i = handleIf(tokens, i, options) 105 | } else if (type == "meta_hint_open" && options.strip === "hints") { 106 | do { i++ } while (tokens[i].type != "meta_hint_close") 107 | } else if (type == "meta_if_close" || (options.index === false && (type == "meta_indexsee" || type == "meta_index"))) { 108 | // Drop 109 | } else if (tok.tag == "h1" && options.takeTitle) { 110 | if (tokens[i + 1].children.length != 1) throw new Error("Complex H1 not supported") 111 | meta.title = tokens[i + 1].children[0].content 112 | i += 2 113 | } else { 114 | if (type == "paragraph_open") 115 | tok.hashID = "p-" + hash(startAndEnd(childrenText(tokens[i + 1]))) 116 | else if (type == "heading_open") 117 | tok.hashID = (tok.tag == "h2" ? "h-" : "i-") + hash(childrenText(tokens[i + 1])) 118 | else if (type == "fence") 119 | tok.hashID = "c-" + hash(tok.content) 120 | 121 | if (tok.children) tok.children = transformInline(tok.children, options, tokens[i - 1].type) 122 | 123 | result.push(tok) 124 | } 125 | } 126 | return {tokens: result, metadata: meta} 127 | } 128 | -------------------------------------------------------------------------------- /src/varify.mjs: -------------------------------------------------------------------------------- 1 | import {parse} from "acorn" 2 | 3 | export default function(code) { 4 | let ast 5 | try { ast = parse(code, {sourceType: "module", ecmaVersion: 2022}) } 6 | catch(_) { return code } 7 | 8 | let patches = [] 9 | ast.body.forEach(node => { 10 | if (node.type == "VariableDeclaration" && node.kind != "var") { 11 | patches.push({from: node.start, to: node.start + node.kind.length, text: "var"}) 12 | } else if (node.type == "ClassDeclaration") { 13 | patches.push({from: node.start, to: node.start, text: "var " + node.id.name + " = "}) 14 | } else if (node.type == "ImportDeclaration") { 15 | let req = "require(" + node.source.raw + ")", text 16 | if (node.specifiers.length == 0) { 17 | text = req 18 | } else if (node.specifiers.length > 1 || node.specifiers[0].type == "ImportDefaultSpecifier") { 19 | let name = "m_" + node.source.value.replace(/\W+/g, "_") + "__" 20 | text = "var " + name + " = " + req 21 | node.specifiers.forEach(spec => { 22 | if (spec.type == "ImportDefaultSpecifier") 23 | text += ", " + spec.local.name + " = " + name + ".default || " + name 24 | else if (name != null) 25 | text += ", " + spec.local.name + " = " + name + "." + spec.imported.name 26 | }) 27 | } else { 28 | text = "var " 29 | node.specifiers.forEach(spec => { 30 | if (spec.type == "ImportNamespaceSpecifier") 31 | text += spec.local.name + " = " + req 32 | else 33 | text += spec.local.name + " = " + req + "." + spec.imported.name 34 | }) 35 | } 36 | patches.push({from: node.start, to: node.end, text: text + ";"}) 37 | } else if (node.type == "ExportNamedDeclaration") { 38 | if (node.source || !node.declaration) 39 | patches.push({from: node.start, to: node.end, text: ""}) 40 | else 41 | patches.push({from: node.start, to: node.declaration.start, text: ""}) 42 | } else if (node.type == "ExportDefaultDeclaration") { 43 | if (/Declaration/.test(node.declaration.type)) { 44 | patches.push({from: node.start, to: node.declaration.start, text: ""}) 45 | } else { 46 | patches.push({from: node.start, to: node.declaration.start, text: ";("}, 47 | {from: node.declaration.end, text: ")"}) 48 | } 49 | } else if (node.type == "ExportAllDeclaration") { 50 | patches.push({from: node.start, to: node.end, text: ""}) 51 | } 52 | }) 53 | 54 | patches.sort((a, b) => a.from - b.from || (a.to || a.from) - (b.to || b.from)) 55 | let out = "", pos = 0 56 | patches.forEach(({from, to, text}) => { 57 | out += code.slice(pos, from) + text 58 | pos = to 59 | }) 60 | out += code.slice(pos) 61 | return out 62 | } 63 | --------------------------------------------------------------------------------